goalresolvers/goal_resolver.go (146 lines of code) (raw):

package goalresolvers import ( "context" "crypto/x509" "crypto/x509/pkix" "time" "github.com/Azure/webhook-tls-manager/config" "github.com/Azure/webhook-tls-manager/consts" "github.com/Azure/webhook-tls-manager/toolkit/certificates" "github.com/Azure/webhook-tls-manager/toolkit/certificates/certcreator" "github.com/Azure/webhook-tls-manager/toolkit/certificates/certgenerator" "github.com/Azure/webhook-tls-manager/toolkit/certificates/certoperator" "github.com/Azure/webhook-tls-manager/toolkit/log" "github.com/Azure/webhook-tls-manager/utils" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) type CertificateData struct { CaCertPem []byte CaKeyPem []byte ServerCertPem []byte ServerKeyPem []byte } type WebhookTlsManagerGoal struct { CertData *CertificateData IsKubeSystemNamespaceBlocked bool IsWebhookTlsManagerEnabled bool } type webhookTlsManagerGoalResolver struct { certOperator certoperator.CertOperator kubeClient kubernetes.Interface isKubeSystemNamespaceBlocked bool IsWebhookTlsManagerEnabled bool } func (g *webhookTlsManagerGoalResolver) shouldRotateCert(ctx context.Context) (bool, *error) { logger := log.MustGetLogger(ctx) logger.Infof(ctx, "config is %v", config.AppConfig) secret, getErr := g.kubeClient.CoreV1().Secrets(config.AppConfig.Namespace).Get(ctx, utils.SecretName(), metav1.GetOptions{}) if k8serrors.IsNotFound(getErr) { logger.Infof(ctx, "secret %s not exists", utils.SecretName()) return true, nil } if getErr != nil { logger.Errorf(ctx, "get secret %s failed. error: %s", utils.SecretName(), getErr) return false, &getErr } logger.Infof(ctx, "secret %s exists", utils.SecretName()) if v, exist := secret.ObjectMeta.Labels[consts.ManagedLabelKey]; exist && v == consts.ManagedLabelValue { logger.Infof(ctx, "found secret %s managed by aks. checking expiration date.", utils.SecretName()) expired, err := certificates.IsPEMCertificateExpired(ctx, string(secret.Data["serverCert.pem"]), utils.SecretName(), time.Now().AddDate(0, 1, 0)) if err != nil { logger.Errorf(ctx, "failed to check cert %s. error: %s", utils.SecretName(), err) return false, &err } if expired { logger.Infof(ctx, "cert expired.") return true, nil } logger.Infof(ctx, "cert valid.") return false, nil } logger.Warningf(ctx, "found secret %s is not managed by AKS.", utils.SecretName()) return false, nil } func (g *webhookTlsManagerGoalResolver) generateCertificates(ctx context.Context) (*CertificateData, *error) { logger := log.MustGetLogger(ctx) now := time.Now().UTC() notBefore := now.Add(-certificates.ClockSkewDuration) notAfter := now.AddDate(config.AppConfig.CaValidityYears, 0, 0) caCsr := &x509.Certificate{ Subject: pkix.Name{CommonName: utils.CACertificateCommonName()}, NotBefore: notBefore, NotAfter: notAfter, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign, IsCA: true, DNSNames: []string{utils.CACertificateCommonName()}, } caCert, caCertPem, caKey, caKeyPem, rerr := g.certOperator.CreateSelfSignedCertificateKeyPair(ctx, caCsr) if rerr != nil { logger.Errorf(ctx, "generateCertificates generate ca certs and key failed: %s", rerr.Error()) return &CertificateData{}, &rerr.RawError } notAfter = now.AddDate(config.AppConfig.ServerValidityYears, 0, 0) serverCsr := &x509.Certificate{ Subject: pkix.Name{CommonName: utils.ServerCertificateCommonName()}, Issuer: pkix.Name{CommonName: utils.CACertificateCommonName()}, NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, IsCA: false, DNSNames: []string{utils.ServerCertificateCommonName()}, } serverCertPem, serverKeyPem, rerr := g.certOperator.CreateCertificateKeyPair(ctx, serverCsr, caCert, caKey) if rerr != nil { logger.Errorf(ctx, "generateCertificates generate server certs and key failed: %s", rerr.Error()) return &CertificateData{}, &rerr.RawError } logger.Info(ctx, "new cert generated") return &CertificateData{ CaCertPem: []byte(caCertPem), CaKeyPem: []byte(caKeyPem), ServerCertPem: []byte(serverCertPem), ServerKeyPem: []byte(serverKeyPem), }, nil } func NewWebhookTlsManagerGoalResolver(ctx context.Context, kubeClient kubernetes.Interface, isKubeSystemNamespaceBlocked bool, IsWebhookTlsManagerEnabled bool) WebhookTlsManagerGoalResolverInterface { logger := log.MustGetLogger(ctx) logger.Infof(ctx, "NewWebhookTlsManagerGoalResolver: isKubeSystemNamespaceBlocked=%v, IsWebhookTlsManagerEnabled=%v", isKubeSystemNamespaceBlocked, IsWebhookTlsManagerEnabled) generator := certgenerator.NewCertGenerator(certcreator.NewCertCreator()) operator := certoperator.NewCertOperator(generator) return &webhookTlsManagerGoalResolver{ certOperator: operator, kubeClient: kubeClient, isKubeSystemNamespaceBlocked: isKubeSystemNamespaceBlocked, IsWebhookTlsManagerEnabled: IsWebhookTlsManagerEnabled, } } func (g *webhookTlsManagerGoalResolver) Resolve(ctx context.Context) (*WebhookTlsManagerGoal, *error) { logger := log.MustGetLogger(ctx) logger.Infof(ctx, "Resolve: isKubeSystemNamespaceBlocked=%v, IsWebhookTlsManagerEnabled=%v", g.isKubeSystemNamespaceBlocked, g.IsWebhookTlsManagerEnabled) goal := &WebhookTlsManagerGoal{ IsKubeSystemNamespaceBlocked: g.isKubeSystemNamespaceBlocked, IsWebhookTlsManagerEnabled: g.IsWebhookTlsManagerEnabled, } rotateCert, cerr := g.shouldRotateCert(ctx) if cerr != nil { logger.Errorf(ctx, "Failed to check cert expiration date. error: %s", *cerr) return nil, cerr } if !rotateCert { logger.Info(ctx, "no need to rotate cert.") goal.CertData = nil } else { data, cerr := g.generateCertificates(ctx) if cerr != nil { logger.Errorf(ctx, "generateCertificates. error: %s", *cerr) return nil, cerr } goal.CertData = data } return goal, nil }