diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 6a213d7..a1eceec 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -1,4 +1,4 @@ -// internal/api/api.go +// internal/pkg/api/api.go package api import ( diff --git a/internal/pkg/cert/persist.go b/internal/pkg/cert/persist.go index 577108a..1c9ccb6 100644 --- a/internal/pkg/cert/persist.go +++ b/internal/pkg/cert/persist.go @@ -24,7 +24,8 @@ AccountURL: cert.AccountURL, IssuerType: issuertype, SecretName: secretname, - EnableRotation: false, + EnableRotation: cert.EnableRotation, + RenewBefore: cert.RenewBefore, } if err := store.SaveCertificate(ctx, certStorage); err != nil { diff --git a/internal/pkg/rotation/rotator.go b/internal/pkg/rotation/rotator.go index e4abbc2..5ce1592 100644 --- a/internal/pkg/rotation/rotator.go +++ b/internal/pkg/rotation/rotator.go @@ -2,7 +2,19 @@ import ( "context" + internallog "envoy-control-plane/internal/log" + "envoy-control-plane/internal/pkg/api" + "envoy-control-plane/internal/pkg/cert" + certapi "envoy-control-plane/internal/pkg/cert/api" + "envoy-control-plane/internal/pkg/cert/tool" + "envoy-control-plane/internal/pkg/storage" "fmt" + + "time" +) + +const ( + defaultRenewBefore = 7 * 24 * time.Hour ) func NewCertRotor(ctx context.Context) (*CertRotator, error) { @@ -11,17 +23,100 @@ } type CertRotator struct { - IntervalHours int - RotateCertificatesFunc func(ctx context.Context) error + checkInterval time.Duration + storage storage.Storage + certIssuer certapi.CertIssuer + certPaser tool.CertificateParser +} + +func NewCertRotator(interval time.Duration, s storage.Storage, ci certapi.CertIssuer) CertRotator { + return CertRotator{ + checkInterval: interval, + storage: s, + certIssuer: ci, + certPaser: tool.CertificateParser{}, + } +} + +// loadCertificatesWithAutoRotationEnrolled retrieves all certificates marked for rotation. +// The method name has been corrected for clarity and idiomatic Go naming conventions (shorter). +func (cr *CertRotator) loadCertificatesWithAutoRotationEnrolled(ctx context.Context) ([]*storage.CertStorage, error) { + // Use the logger from context for better tracing + log := internallog.LogFromContext(ctx) + + certs, err := cr.storage.LoadAllCertificates(ctx) + if err != nil { + log.Errorf("Failed to load all certificates from storage: %w", err) + return nil, fmt.Errorf("failed to load certificates: %w", err) + } + + res := make([]*storage.CertStorage, 0, len(certs)) // Pre-allocate capacity + for _, cert := range certs { + if cert.EnableRotation { + res = append(res, cert) + } + } + log.Debugf("Loaded %d certificates enrolled for rotation.", len(res)) + return res, nil } func (cr *CertRotator) RotateCertificates(ctx context.Context) error { - // Implementation for rotating certificates - fmt.Println("Rotating certificates...") - return nil -} + log := internallog.LogFromContext(ctx) -func (cr *CertRotator) Start(ctx context.Context) { - // Implementation for starting the rotation process - fmt.Println("Starting certificate rotation process...") + // Implementation for rotating certificates + ticker := time.NewTicker(cr.checkInterval) + defer ticker.Stop() + + // 3. Start the goroutine loop. + go func() { + log.Infof("[Rotator] Starting certificate rotation watcher. Interval: %v\n", cr.checkInterval) + for { + select { + case <-ctx.Done(): + // Context was canceled (e.g., application shutdown). Exit the loop gracefully. + log.Infof("[Rotator] Context cancelled. Shutting down rotation watcher.") + return + case <-ticker.C: + // The ticker fired. Time to check and potentially rotate the certificate. + certs, err := cr.loadCertificatesWithAutoRotationEnrolled(ctx) + if err != nil { + // Log the error but continue the loop, as this is a transient failure. + log.Errorf("[Rotator] failed to load certificates with auto ratation enabled: %v\n", err) + } + + for _, c := range certs { + certInfo, err := cr.certPaser.Parse(c.CertPEM) + if err != nil { + log.Errorf("failed to parse the cert %s: %w", string(c.CertPEM), err) + continue + } + renewBefore := c.RenewBefore + if renewBefore == 0 { + renewBefore = defaultRenewBefore + } + + if time.Now().Add(c.RenewBefore).After(certInfo[0].NotBefore) { + oldCert := &certapi.Certificate{ + Domain: c.Domain, + CertPEM: c.CertPEM, + KeyPEM: c.KeyPEM, + AccountKey: c.AccountKey, + AccountURL: c.AccountURL, + } + newCert, err := cr.certIssuer.RenewCertificate(oldCert, api.ACME_CALLENGE_WEB_PATH, c.Email) + if err != nil { + log.Errorf("failed to renew the certificate: %w", err) + continue + } + if err := cert.SaveCertificateData(ctx, &cr.storage, newCert, c.Email, c.IssuerType, c.SecretName); err != nil { + log.Errorf("failed to save the new certificate into the database") + continue + } + + } + } + } + } + }() + return nil }