diff --git a/acme/acme.go b/acme/acme.go index f047980..693b5ef 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -8,9 +8,9 @@ import ( "crypto/x509" "encoding/pem" "errors" - "fmt" "net/http" "os" + "time" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/certificate" @@ -21,25 +21,33 @@ import ( domainrouter "github.com/pablu23/domain-router" ) -func SetupAcme(config *domainrouter.Config) error { - acme := config.Server.Ssl.Acme +type Acme struct { + user *User + client *lego.Client + domains []string + certFilePath string + keyFilePath string + renewTicker *time.Ticker +} +func SetupAcme(config *domainrouter.Config) (*Acme, error) { + acme := config.Server.Ssl.Acme var privateKey *ecdsa.PrivateKey if _, err := os.Stat(acme.KeyFile); errors.Is(err, os.ErrNotExist) { privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - return err + return nil, err } - err = os.WriteFile(acme.KeyFile, []byte(encode(privateKey)), 0666) + err = os.WriteFile(acme.KeyFile, []byte(encodePrivKey(privateKey)), 0666) if err != nil { - return err + return nil, err } } else { keyBytes, err := os.ReadFile(acme.KeyFile) if err != nil { - return err + return nil, err } - privateKey = decode(string(keyBytes)) + privateKey = decodePrivKey(string(keyBytes)) } user := User{ @@ -56,23 +64,23 @@ func SetupAcme(config *domainrouter.Config) error { client, err := lego.NewClient(leConfig) if err != nil { - return err + return nil, err } // strconv.Itoa(config.Server.Port) - err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002")) + err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", acme.Http01Port)) if err != nil { - return err + return nil, err } - err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "5001")) + err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", acme.TlsAlpn01Port)) if err != nil { - return err + return nil, err } reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) if err != nil { - return err + return nil, err } user.Registration = reg @@ -87,22 +95,76 @@ func SetupAcme(config *domainrouter.Config) error { } certificates, err := client.Certificate.Obtain(request) + if err != nil { + return nil, err + } + + err = os.WriteFile(config.Server.Ssl.CertFile, certificates.Certificate, 0666) + if err != nil { + return nil, err + } + + err = os.WriteFile(config.Server.Ssl.KeyFile, certificates.PrivateKey, 0666) + if err != nil { + return nil, err + } + + d, err := time.ParseDuration(acme.RenewTime) + if err != nil { + return nil, err + } + + return &Acme{ + user: &user, + client: client, + domains: domains, + certFilePath: config.Server.Ssl.CertFile, + keyFilePath: config.Server.Ssl.KeyFile, + renewTicker: time.NewTicker(d), + }, nil +} + +func (a *Acme) RenewAcme() error { + request := certificate.ObtainRequest{ + Domains: a.domains, + Bundle: true, + } + + certificates, err := a.client.Certificate.Obtain(request) + if err != nil { + return err + } + + err = os.WriteFile(a.certFilePath, certificates.Certificate, 0666) + if err != nil { + return err + } + + err = os.WriteFile(a.keyFilePath, certificates.PrivateKey, 0666) if err != nil { return err } - fmt.Printf("%#v\n", certificates) return nil } -func encode(privateKey *ecdsa.PrivateKey) string { +func (a *Acme) RegisterTicker() { + for { + select { + case <-a.renewTicker.C: + a.RenewAcme() + } + } +} + +func encodePrivKey(privateKey *ecdsa.PrivateKey) string { x509Encoded, _ := x509.MarshalECPrivateKey(privateKey) pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509Encoded}) return string(pemEncoded) } -func decode(pemEncoded string) *ecdsa.PrivateKey { +func decodePrivKey(pemEncoded string) *ecdsa.PrivateKey { block, _ := pem.Decode([]byte(pemEncoded)) x509Encoded := block.Bytes privateKey, _ := x509.ParseECPrivateKey(x509Encoded) diff --git a/cmd/domain-router/main.go b/cmd/domain-router/main.go index 5398dbc..5ef44f5 100644 --- a/cmd/domain-router/main.go +++ b/cmd/domain-router/main.go @@ -70,12 +70,15 @@ func main() { } if config.Server.Ssl.Acme.Enabled { - err := acme.SetupAcme(config) + acmeRenewer, err := acme.SetupAcme(config) if err != nil { log.Fatal().Err(err).Msg("unable to setup acme") } - } + go func() { + acmeRenewer.RegisterTicker() + }() + } log.Info().Int("port", config.Server.Port).Str("cert", config.Server.Ssl.CertFile).Str("key", config.Server.Ssl.KeyFile).Msg("Starting server") err := server.ListenAndServeTLS("", "") log.Fatal().Err(err).Str("cert", config.Server.Ssl.CertFile).Str("key", config.Server.Ssl.KeyFile).Int("port", config.Server.Port).Msg("Could not start server") diff --git a/config.go b/config.go index c2f70cc..6b11f4e 100644 --- a/config.go +++ b/config.go @@ -12,10 +12,13 @@ type Config struct { CertFile string `yaml:"certFile"` KeyFile string `yaml:"keyFile"` Acme struct { - Enabled bool `yaml:"enabled"` - Email string `yaml:"email"` - KeyFile string `yaml:"keyFile"` - CADirURL string `yaml:"caDirUrl"` + Enabled bool `yaml:"enabled"` + Email string `yaml:"email"` + KeyFile string `yaml:"keyFile"` + CADirURL string `yaml:"caDirUrl"` + Http01Port string `yaml:"http01Port"` + TlsAlpn01Port string `yaml:"tlsAlpn01Port"` + RenewTime string `yaml:"renewTime"` } `yaml:"acme"` } `yaml:"ssl"` } `yaml:"server"` diff --git a/config.yaml b/config.yaml index b51395e..4b0c778 100644 --- a/config.yaml +++ b/config.yaml @@ -9,6 +9,9 @@ server: email: me@pablu.de keyFile: userKey.key caDirUrl: https://192.168.2.154:14000/dir + tlsAlpn01Port: 5001 + http01Port: 5002 + renewTime: 30s logging: