diff --git a/internal/cert_manager/manager.go b/internal/cert_manager/manager.go index 05dd50c7..bfd28419 100644 --- a/internal/cert_manager/manager.go +++ b/internal/cert_manager/manager.go @@ -96,7 +96,7 @@ func (t KeyType) String() string { return string(t) } -// Interface inspired to https://godoc.org/golang.org/x/crypto/acme/autocert#Manager but not compatible guarantee +// Manager interface inspired to https://godoc.org/golang.org/x/crypto/acme/autocert#Manager but not compatible guarantee type Manager struct { CertificateIssueTimeout time.Duration Cache cache.Bytes @@ -105,15 +105,15 @@ type Manager struct { // Every subdomain must have suffix dot. For example: "www." AutoSubdomains []string - // Client is used to perform low-level operations, such as account registration + // acmeClient is used to perform low-level operations, such as account registration // and requesting new certificates. // - // If Client is nil, a zero-value acme.Client is used with acme.LetsEncryptURL - // as directory endpoint. If the Client.Key is nil, a new ECDSA P-256 key is + // If acmeClient is nil, a zero-value acme.Client is used with acme.LetsEncryptURL + // as directory endpoint. If the acmeClient.Key is nil, a new ECDSA P-256 key is // generated and, if Cache is not nil, stored in cache. // // Mutating the field after the first call of GetCertificate method will have no effect. - Client AcmeClient + acmeClient AcmeClient DomainChecker DomainChecker EnableHTTPValidation bool EnableTLSValidation bool @@ -136,7 +136,7 @@ type Manager struct { func New(client AcmeClient, c cache.Bytes, r prometheus.Registerer) *Manager { res := Manager{} - res.Client = client + res.acmeClient = client res.certForDomainAuthorize = cache.NewMemoryValueLRU("authcert") res.certState = cache.NewMemoryValueLRU("certstate") res.CertificateIssueTimeout = time.Minute @@ -193,6 +193,10 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (resultCert *tls.Ce return m.getCertificate(ctx, needDomain, KeyRSA) } +func (m *Manager) GetAcmeClient() (AcmeClient, error) { + return m.acmeClient, nil +} + //nolint:funlen,gocognit func (m *Manager) getCertificate(ctx context.Context, needDomain domain.DomainName, certType KeyType) (resultCert *tls.Certificate, err error) { switch certType { @@ -414,13 +418,19 @@ func (m *Manager) createCertificateForDomains(ctx context.Context, cd CertDescri logger.Debug("Start issue process") - order, err := m.createOrderForDomains(ctx, domainNames...) + acmeClient, err := m.GetAcmeClient() + log.DebugError(logger, err, "Get acme client") + if err != nil { + return nil, xerrors.Errorf("failed to get acme client: %w", err) + } + + order, err := m.createOrderForDomains(ctx, acmeClient, domainNames...) log.DebugWarning(logger, err, "Domains authorized") if err != nil { return nil, errors.New("order authorization error") } - res, err = m.issueCertificate(ctx, cd, order) + res, err = m.issueCertificate(ctx, acmeClient, cd, order) log.DebugWarning(logger, err, "Issue certificate") return res, err } @@ -439,8 +449,7 @@ func (m *Manager) supportedChallenges() []string { // createOrderForDomains similar to func (m *Manager) verifyRFC(ctx context.Context, client *acme.Client, domain string) (*acme.Order, error) // from acme/autocert //nolint:funlen,gocognit -func (m *Manager) createOrderForDomains(ctx context.Context, domains ...domain.DomainName) (*acme.Order, error) { - client := m.Client +func (m *Manager) createOrderForDomains(ctx context.Context, acmeClient AcmeClient, domains ...domain.DomainName) (*acme.Order, error) { logger := zc.L(ctx) challengeTypes := m.supportedChallenges() logger.Debug("Start order authorization.") @@ -463,7 +472,7 @@ authorizeOrderLoop: authIDs[i] = acme.AuthzID{Type: "dns", Value: domains[i].ASCII()} } var err error - order, err = m.Client.AuthorizeOrder(ctx, authIDs) + order, err = acmeClient.AuthorizeOrder(ctx, authIDs) log.DebugError(logger, err, "Create authorization order.", zap.Reflect("order", order)) if err != nil { return nil, err @@ -480,7 +489,7 @@ authorizeOrderLoop: defer cancel() revokeCtx = zc.WithLogger(revokeCtx, revokeLogger) - m.deactivatePendingAuthz(revokeCtx, order.AuthzURLs) + m.deactivatePendingAuthz(revokeCtx, acmeClient, order.AuthzURLs) }(order) }(order) @@ -501,7 +510,7 @@ authorizeOrderLoop: // Satisfy all pending authorizations. authDomainLoop: for _, zurl := range order.AuthzURLs { - z, err := client.GetAuthorization(ctx, zurl) + z, err := acmeClient.GetAuthorization(ctx, zurl) log.DebugError(logger, err, "Get authorization object.", zap.Reflect("authorization", z)) if err != nil { @@ -528,7 +537,7 @@ authorizeOrderLoop: hasCompatibleChallenge = true // Respond to the challenge and wait for validation result. - cleanup, err := m.fulfill(ctx, chal, domain.DomainName(z.Identifier.Value)) + cleanup, err := m.fulfill(ctx, acmeClient, chal, domain.DomainName(z.Identifier.Value)) log.DebugError(logger, err, "Write respond to challenge") if err != nil { continue authorizeOrderLoop @@ -539,12 +548,12 @@ authorizeOrderLoop: //noinspection GoDeferInLoop defer cleanup(cleanupContext) - authorizedChallenge, err := client.Accept(ctx, chal) + authorizedChallenge, err := acmeClient.Accept(ctx, chal) log.DebugError(logger, err, "accept authorization", zap.Reflect("authorized_challenge", authorizedChallenge)) if err != nil { continue authorizeOrderLoop } - authorization, err := client.WaitAuthorization(ctx, z.URI) + authorization, err := acmeClient.WaitAuthorization(ctx, z.URI) log.DebugError(logger, err, "wait authorization", zap.Reflect("authorization", authorization)) if err != nil { continue authorizeOrderLoop @@ -558,7 +567,7 @@ authorizeOrderLoop: // All authorizations are satisfied. // Wait for the CA to update the order status. - order, err = client.WaitOrder(ctx, order.URI) + order, err = acmeClient.WaitOrder(ctx, order.URI) log.DebugWarning(logger, err, "Wait order authorization.", zap.Reflect("order", order)) if err == nil { break authorizeOrderLoop @@ -567,7 +576,7 @@ authorizeOrderLoop: return order, nil } -func (m *Manager) issueCertificate(ctx context.Context, cd CertDescription, order *acme.Order) (*tls.Certificate, error) { +func (m *Manager) issueCertificate(ctx context.Context, acmeClient AcmeClient, cd CertDescription, order *acme.Order) (*tls.Certificate, error) { if len(order.Identifiers) == 0 { return nil, errors.New("no domains for issue certificate") } @@ -590,7 +599,7 @@ func (m *Manager) issueCertificate(ctx context.Context, cd CertDescription, orde return nil, err } - der, _, err := m.Client.CreateOrderCert(ctx, order.FinalizeURL, csr, true) + der, _, err := acmeClient.CreateOrderCert(ctx, order.FinalizeURL, csr, true) log.InfoError(logger, err, "Receive certificate from acme server") if err != nil { return nil, err @@ -630,18 +639,18 @@ func (m *Manager) renewCertInBackground(ctx context.Context, needDomain domain.D log.DebugError(logger, err, "Cert reissue in background finished") } -func (m *Manager) deactivatePendingAuthz(ctx context.Context, uries []string) { +func (m *Manager) deactivatePendingAuthz(ctx context.Context, acmeClient AcmeClient, uries []string) { logger := zc.L(ctx) for _, uri := range uries { localLogger := logger.With(zap.String("uri", uri)) - authorization, err := m.Client.GetAuthorization(ctx, uri) + authorization, err := acmeClient.GetAuthorization(ctx, uri) log.DebugError(localLogger, err, "Get authorization", zap.Reflect("authorization", authorization)) if err != nil { continue } if authorization.Status == acme.StatusPending { - err := m.Client.RevokeAuthorization(ctx, uri) + err := acmeClient.RevokeAuthorization(ctx, uri) log.DebugInfo(localLogger, err, "Revoke authorization", zap.String("uri", uri)) } else { localLogger.Debug("Authorization not in pending state. Skip revoke.", zap.String("status", authorization.Status)) @@ -683,12 +692,12 @@ func (m *Manager) filterTlsHello(ctx context.Context, hello *tls.ClientHelloInfo hello.CipherSuites = newChipers } -func (m *Manager) fulfill(ctx context.Context, challenge *acme.Challenge, domain domain.DomainName) (func(context.Context), error) { +func (m *Manager) fulfill(ctx context.Context, acmeClient AcmeClient, challenge *acme.Challenge, domain domain.DomainName) (func(context.Context), error) { logger := zc.L(ctx) switch challenge.Type { case tlsAlpn01: - cert, err := m.Client.TLSALPN01ChallengeCert(challenge.Token, domain.String()) + cert, err := acmeClient.TLSALPN01ChallengeCert(challenge.Token, domain.String()) log.DebugError(logger, err, "Got TLSALPN01ChallengeCert", log.Cert(&cert)) if err != nil { return nil, err @@ -699,7 +708,7 @@ func (m *Manager) fulfill(ctx context.Context, challenge *acme.Challenge, domain go m.deleteCertToken(localContext, domain) }, nil case http01: - resp, err := m.Client.HTTP01ChallengeResponse(challenge.Token) + resp, err := acmeClient.HTTP01ChallengeResponse(challenge.Token) if err != nil { return nil, err } diff --git a/internal/cert_manager/manager_issues_test.go b/internal/cert_manager/manager_issues_test.go index 311e1d9c..3cec5112 100644 --- a/internal/cert_manager/manager_issues_test.go +++ b/internal/cert_manager/manager_issues_test.go @@ -3,12 +3,13 @@ package cert_manager import ( "context" "errors" + "testing" + "github.com/gojuno/minimock/v3" "github.com/maxatome/go-testdeep" "github.com/rekby/lets-proxy2/internal/th" "golang.org/x/crypto/acme" "golang.org/x/xerrors" - "testing" ) // Test nil in getAuthorized @@ -45,8 +46,8 @@ func TestIssue_134(t *testing.T) { return nil, testErr }) - m := &Manager{Client: client} - res, err := m.createOrderForDomains(ctx, testDomain) + m := &Manager{} + res, err := m.createOrderForDomains(ctx, client, testDomain) td.Nil(res) td.True(xerrors.Is(err, testErr)) } diff --git a/internal/cert_manager/manager_test.go b/internal/cert_manager/manager_test.go index de86870c..7209a248 100644 --- a/internal/cert_manager/manager_test.go +++ b/internal/cert_manager/manager_test.go @@ -266,7 +266,7 @@ func createManager(t *testing.T) (res testManagerContext, cancel func()) { res.manager = &Manager{ CertificateIssueTimeout: time.Second, Cache: res.cache, - Client: res.client, + acmeClient: res.client, DomainChecker: res.domainChecker, EnableHTTPValidation: true, EnableTLSValidation: true,