package letsencrypt import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "fmt" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/certificate" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/providers/http/webroot" "github.com/go-acme/lego/v4/registration" api "envoy-control-plane/internal/pkg/cert/api" cert "envoy-control-plane/internal/pkg/cert/api" ) // LEOptions is a simple struct to satisfy the necessary interface for the lego ACME client. type LEOptions struct { Email string Registration *registration.Resource key crypto.PrivateKey } func (u *LEOptions) GetEmail() string { return u.Email } func (u *LEOptions) GetRegistration() *registration.Resource { return u.Registration } func (u *LEOptions) GetPrivateKey() crypto.PrivateKey { return u.key } // LetsEncryptIssuer implements the CertIssuer interface for Let's Encrypt. type LetsEncryptIssuer struct { } // GetName returns the name of the issuer. func (l *LetsEncryptIssuer) GetName() string { return "LetsEncrypt (Production)" } // IssueCertificate implements the core certificate issuance logic using go-acme/lego. func (l *LetsEncryptIssuer) IssueCertificate(domain, webrootPath, email string) (*cert.Certificate, error) { // 1. Setup ACME Account Key and User privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, fmt.Errorf("failed to generate private key: %w", err) } acmeUser := &LEOptions{ Email: email, key: privateKey, } // 2. Configure the ACME client config := lego.NewConfig(acmeUser) config.CADirURL = lego.LEDirectoryProduction // Set the key type (this should reflect the key used in the private key) config.Certificate.KeyType = certcrypto.EC256 client, err := lego.NewClient(config) if err != nil { return nil, fmt.Errorf("failed to create ACME client: %w", err) } // 3. Set up the HTTP-01 challenge provider (Webroot) // Use the correct method to set up the Webroot provider for HTTP-01 challenge httpProvider, err := webroot.NewHTTPProvider(webrootPath) if err != nil { return nil, fmt.Errorf("failed to create HTTP-01 provider: %w", err) } if err := client.Challenge.SetHTTP01Provider(httpProvider); err != nil { return nil, fmt.Errorf("failed to set HTTP-01 provider: %w", err) } // 4. Register the ACME account reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) if err != nil { return nil, fmt.Errorf("failed to register ACME account: %w", err) } acmeUser.Registration = reg // 5. Issue the certificate request := certificate.ObtainRequest{ Domains: []string{domain}, Bundle: true, // Include full chain } certResources, err := client.Certificate.Obtain(request) if err != nil { return nil, fmt.Errorf("failed to obtain certificate: %w", err) } // 6. Map the results to the generic Certificate struct // Make sure to handle the private key properly here. return &api.Certificate{ Domain: domain, CertPEM: certResources.Certificate, KeyPEM: certResources.PrivateKey, FullChain: certResources.Certificate, // lego's Certificate field includes the chain AccountKey: privateKey.D.Bytes(), // This is safer, as it assumes the type is ecdsa.PrivateKey }, nil }