diff --git a/pkg/certmanager/provisioner.go b/pkg/certmanager/provisioner.go index 5a24d4ecd..5457cf211 100644 --- a/pkg/certmanager/provisioner.go +++ b/pkg/certmanager/provisioner.go @@ -85,7 +85,7 @@ func (p *Provisioner) checkPendingDomains(ctx context.Context) error { return nil } - p.logger.InfoCtx(ctx, "found domains with pending challenges", log.Int("count", len(domains))) + p.logger.InfoCtx(ctx, "found domains needing SSL provisioning", log.Int("count", len(domains))) for _, domain := range domains { select { @@ -94,10 +94,10 @@ func (p *Provisioner) checkPendingDomains(ctx context.Context) error { default: } - if err := p.completeDomainCertificate(ctx, conn, domain); err != nil { + if err := p.provisionDomainCertificate(ctx, conn, domain); err != nil { p.logger.ErrorCtx( ctx, - "cannot complete certificate for domain", + "cannot provision certificate for domain", log.String("domain", domain.Domain), log.Error(err), ) @@ -108,11 +108,53 @@ func (p *Provisioner) checkPendingDomains(ctx context.Context) error { }) } -func (p *Provisioner) completeDomainCertificate( +func (p *Provisioner) provisionDomainCertificate( ctx context.Context, conn pg.Conn, domain *coredata.CustomDomain, ) error { + if domain.SSLStatus == coredata.CustomDomainSSLStatusPending { + p.logger.InfoCtx(ctx, "initiating HTTP challenge for domain", log.String("domain", domain.Domain)) + + challenge, err := p.acmeService.GetHTTPChallenge(ctx, domain.Domain) + if err != nil { + p.logger.ErrorCtx( + ctx, + "failed to get HTTP challenge", + log.String("domain", domain.Domain), + log.Error(err), + ) + return err + } + + // Update domain with challenge details and set to PROVISIONING + scope := coredata.NewScope(domain.OrganizationID.TenantID()) + fullDomain := &coredata.CustomDomain{} + if err := fullDomain.LoadByIDForUpdate(ctx, conn, scope, p.encryptionKey, domain.ID); err != nil { + return fmt.Errorf("cannot load domain for update: %w", err) + } + + fullDomain.HTTPChallengeToken = &challenge.Token + fullDomain.HTTPChallengeKeyAuth = &challenge.KeyAuth + fullDomain.HTTPChallengeURL = &challenge.URL + fullDomain.HTTPOrderURL = &challenge.OrderURL + fullDomain.SSLStatus = coredata.CustomDomainSSLStatusProvisioning + + if err := fullDomain.Update(ctx, conn, scope, p.encryptionKey); err != nil { + return fmt.Errorf("failed to update domain with challenge: %w", err) + } + + p.logger.InfoCtx( + ctx, + "HTTP challenge initiated, will complete in next cycle", + log.String("domain", domain.Domain), + log.String("token", challenge.Token), + ) + + return nil + } + + // Domain already has challenge details, complete it challenge := &HTTPChallenge{ Domain: domain.Domain, Token: *domain.HTTPChallengeToken, @@ -151,8 +193,7 @@ func (p *Provisioner) completeDomainCertificate( chainStr := string(cert.ChainPEM) fullDomain.SSLCertificateChain = &chainStr fullDomain.SSLExpiresAt = &cert.ExpiresAt - status := coredata.CustomDomainSSLStatusActive - fullDomain.SSLStatus = &status + fullDomain.SSLStatus = coredata.CustomDomainSSLStatusActive fullDomain.HTTPChallengeToken = nil fullDomain.HTTPChallengeKeyAuth = nil diff --git a/pkg/certmanager/renewer.go b/pkg/certmanager/renewer.go index 5497cb0ad..4607be2c4 100644 --- a/pkg/certmanager/renewer.go +++ b/pkg/certmanager/renewer.go @@ -24,7 +24,6 @@ import ( "github.com/getprobo/probo/pkg/crypto/cipher" "go.gearno.de/kit/log" "go.gearno.de/kit/pg" - "go.gearno.de/x/ref" ) type ( @@ -136,7 +135,7 @@ func (r *Renewer) renewDomain(ctx context.Context, conn pg.Conn, domain *coredat return fmt.Errorf("cannot lock domain for renewal: %w", err) } - if lockedDomain.SSLStatus == nil || *lockedDomain.SSLStatus != coredata.CustomDomainSSLStatusActive { + if lockedDomain.SSLStatus != coredata.CustomDomainSSLStatusActive { r.logger.InfoCtx( ctx, "domain status changed, skipping renewal", @@ -164,7 +163,7 @@ func (r *Renewer) renewDomain(ctx context.Context, conn pg.Conn, domain *coredat lockedDomain.HTTPChallengeKeyAuth = &challenge.KeyAuth lockedDomain.HTTPChallengeURL = &challenge.URL lockedDomain.HTTPOrderURL = &challenge.OrderURL - lockedDomain.SSLStatus = ref.Ref(coredata.CustomDomainSSLStatusRenewing) + lockedDomain.SSLStatus = coredata.CustomDomainSSLStatusRenewing if err := lockedDomain.Update(ctx, conn, scope, r.encryptionKey); err != nil { return fmt.Errorf("cannot update domain with renewal challenge: %w", err) @@ -189,7 +188,7 @@ func (r *Renewer) renewDomain(ctx context.Context, conn pg.Conn, domain *coredat chainStr := string(cert.ChainPEM) lockedDomain.SSLCertificateChain = &chainStr lockedDomain.SSLExpiresAt = &cert.ExpiresAt - lockedDomain.SSLStatus = ref.Ref(coredata.CustomDomainSSLStatusActive) + lockedDomain.SSLStatus = coredata.CustomDomainSSLStatusActive lockedDomain.HTTPChallengeToken = nil lockedDomain.HTTPChallengeKeyAuth = nil diff --git a/pkg/certmanager/selector.go b/pkg/certmanager/selector.go index 51ed4f182..c91913d40 100644 --- a/pkg/certmanager/selector.go +++ b/pkg/certmanager/selector.go @@ -118,7 +118,7 @@ func (s *Selector) rebuildCacheEntry(ctx context.Context, conn pg.Conn, domain s return fmt.Errorf("domain is not active") } - if customDomain.SSLStatus == nil || *customDomain.SSLStatus != coredata.CustomDomainSSLStatusActive { + if customDomain.SSLStatus != coredata.CustomDomainSSLStatusActive { return fmt.Errorf("domain does not have active SSL certificate") } diff --git a/pkg/coredata/custom_domain.go b/pkg/coredata/custom_domain.go index a2b0ce844..7db982b1b 100644 --- a/pkg/coredata/custom_domain.go +++ b/pkg/coredata/custom_domain.go @@ -30,24 +30,24 @@ import ( type ( CustomDomain struct { - ID gid.GID `db:"id"` - OrganizationID gid.GID `db:"organization_id"` - Domain string `db:"domain"` - HTTPChallengeToken *string `db:"http_challenge_token"` - HTTPChallengeKeyAuth *string `db:"http_challenge_key_auth"` - HTTPChallengeURL *string `db:"http_challenge_url"` - HTTPOrderURL *string `db:"http_order_url"` - SSLCertificate *tls.Certificate `db:"-"` // Parsed certificate - SSLCertificatePEM []byte `db:"-"` // Decrypted PEM - EncryptedSSLCertificate []byte `db:"encrypted_ssl_certificate"` - SSLPrivateKeyPEM []byte `db:"-"` // Decrypted PEM - EncryptedSSLPrivateKey []byte `db:"encrypted_ssl_private_key"` - SSLCertificateChain *string `db:"ssl_certificate_chain"` - SSLStatus *CustomDomainSSLStatus `db:"ssl_status"` - SSLExpiresAt *time.Time `db:"ssl_expires_at"` - IsActive bool `db:"is_active"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + ID gid.GID `db:"id"` + OrganizationID gid.GID `db:"organization_id"` + Domain string `db:"domain"` + HTTPChallengeToken *string `db:"http_challenge_token"` + HTTPChallengeKeyAuth *string `db:"http_challenge_key_auth"` + HTTPChallengeURL *string `db:"http_challenge_url"` + HTTPOrderURL *string `db:"http_order_url"` + SSLCertificate *tls.Certificate `db:"-"` // Parsed certificate + SSLCertificatePEM []byte `db:"-"` // Decrypted PEM + EncryptedSSLCertificate []byte `db:"encrypted_ssl_certificate"` + SSLPrivateKeyPEM []byte `db:"-"` // Decrypted PEM + EncryptedSSLPrivateKey []byte `db:"encrypted_ssl_private_key"` + SSLCertificateChain *string `db:"ssl_certificate_chain"` + SSLStatus CustomDomainSSLStatus `db:"ssl_status"` + SSLExpiresAt *time.Time `db:"ssl_expires_at"` + IsActive bool `db:"is_active"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } CustomDomains []*CustomDomain @@ -762,10 +762,10 @@ WHERE q = fmt.Sprintf(q, scope.SQLFragment()) args := pgx.NamedArgs{ - "statuses": []CustomDomainSSLStatus{ - CustomDomainSSLStatusPending, - CustomDomainSSLStatusProvisioning, - CustomDomainSSLStatusRenewing, + "statuses": []string{ + string(CustomDomainSSLStatusPending), + string(CustomDomainSSLStatusProvisioning), + string(CustomDomainSSLStatusRenewing), }, } maps.Copy(args, scope.SQLArguments())