pkg/webhook/cert.go (116 lines of code) (raw):

package webhook import ( "context" "fmt" "time" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" certresources "knative.dev/pkg/webhook/certificates/resources" ctrlClient "sigs.k8s.io/controller-runtime/pkg/client" ) const ( // secretCertsName is name of secret where ca.crt, tls.crt, tls.key will be stored after generation. secretCertsName = "edp-codebase-operator-webhook-certs" // secretTLSKey is the name of the key associated with the secret's private key. secretTLSKey = "tls.key" // secretCACert is the name of the key associated with the certificate of the CA for the keypair. secretCACert = "ca.crt" // secretTLSCert is the name of the key associated with the secret's certificate. secretTLSCert = "tls.crt" century = 100 * 365 * 24 * time.Hour // serviceName is the name of the service used to serve the webhook. serviceName = "edp-codebase-operator-webhook-service" // validatingWebHookName is the name of the ValidatingWebhookConfiguration resource used for webhook configuration. validatingWebHookName = "edp-codebase-operator-validating-webhook-configuration" ) // CertData is a struct that contains certificates data. type CertData struct { ServerKey []byte ServerCert []byte CaCert []byte } // NewCertData creates a new CertData struct. func NewCertData(serverKey, serverCert, caCert []byte) *CertData { return &CertData{ServerKey: serverKey, ServerCert: serverCert, CaCert: caCert} } // CertService is a service that provides certificates for webhook. type CertService struct { clientReader ctrlClient.Reader clientWriter ctrlClient.Writer } // NewCertService creates a new CertService service. func NewCertService(clientReader ctrlClient.Reader, clientWriter ctrlClient.Writer) *CertService { return &CertService{ clientReader: clientReader, clientWriter: clientWriter, } } // PopulateCertificates populates certificates for webhook. func (s *CertService) PopulateCertificates(ctx context.Context, namespace string) error { cert, err := s.createCertsSecret(ctx, namespace, serviceName) if err != nil { return fmt.Errorf("failed to create certificates: %w", err) } return s.updateWebHookCABundle(ctx, getValidationWebHookName(namespace), cert.CaCert) } // createCertsSecret creates and returns a CertData with CA certificate, server certificate and key. // serverKey and serverCert are used by the server to establish trust for clients, CA certificate is used by the // client to verify the server authentication chain. Certificates are based on kubernetes service spec and namespace. // After generation all certificates are stored in secret: secretCertsName. func (s *CertService) createCertsSecret( ctx context.Context, namespace, serviceName string, ) (*CertData, error) { serKey, serCert, caCert, err := certresources.CreateCerts( ctx, serviceName, namespace, time.Now().Add(century), ) if err != nil { return nil, fmt.Errorf("failed to create certs: %w", err) } certData := NewCertData(serKey, serCert, caCert) secret := &corev1.Secret{} err = s.clientReader.Get(ctx, ctrlClient.ObjectKey{Namespace: namespace, Name: secretCertsName}, secret) if err != nil { if k8serrors.IsNotFound(err) { secret.ObjectMeta = metav1.ObjectMeta{ Namespace: namespace, Name: secretCertsName, } secret.Data = map[string][]byte{ secretTLSKey: serKey, secretTLSCert: serCert, secretCACert: caCert, } secret.Type = corev1.SecretTypeOpaque if err = s.clientWriter.Create(ctx, secret); err != nil { return nil, fmt.Errorf("failed to create secret: %w", err) } return certData, nil } return nil, fmt.Errorf("failed to get secret: %w", err) } secret.Data = map[string][]byte{ secretTLSKey: serKey, secretTLSCert: serCert, secretCACert: caCert, } if err = s.clientWriter.Update(ctx, secret); err != nil { return nil, fmt.Errorf("failed to update secret: %w", err) } return certData, nil } // updateWebHookCABundle updates ValidatingWebhookConfiguration CaBundle spec with CA certificate. func (s *CertService) updateWebHookCABundle( ctx context.Context, webHookName string, caBundle []byte, ) error { webHook := &admissionregistrationv1.ValidatingWebhookConfiguration{} err := s.clientReader.Get(ctx, ctrlClient.ObjectKey{Name: webHookName}, webHook) if err != nil { return fmt.Errorf("failed to get validation webHook: %w", err) } if len(webHook.Webhooks) == 0 { return nil } for i := range webHook.Webhooks { webHook.Webhooks[i].ClientConfig.CABundle = caBundle } if err = s.clientWriter.Update(ctx, webHook); err != nil { return fmt.Errorf("failed to update validation webHook caBundle: %w", err) } return nil } // getValidationWebHookName returns name of ValidatingWebhookConfiguration resource. func getValidationWebHookName(namespace string) string { return fmt.Sprintf("%s-%s", validatingWebHookName, namespace) }