diff --git a/.golangci.yml b/.golangci.yml index 8296de4c..8a466145 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -94,3 +94,5 @@ linters-settings: desc: not allowed - pkg: "github.com/pkg/errors" desc: Should be replaced by standard lib errors package + varnamelen: + ignore-map-index-ok: true diff --git a/controllers/policyserver_controller_test.go b/controllers/policyserver_controller_test.go index a9d3858a..f7387e1f 100644 --- a/controllers/policyserver_controller_test.go +++ b/controllers/policyserver_controller_test.go @@ -44,7 +44,6 @@ var _ = Describe("PolicyServer controller", func() { }) When("deleting a PolicyServer", func() { - BeforeEach(func() { createPolicyServerAndWaitForItsService(policyServerFactory(policyServerName)) }) @@ -135,7 +134,6 @@ var _ = Describe("PolicyServer controller", func() { return err }, timeout, pollInterval).Should(notFound()) }) - }) Context("with assigned policies", func() { @@ -201,7 +199,6 @@ var _ = Describe("PolicyServer controller", func() { HaveField("Finalizers", Not(ContainElement(constants.KubewardenFinalizerPre114))), HaveField("Finalizers", ContainElement(IntegrationTestsFinalizer)), )) - }) It("should not delete its managed resources until all the scheduled policies are gone", func() { @@ -248,15 +245,11 @@ var _ = Describe("PolicyServer controller", func() { }, timeout, pollInterval).ShouldNot( HaveField("Finalizers", ContainElement(constants.KubewardenFinalizer)), ) - }) - }) - }) When("creating a PolicyServer", func() { - It("should use the policy server affinity configuration in the policy server deployment", func() { policyServer := policyServerFactory(policyServerName) policyServer.Spec.Affinity = corev1.Affinity{ @@ -316,7 +309,8 @@ var _ = Describe("PolicyServer controller", func() { "RunAsGroup": BeNil(), "ProcMount": BeNil(), "SeccompProfile": BeNil(), - }))}))) + })), + }))) By("checking the deployment pod security context") Expect(deployment.Spec.Template.Spec.SecurityContext).To(PointTo(MatchFields(IgnoreExtras, Fields{ "SELinuxOptions": BeNil(), @@ -328,7 +322,8 @@ var _ = Describe("PolicyServer controller", func() { "FSGroup": BeNil(), "Sysctls": BeNil(), "FSGroupChangePolicy": BeNil(), - "SeccompProfile": BeNil()}))) + "SeccompProfile": BeNil(), + }))) By("checking the deployment affinity") Expect(deployment.Spec.Template.Spec.Affinity).To(BeNil()) @@ -366,7 +361,8 @@ var _ = Describe("PolicyServer controller", func() { "RunAsGroup": BeNil(), "ProcMount": BeNil(), "SeccompProfile": BeNil(), - }))}))) + })), + }))) Expect(deployment.Spec.Template.Spec.SecurityContext).To(PointTo(MatchFields(IgnoreExtras, Fields{ "SELinuxOptions": BeNil(), "WindowsOptions": BeNil(), @@ -377,7 +373,8 @@ var _ = Describe("PolicyServer controller", func() { "FSGroup": BeNil(), "Sysctls": BeNil(), "FSGroupChangePolicy": BeNil(), - "SeccompProfile": BeNil()}))) + "SeccompProfile": BeNil(), + }))) }) It("should create the policy server configmap empty if no policies are assigned ", func() { @@ -465,7 +462,6 @@ var _ = Describe("PolicyServer controller", func() { }) It("should create PodDisruptionBudget when policy server has MinAvailable configuration set", func() { - policyServer := policyServerFactory(policyServerName) minAvailable := intstr.FromInt(2) policyServer.Spec.MinAvailable = &minAvailable @@ -603,9 +599,24 @@ var _ = Describe("PolicyServer controller", func() { }).Should(Succeed()) }) - It("should create secret with owner reference", func() { + It("should create the policy server secrets", func() { policyServer := policyServerFactory(policyServerName) createPolicyServerAndWaitForItsService(policyServer) + + Eventually(func() error { + secret, err := getTestPolicyServerCASecret() + if err != nil { + return err + } + + By("creating a secret containing the CA certificate and key") + Expect(secret.Data).To(HaveKey(constants.PolicyServerCARootCACert)) + Expect(secret.Data).To(HaveKey(constants.PolicyServerCARootPemName)) + Expect(secret.Data).To(HaveKey(constants.PolicyServerCARootPrivateKeyCertName)) + + return nil + }).Should(Succeed()) + Eventually(func() error { secret, err := getTestPolicyServerSecret(policyServerName) if err != nil { @@ -615,6 +626,12 @@ var _ = Describe("PolicyServer controller", func() { if err != nil { return err } + + By("creating a secret containing the TLS certificate and key") + Expect(secret.Data).To(HaveKey(constants.PolicyServerTLSCert)) + Expect(secret.Data).To(HaveKey(constants.PolicyServerTLSKey)) + + By("setting the secret owner reference") Expect(secret.OwnerReferences).To(ContainElement( MatchFields(IgnoreExtras, Fields{ "UID": Equal(policyServer.GetUID()), @@ -690,9 +707,7 @@ var _ = Describe("PolicyServer controller", func() { } return nil }, timeout, pollInterval).Should(Succeed()) - }) - }) When("updating the PolicyServer", func() { @@ -968,6 +983,5 @@ var _ = Describe("PolicyServer controller", func() { ), ) }) - }) }) diff --git a/controllers/utils_test.go b/controllers/utils_test.go index e0d6ea5d..f61f2bb2 100644 --- a/controllers/utils_test.go +++ b/controllers/utils_test.go @@ -164,6 +164,14 @@ func getTestPolicyServerService(policyServerName string) (*corev1.Service, error return &service, nil } +func getTestPolicyServerCASecret() (*corev1.Secret, error) { + secret := corev1.Secret{} + if err := reconciler.APIReader.Get(ctx, client.ObjectKey{Name: constants.PolicyServerCARootSecretName, Namespace: DeploymentsNamespace}, &secret); err != nil { + return nil, errors.Join(errors.New("could not find the PolicyServer CA secret"), err) + } + return &secret, nil +} + func getTestPolicyServerSecret(policyServerName string) (*corev1.Secret, error) { secretName := getPolicyServerNameWithPrefix(policyServerName) secret := corev1.Secret{} diff --git a/internal/pkg/admission/policy-server-ca-secret.go b/internal/pkg/admission/policy-server-ca-secret.go index c9ebae01..934b2d7c 100644 --- a/internal/pkg/admission/policy-server-ca-secret.go +++ b/internal/pkg/admission/policy-server-ca-secret.go @@ -12,21 +12,18 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/kubewarden/kubewarden-controller/internal/pkg/admissionregistration" + "github.com/kubewarden/kubewarden-controller/internal/pkg/certs" "github.com/kubewarden/kubewarden-controller/internal/pkg/constants" ) -type generateCAFunc = func() (*admissionregistration.CA, error) -type pemEncodeCertificateFunc = func(certificate []byte) ([]byte, error) -type generateCertFunc = func(ca []byte, commonName string, extraSANs []string, CAPrivateKey *rsa.PrivateKey) ([]byte, []byte, error) - -func (r *Reconciler) fetchOrInitializePolicyServerCASecret(ctx context.Context, policyServer *policiesv1.PolicyServer, caSecret *corev1.Secret, generateCert generateCertFunc) error { +func (r *Reconciler) fetchOrInitializePolicyServerCASecret(ctx context.Context, policyServer *policiesv1.PolicyServer, caSecret *corev1.Secret) error { policyServerSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: r.DeploymentsNamespace, Name: policyServer.NameWithPrefix(), }, } + _, err := controllerutil.CreateOrPatch(ctx, r.Client, policyServerSecret, func() error { if err := controllerutil.SetOwnerReference(policyServer, policyServerSecret, r.Client.Scheme()); err != nil { return errors.Join(errors.New("failed to set policy server secret owner reference"), err) @@ -36,22 +33,34 @@ func (r *Reconciler) fetchOrInitializePolicyServerCASecret(ctx context.Context, _, hasTLSCert := policyServerSecret.Data[constants.PolicyServerTLSCert] _, hasTLSKey := policyServerSecret.Data[constants.PolicyServerTLSKey] if !hasTLSCert || !hasTLSKey { - admissionregCA, err := extractCaFromSecret(caSecret) + caCert, caPrivateKey, err := extractCAFromSecret(caSecret) if err != nil { return err } - servingCert, servingKey, err := generateCert( - admissionregCA.CaCert, + + cert, privateKey, err := certs.GenerateCert( + caCert, fmt.Sprintf("%s.%s.svc", policyServer.NameWithPrefix(), r.DeploymentsNamespace), []string{fmt.Sprintf("%s.%s.svc", policyServer.NameWithPrefix(), r.DeploymentsNamespace)}, - admissionregCA.CaPrivateKey) + caPrivateKey) if err != nil { return fmt.Errorf("cannot generate policy-server %s certificate: %w", policyServer.NameWithPrefix(), err) } + + certPEM, err := certs.PEMEncodeCertificate(cert) + if err != nil { + return fmt.Errorf("cannot PEM encode policy-server %s certificate: %w", policyServer.NameWithPrefix(), err) + } + + privateKeyPEM, err := certs.PEMEncodePrivateKey(privateKey) + if err != nil { + return fmt.Errorf("cannot PEM encode policy-server %s private key: %w", policyServer.NameWithPrefix(), err) + } + policyServerSecret.Type = corev1.SecretTypeOpaque policyServerSecret.StringData = map[string]string{ - constants.PolicyServerTLSCert: string(servingCert), - constants.PolicyServerTLSKey: string(servingKey), + constants.PolicyServerTLSCert: string(certPEM), + constants.PolicyServerTLSKey: string(privateKeyPEM), } } @@ -65,6 +74,7 @@ func (r *Reconciler) fetchOrInitializePolicyServerCASecret(ctx context.Context, ) return errors.Join(errors.New("cannot fetch or initialize Policy Server CA secret"), err) } + setTrueConditionType( &policyServer.Status.Conditions, string(policiesv1.PolicyServerCASecretReconciled), @@ -73,37 +83,40 @@ func (r *Reconciler) fetchOrInitializePolicyServerCASecret(ctx context.Context, return nil } -func extractCaFromSecret(caSecret *corev1.Secret) (*admissionregistration.CA, error) { +func extractCAFromSecret(caSecret *corev1.Secret) ([]byte, *rsa.PrivateKey, error) { caCert, ok := caSecret.Data[constants.PolicyServerCARootCACert] if !ok { - return nil, fmt.Errorf("CA could not be extracted from secret %s", caSecret.Kind) + return nil, nil, fmt.Errorf("CA could not be extracted from secret %s", caSecret.Kind) } - caPrivateKeyBytes, ok := caSecret.Data[constants.PolicyServerCARootPrivateKeyCertName] + + privateKeyBytes, ok := caSecret.Data[constants.PolicyServerCARootPrivateKeyCertName] if !ok { - return nil, fmt.Errorf("CA private key bytes could not be extracted from secret %s", caSecret.Kind) + return nil, nil, fmt.Errorf("CA private key bytes could not be extracted from secret %s", caSecret.Kind) } - caPrivateKey, err := x509.ParsePKCS1PrivateKey(caPrivateKeyBytes) + privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBytes) if err != nil { - return nil, fmt.Errorf("CA private key could not be extracted from secret %s", caSecret.Kind) + return nil, nil, fmt.Errorf("CA private key could not be extracted from secret %s", caSecret.Kind) } - return &admissionregistration.CA{CaCert: caCert, CaPrivateKey: caPrivateKey}, nil + + return caCert, privateKey, nil } -func (r *Reconciler) fetchOrInitializePolicyServerCARootSecret(ctx context.Context, policyServer *policiesv1.PolicyServer, generateCA generateCAFunc, pemEncodeCertificate pemEncodeCertificateFunc) (*corev1.Secret, error) { +func (r *Reconciler) fetchOrInitializePolicyServerCARootSecret(ctx context.Context, policyServer *policiesv1.PolicyServer) (*corev1.Secret, error) { policyServerSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: r.DeploymentsNamespace, Name: constants.PolicyServerCARootSecretName, }, } + _, err := controllerutil.CreateOrPatch(ctx, r.Client, policyServerSecret, func() error { _, hasCARootCert := policyServerSecret.Data[constants.PolicyServerCARootCACert] _, hasCARootPem := policyServerSecret.Data[constants.PolicyServerCARootPemName] _, hasCARootPrivateKey := policyServerSecret.Data[constants.PolicyServerCARootPrivateKeyCertName] if !hasCARootCert || !hasCARootPem || !hasCARootPrivateKey { - return updateSecretCA(policyServerSecret, generateCA, pemEncodeCertificate) + return updateCASecret(policyServerSecret) } return nil }) @@ -115,29 +128,34 @@ func (r *Reconciler) fetchOrInitializePolicyServerCARootSecret(ctx context.Conte ) return nil, errors.Join(errors.New("cannot fetch or initialize Policy Server CA secret"), err) } + setTrueConditionType( &policyServer.Status.Conditions, string(policiesv1.PolicyServerCARootSecretReconciled), ) + return policyServerSecret, nil } -func updateSecretCA(policyServerSecret *corev1.Secret, generateCA generateCAFunc, pemEncodeCertificate pemEncodeCertificateFunc) error { - caRoot, err := generateCA() +func updateCASecret(policyServerSecret *corev1.Secret) error { + caCert, privateKey, err := certs.GenerateCA() if err != nil { return fmt.Errorf("cannot generate policy-server secret CA: %w", err) } - caPEMEncoded, err := pemEncodeCertificate(caRoot.CaCert) + + caCertPEM, err := certs.PEMEncodeCertificate(caCert) if err != nil { - return fmt.Errorf("cannot encode policy-server secret CA: %w", err) + return fmt.Errorf("cannot PEM encode policy-server secret CA certificate: %w", err) } - caPrivateKeyBytes := x509.MarshalPKCS1PrivateKey(caRoot.CaPrivateKey) + + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) secretContents := map[string][]byte{ - constants.PolicyServerCARootCACert: caRoot.CaCert, - constants.PolicyServerCARootPemName: caPEMEncoded, - constants.PolicyServerCARootPrivateKeyCertName: caPrivateKeyBytes, + constants.PolicyServerCARootCACert: caCert, + constants.PolicyServerCARootPemName: caCertPEM, + constants.PolicyServerCARootPrivateKeyCertName: privateKeyBytes, } policyServerSecret.Type = corev1.SecretTypeOpaque policyServerSecret.Data = secretContents + return nil } diff --git a/internal/pkg/admission/policy-server-ca-secret_test.go b/internal/pkg/admission/policy-server-ca-secret_test.go deleted file mode 100644 index 093fadb4..00000000 --- a/internal/pkg/admission/policy-server-ca-secret_test.go +++ /dev/null @@ -1,267 +0,0 @@ -package admission - -import ( - "bytes" - "context" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "errors" - "net/http" - "sync" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/kubewarden/kubewarden-controller/internal/pkg/admissionregistration" - "github.com/kubewarden/kubewarden-controller/internal/pkg/constants" - policiesv1 "github.com/kubewarden/kubewarden-controller/pkg/apis/policies/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const port = "8181" - -func TestFetchOrInitializePolicyServerCARootSecret(t *testing.T) { - caPemBytes := []byte{} - admissionregCA, err := admissionregistration.GenerateCA() - generateCACalled := false - - //nolint: wrapcheck - generateCAFunc := func() (*admissionregistration.CA, error) { - generateCACalled = true - return admissionregCA, err - } - - pemEncodeCertificateFunc := func(certificate []byte) ([]byte, error) { - if !bytes.Equal(certificate, admissionregCA.CaCert) { - return nil, errors.New("certificate received should be the one returned by generateCA") - } - return caPemBytes, nil - } - - caSecretContents := map[string][]byte{ - constants.PolicyServerCARootCACert: admissionregCA.CaCert, - constants.PolicyServerCARootPemName: caPemBytes, - constants.PolicyServerCARootPrivateKeyCertName: x509.MarshalPKCS1PrivateKey(admissionregCA.CaPrivateKey), - } - - var tests = []struct { - name string - r Reconciler - err error - secretContents map[string][]byte - generateCACalled bool - }{ - {"Existing CA", createReconcilerWithExistingCA(), nil, mockRootCASecretContents, false}, - {"CA does not exist", newReconciler(nil, false, false), nil, caSecretContents, true}, - } - - for _, ttest := range tests { - t.Run(ttest.name, func(t *testing.T) { - policyServer := &policiesv1.PolicyServer{} - secret, err := ttest.r.fetchOrInitializePolicyServerCARootSecret(context.Background(), policyServer, generateCAFunc, pemEncodeCertificateFunc) - if diff := cmp.Diff(secret.Data, ttest.secretContents); diff != "" { - t.Errorf("got an unexpected secret, diff %s", diff) - } - - if !errors.Is(err, ttest.err) { - t.Errorf("got %s, want %s", err, ttest.err) - } - - if generateCACalled != ttest.generateCACalled { - t.Errorf("got %t, want %t", generateCACalled, ttest.generateCACalled) - } - generateCACalled = false - }) - } -} - -func TestFetchOrInitializePolicyServerSecret(t *testing.T) { - generateCertCalled := false - servingCert := []byte{1} - servingKey := []byte{2} - admissionregCA, _ := admissionregistration.GenerateCA() - caSecret := &corev1.Secret{Data: map[string][]byte{constants.PolicyServerCARootCACert: admissionregCA.CaCert, constants.PolicyServerCARootPrivateKeyCertName: x509.MarshalPKCS1PrivateKey(admissionregCA.CaPrivateKey)}} - - //nolint:unparam,revive - generateCertFunc := func(_ca []byte, _commonName string, _extraSANs []string, _CAPrivateKey *rsa.PrivateKey) ([]byte, []byte, error) { - generateCertCalled = true - return servingCert, servingKey, nil - } - - caSecretContents := map[string]string{ - constants.PolicyServerTLSCert: string(servingCert), - constants.PolicyServerTLSKey: string(servingKey), - } - - var tests = []struct { - name string - r Reconciler - err error - secretContents map[string]string - generateCertCalled bool - }{ - {"Existing cert", createReconcilerWithExistingCert(), nil, mockSecretCert, false}, - {"cert does not exist", newReconciler(nil, false, false), nil, caSecretContents, true}, - } - - for _, ttest := range tests { - t.Run(ttest.name, func(t *testing.T) { - policyServer := &policiesv1.PolicyServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "policyServer", - }, - } - err := ttest.r.fetchOrInitializePolicyServerCASecret(context.Background(), policyServer, caSecret, generateCertFunc) - - secret := &corev1.Secret{} - _ = ttest.r.Client.Get(context.Background(), client.ObjectKey{Namespace: namespace, Name: policyServer.NameWithPrefix()}, secret) - - if diff := cmp.Diff(secret.StringData, ttest.secretContents); diff != "" { - t.Errorf("got an unexpected secret, diff %s", diff) - } - - if !errors.Is(err, ttest.err) { - t.Errorf("got %s, want %s", err, ttest.err) - } - - if generateCertCalled != ttest.generateCertCalled { - t.Errorf("got %t, want %t", generateCertCalled, ttest.generateCertCalled) - } - generateCertCalled = false - }) - } -} - -func TestCAAndCertificateCreationInAHttpsServer(t *testing.T) { - const domain = "localhost" - const maxRetries = 10 - caSecret := &corev1.Secret{} - // create CA - err := updateSecretCA(caSecret, admissionregistration.GenerateCA, admissionregistration.PemEncodeCertificate) - if err != nil { - t.Errorf("CA secret could not be created: %s", err.Error()) - } - admissionregCA, err := extractCaFromSecret(caSecret) - if err != nil { - t.Errorf("CA could not be extracted from secret: %s", err.Error()) - } - // create cert using CA previously created - servingCert, servingKey, err := admissionregistration.GenerateCert( - admissionregCA.CaCert, - domain, - []string{domain}, - admissionregCA.CaPrivateKey) - if err != nil { - t.Errorf("failed generating cert: %s", err.Error()) - } - - var server http.Server - var waitGroup sync.WaitGroup - waitGroup.Add(1) - - // create https server with the certificates created - go func() { - cert, err := tls.X509KeyPair(servingCert, servingKey) - if err != nil { - t.Errorf("could not load cert: %s", err.Error()) - } - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - MinVersion: tls.VersionTLS12, - } - server = http.Server{ - Addr: ":" + port, - TLSConfig: tlsConfig, - ReadHeaderTimeout: time.Second, - } - waitGroup.Done() - _ = server.ListenAndServeTLS("", "") - }() - - // wait for https server to be ready to avoid race conditions - waitGroup.Wait() - rootCAs := x509.NewCertPool() - rootCAs.AppendCertsFromPEM(caSecret.Data[constants.PolicyServerCARootPemName]) - retries := 0 - var conn *tls.Conn - for retries < maxRetries { - // test ssl handshake using the ca pem - conn, err = tls.Dial("tcp", domain+":"+port, &tls.Config{RootCAs: rootCAs, MinVersion: tls.VersionTLS12}) - if err == nil || !isConnectionRefusedError(err) { - break - } - // wait 50 millisecond and retry to avoid race conditions as server might still not be ready - time.Sleep(50 * time.Millisecond) - retries++ - } - if err != nil { - t.Errorf("error when connecting to the https server : %s", err.Error()) - } - err = conn.Close() - if err != nil { - t.Errorf("error when closing connection : %s", err.Error()) - } - err = server.Shutdown(context.Background()) - if err != nil { - t.Errorf("error when shutting down https server : %s", err.Error()) - } -} - -func isConnectionRefusedError(err error) bool { - return err.Error() == "dial tcp [::1]:"+port+": connect: connection refused" -} - -const namespace = "namespace" - -var mockRootCASecretContents = map[string][]byte{ - constants.PolicyServerCARootCACert: []byte("caCert"), - constants.PolicyServerCARootPemName: []byte("caPem"), - constants.PolicyServerCARootPrivateKeyCertName: []byte("caPrivateKey"), -} - -var mockRootCASecretCert = map[string]string{ - constants.PolicyServerCARootCACert: "caCert", - constants.PolicyServerCARootPemName: "caPem", - constants.PolicyServerCARootPrivateKeyCertName: "caPrivateKey", -} - -func createReconcilerWithExistingCA() Reconciler { - mockSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: constants.PolicyServerCARootSecretName, - Namespace: namespace, - }, - Data: mockRootCASecretContents, - StringData: mockRootCASecretCert, - Type: corev1.SecretTypeOpaque, - } - - return newReconciler([]client.Object{mockSecret}, false, false) -} - -var mockSecretContents = map[string][]byte{ - constants.PolicyServerTLSCert: []byte("tlsCert"), - constants.PolicyServerTLSKey: []byte("tlsKey"), -} - -var mockSecretCert = map[string]string{ - constants.PolicyServerTLSCert: "tlsCert", - constants.PolicyServerTLSKey: "tlsKey", -} - -func createReconcilerWithExistingCert() Reconciler { - mockSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "policy-server-policyServer", - Namespace: namespace, - }, - Data: mockSecretContents, - StringData: mockSecretCert, - Type: corev1.SecretTypeOpaque, - } - - return newReconciler([]client.Object{mockSecret}, false, false) -} diff --git a/internal/pkg/admission/reconciler.go b/internal/pkg/admission/reconciler.go index 0d7cc48f..ac6bf0d1 100644 --- a/internal/pkg/admission/reconciler.go +++ b/internal/pkg/admission/reconciler.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/go-logr/logr" - "github.com/kubewarden/kubewarden-controller/internal/pkg/admissionregistration" "github.com/kubewarden/kubewarden-controller/internal/pkg/constants" policiesv1 "github.com/kubewarden/kubewarden-controller/pkg/apis/policies/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -57,12 +56,12 @@ func (r *Reconciler) Reconcile( policyServer *policiesv1.PolicyServer, policies []policiesv1.Policy, ) error { - policyServerCARootSecret, err := r.fetchOrInitializePolicyServerCARootSecret(ctx, policyServer, admissionregistration.GenerateCA, admissionregistration.PemEncodeCertificate) + policyServerCARootSecret, err := r.fetchOrInitializePolicyServerCARootSecret(ctx, policyServer) if err != nil { return err } - err = r.fetchOrInitializePolicyServerCASecret(ctx, policyServer, policyServerCARootSecret, admissionregistration.GenerateCert) + err = r.fetchOrInitializePolicyServerCASecret(ctx, policyServer, policyServerCARootSecret) if err != nil { return err } diff --git a/internal/pkg/admission/testing_common.go b/internal/pkg/admission/testing_common.go index d06b9b88..efd634cc 100644 --- a/internal/pkg/admission/testing_common.go +++ b/internal/pkg/admission/testing_common.go @@ -12,6 +12,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" ) +const namespace = "namespace" + func createReconciler() (Reconciler, policiesv1.ClusterAdmissionPolicy, policiesv1.ClusterAdmissionPolicy, policiesv1.ClusterAdmissionPolicy) { admissionPolicyName := "admissionPolicy" validationPolicy := policiesv1.ClusterAdmissionPolicy{ diff --git a/internal/pkg/admissionregistration/ca.go b/internal/pkg/admissionregistration/ca.go deleted file mode 100644 index 58c78377..00000000 --- a/internal/pkg/admissionregistration/ca.go +++ /dev/null @@ -1,54 +0,0 @@ -package admissionregistration - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "fmt" - "math/big" - "time" -) - -type CA struct { - CaCert []byte - CaPrivateKey *rsa.PrivateKey -} - -func GenerateCA() (*CA, error) { - privateKey, err := newPrivateKey(1024) - if err != nil { - return nil, fmt.Errorf("cannot create private key: %w", err) - } - serialNumber, err := rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil)) - if err != nil { - return nil, fmt.Errorf("cannot init serial number: %w", err) - } - caCertificate := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{""}, - Country: []string{""}, - Province: []string{""}, - Locality: []string{""}, - StreetAddress: []string{""}, - PostalCode: []string{""}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(10, 0, 0), - IsCA: true, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - BasicConstraintsValid: true, - } - caCertificateBytes, err := x509.CreateCertificate( - rand.Reader, - &caCertificate, - &caCertificate, - &privateKey.Key().PublicKey, - privateKey.Key()) - if err != nil { - return nil, fmt.Errorf("cannot create certificate: %w", err) - } - return &CA{caCertificateBytes, privateKey.Key()}, nil -} diff --git a/internal/pkg/admissionregistration/key.go b/internal/pkg/admissionregistration/key.go deleted file mode 100644 index 5f4bb9b6..00000000 --- a/internal/pkg/admissionregistration/key.go +++ /dev/null @@ -1,54 +0,0 @@ -package admissionregistration - -import ( - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" -) - -// KeyPair represents a public/private key pair -type KeyPair struct { - PublicKey string - PrivateKey string - key *rsa.PrivateKey -} - -// Key returns the RSA private key for this private key pair -func (keyPair *KeyPair) Key() *rsa.PrivateKey { - return keyPair.key -} - -func newPrivateKey(keyBitSize int) (*KeyPair, error) { - key, err := rsa.GenerateKey(rand.Reader, keyBitSize) - if err != nil { - return nil, fmt.Errorf("cannot create private key: %w", err) - } - publicKey, err := x509.MarshalPKIXPublicKey(&key.PublicKey) - if err != nil { - return nil, fmt.Errorf("cannot marshal public key: %w", err) - } - publicKeyPEM := new(bytes.Buffer) - err = pem.Encode(publicKeyPEM, &pem.Block{ - Type: "PUBLIC KEY", - Bytes: publicKey, - }) - if err != nil { - return nil, fmt.Errorf("cannot encode public key: %w", err) - } - privateKeyPEM := new(bytes.Buffer) - err = pem.Encode(privateKeyPEM, &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(key), - }) - if err != nil { - return nil, fmt.Errorf("cannot encode private key: %w", err) - } - return &KeyPair{ - PublicKey: publicKeyPEM.String(), - PrivateKey: privateKeyPEM.String(), - key: key, - }, nil -} diff --git a/internal/pkg/admissionregistration/cert.go b/internal/pkg/certs/certs.go similarity index 50% rename from internal/pkg/admissionregistration/cert.go rename to internal/pkg/certs/certs.go index 96984c0c..fdf216e0 100644 --- a/internal/pkg/admissionregistration/cert.go +++ b/internal/pkg/certs/certs.go @@ -1,4 +1,4 @@ -package admissionregistration +package certs import ( "bytes" @@ -13,11 +13,59 @@ import ( "time" ) +const bitSize = 4096 + +// GenerateCA generates a self-signed CA root certificate and private key +// The certificate is valid for 10 years. +func GenerateCA() ([]byte, *rsa.PrivateKey, error) { + serialNumber, err := rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil)) + if err != nil { + return nil, nil, fmt.Errorf("cannot init serial number: %w", err) + } + + privateKey, err := rsa.GenerateKey(rand.Reader, bitSize) + if err != nil { + return nil, nil, fmt.Errorf("cannot create private key: %w", err) + } + + caCert := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{""}, + Country: []string{""}, + Province: []string{""}, + Locality: []string{""}, + StreetAddress: []string{""}, + PostalCode: []string{""}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + caCertBytes, err := x509.CreateCertificate( + rand.Reader, + &caCert, + &caCert, + &privateKey.PublicKey, + privateKey) + if err != nil { + return nil, nil, fmt.Errorf("cannot create certificate: %w", err) + } + + return caCertBytes, privateKey, nil +} + +// GenerateCert generates a certificate and private key signed by the provided CA. +// The certificate is valid for 10 years. func GenerateCert(ca []byte, commonName string, extraSANs []string, - CAPrivateKey *rsa.PrivateKey, -) ([]byte, []byte, error) { + caPrivateKey *rsa.PrivateKey, +) ([]byte, *rsa.PrivateKey, error) { caCertificate, err := x509.ParseCertificate(ca) if err != nil { return nil, nil, fmt.Errorf("error parsing certificate: %w", err) @@ -30,7 +78,7 @@ func GenerateCert(ca []byte, // key size must be higher than 1024, otherwise the PolicyServer // TLS acceptor will refuse to start - servingPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096) + privateKey, err := rsa.GenerateKey(rand.Reader, bitSize) if err != nil { return nil, nil, fmt.Errorf("cannot generate private key: %w", err) } @@ -47,7 +95,7 @@ func GenerateCert(ca []byte, } } - newCertificate := x509.Certificate{ + cert := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: commonName, @@ -66,29 +114,24 @@ func GenerateCert(ca []byte, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature, } - servingCert, err := x509.CreateCertificate( + + certBytes, err := x509.CreateCertificate( rand.Reader, - &newCertificate, + &cert, caCertificate, - &servingPrivateKey.PublicKey, - CAPrivateKey) + &privateKey.PublicKey, + caPrivateKey) if err != nil { return nil, nil, fmt.Errorf("cannot create certificate: %w", err) } - servingCertPEM, err := PemEncodeCertificate(servingCert) - if err != nil { - return nil, nil, fmt.Errorf("cannot encode certificate to PEM format: %w", err) - } - servingPrivateKeyPKCS1 := x509.MarshalPKCS1PrivateKey(servingPrivateKey) - servingPrivateKeyPEM, err := pemEncodePrivateKey(servingPrivateKeyPKCS1) - if err != nil { - return nil, nil, fmt.Errorf("cannot encode private key to PEM format: %w", err) - } - return servingCertPEM, servingPrivateKeyPEM, nil + + return certBytes, privateKey, nil } -func PemEncodeCertificate(certificate []byte) ([]byte, error) { +// PEMEncodeCertificate encodes a certificate to PEM format +func PEMEncodeCertificate(certificate []byte) ([]byte, error) { certificatePEM := new(bytes.Buffer) + err := pem.Encode(certificatePEM, &pem.Block{ Type: "CERTIFICATE", Bytes: certificate, @@ -96,17 +139,22 @@ func PemEncodeCertificate(certificate []byte) ([]byte, error) { if err != nil { return []byte{}, fmt.Errorf("PEM encode failure: %w", err) } + return certificatePEM.Bytes(), nil } -func pemEncodePrivateKey(privateKey []byte) ([]byte, error) { +// PEMEncodePrivateKey encodes a private key to PEM format +func PEMEncodePrivateKey(privateKey *rsa.PrivateKey) ([]byte, error) { + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) privateKeyPEM := new(bytes.Buffer) + err := pem.Encode(privateKeyPEM, &pem.Block{ Type: "RSA PRIVATE KEY", - Bytes: privateKey, + Bytes: privateKeyBytes, }) if err != nil { return []byte{}, fmt.Errorf("PEM encode failure: %w", err) } + return privateKeyPEM.Bytes(), nil }