package certificates import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "encoding/json" "fmt" "os" "codeberg.org/codeberg/pages/config" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/registration" "github.com/rs/zerolog/log" ) const challengePath = "/.well-known/acme-challenge/" func setupAcmeConfig(cfg config.ACMEConfig) (*lego.Config, error) { var myAcmeAccount AcmeAccount var myAcmeConfig *lego.Config if cfg.AccountConfigFile == "" { return nil, fmt.Errorf("invalid acme config file: '%s'", cfg.AccountConfigFile) } if account, err := os.ReadFile(cfg.AccountConfigFile); err == nil { log.Info().Msgf("found existing acme account config file '%s'", cfg.AccountConfigFile) if err := json.Unmarshal(account, &myAcmeAccount); err != nil { return nil, err } myAcmeAccount.Key, err = certcrypto.ParsePEMPrivateKey([]byte(myAcmeAccount.KeyPEM)) if err != nil { return nil, err } myAcmeConfig = lego.NewConfig(&myAcmeAccount) myAcmeConfig.CADirURL = cfg.APIEndpoint myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048 // Validate Config _, err := lego.NewClient(myAcmeConfig) if err != nil { log.Info().Err(err).Msg("config validation failed, you might just delete the config file and let it recreate") return nil, fmt.Errorf("acme config validation failed: %w", err) } return myAcmeConfig, nil } else if !os.IsNotExist(err) { return nil, err } log.Info().Msgf("no existing acme account config found, try to create a new one") privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, err } myAcmeAccount = AcmeAccount{ Email: cfg.Email, Key: privateKey, KeyPEM: string(certcrypto.PEMEncode(privateKey)), } myAcmeConfig = lego.NewConfig(&myAcmeAccount) myAcmeConfig.CADirURL = cfg.APIEndpoint myAcmeConfig.Certificate.KeyType = certcrypto.RSA2048 tempClient, err := lego.NewClient(myAcmeConfig) if err != nil { log.Error().Err(err).Msg("Can't create ACME client, continuing with mock certs only") } else { // accept terms & log in to EAB if cfg.EAB_KID == "" || cfg.EAB_HMAC == "" { reg, err := tempClient.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: cfg.AcceptTerms}) if err != nil { log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only") } else { myAcmeAccount.Registration = reg } } else { reg, err := tempClient.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ TermsOfServiceAgreed: cfg.AcceptTerms, Kid: cfg.EAB_KID, HmacEncoded: cfg.EAB_HMAC, }) if err != nil { log.Error().Err(err).Msg("Can't register ACME account, continuing with mock certs only") } else { myAcmeAccount.Registration = reg } } if myAcmeAccount.Registration != nil { acmeAccountJSON, err := json.Marshal(myAcmeAccount) if err != nil { log.Error().Err(err).Msg("json.Marshalfailed, waiting for manual restart to avoid rate limits") select {} } log.Info().Msgf("new acme account created. write to config file '%s'", cfg.AccountConfigFile) err = os.WriteFile(cfg.AccountConfigFile, acmeAccountJSON, 0o600) if err != nil { log.Error().Err(err).Msg("os.WriteFile failed, waiting for manual restart to avoid rate limits") select {} } } } return myAcmeConfig, nil }