Skip to content

Commit

Permalink
feat: create root ca.
Browse files Browse the repository at this point in the history
Updates the controller to create the root ca used in all the
certificates used by the Kubewarden stack.

Signed-off-by: José Guilherme Vanz <[email protected]>
  • Loading branch information
jvanz committed Aug 29, 2023
1 parent a8d1538 commit 862f4af
Show file tree
Hide file tree
Showing 24 changed files with 1,098 additions and 175 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build the manager binary
FROM golang:1.19 as builder
FROM golang:1.20 as builder

WORKDIR /workspace
# Copy the Go Modules manifests
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ setup-envtest: $(SETUP_ENVTEST) # Build setup-envtest
unit-tests: manifests generate fmt vet setup-envtest ## Run unit tests.
KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test ./internal/... -test.v -coverprofile cover.out
KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test ./pkg/... -test.v -coverprofile cover.out
KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test . -test.v -coverprofile cover.out

.PHONY: setup-envtest integration-tests
integration-tests: manifests generate fmt vet setup-envtest ## Run integration tests.
Expand All @@ -124,7 +125,7 @@ build: generate fmt vet ## Build manager binary.
go build -o bin/manager .

run: manifests generate fmt vet ## Run a controller from your host.
go run . -deployments-namespace kubewarden --default-policy-server default
go run . -deployments-namespace kubewarden --default-policy-server default -zap-log-level debug

docker-build: test ## Build docker image with the manager.
docker build -t ${IMG} .
Expand Down
11 changes: 7 additions & 4 deletions controllers/admissionpolicy_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ var _ = Describe("Given an AdmissionPolicy", func() {
Context("and it has a non-empty policy server set on its spec", func() {
var (
policyNamespace = someNamespace.Name
policyName = "scheduled-policy"
policyServerName = "some-policy-server"
policyName = "scheduled-policy"
)
BeforeEach(func() {
Expect(
Expand All @@ -76,16 +76,19 @@ var _ = Describe("Given an AdmissionPolicy", func() {
func(admissionPolicy *policiesv1.AdmissionPolicy) policiesv1.PolicyStatusEnum {
return admissionPolicy.Status.PolicyStatus
},
Equal(policiesv1.PolicyStatusScheduled),
),
)
Equal(policiesv1.PolicyStatusScheduled)))
})
Context("and the targeted policy server is created", func() {
BeforeEach(func() {
Expect(
k8sClient.Create(ctx, policyServer(policyServerName)),
).To(HaveSucceededOrAlreadyExisted())
})
AfterEach(func() {
Expect(
k8sClient.Delete(ctx, policyServer(policyServerName)),
).To(Succeed())
})
It(fmt.Sprintf("should set its policy status to %q", policiesv1.PolicyStatusPending), func() {
Eventually(func(g Gomega) (*policiesv1.AdmissionPolicy, error) {
return getFreshAdmissionPolicy(policyNamespace, policyName)
Expand Down
2 changes: 1 addition & 1 deletion controllers/clusteradmissionpolicy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (r *ClusterAdmissionPolicyReconciler) Reconcile(ctx context.Context, req ct
if apierrors.IsNotFound(err) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, fmt.Errorf("cannot retrieve admission policy: %w", err)
return ctrl.Result{}, fmt.Errorf("cannot retrieve cluster admission policy: %w", err)
}

return startReconciling(ctx, r.Reconciler.Client, r.Reconciler, &clusterAdmissionPolicy)
Expand Down
9 changes: 6 additions & 3 deletions controllers/clusteradmissionpolicy_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ import (
"fmt"
"time"

// "github.com/kubewarden/kubewarden-controller/internal/pkg/constants"
policiesv1 "github.com/kubewarden/kubewarden-controller/pkg/apis/policies/v1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
// admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
// "sigs.k8s.io/controller-runtime/pkg/client"
)

var _ = Describe("Given a ClusterAdmissionPolicy", func() {
Expand Down Expand Up @@ -54,11 +57,11 @@ var _ = Describe("Given a ClusterAdmissionPolicy", func() {
var (
policyName = "scheduled-policy"
policyServerName = "other-policy-server"
policy *policiesv1.ClusterAdmissionPolicy
)
BeforeEach(func() {
Expect(
k8sClient.Create(ctx, clusterAdmissionPolicyWithPolicyServerName(policyName, policyServerName)),
).To(HaveSucceededOrAlreadyExisted())
policy = clusterAdmissionPolicyWithPolicyServerName(policyName, policyServerName)
Expect(k8sClient.Create(ctx, policy)).To(HaveSucceededOrAlreadyExisted())
})
It(fmt.Sprintf("should set its policy status to %q", policiesv1.PolicyStatusScheduled), func() {
Eventually(func(g Gomega) (*policiesv1.ClusterAdmissionPolicy, error) {
Expand Down
10 changes: 8 additions & 2 deletions controllers/policy_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ func startReconciling(ctx context.Context, client client.Client, reconciler admi

_ = setPolicyStatus(ctx, reconciler.DeploymentsNamespace, reconciler.APIReader, policy)
if err := client.Status().Update(ctx, policy); err != nil {
if apierrors.IsConflict(err) {
return ctrl.Result{
Requeue: true,
RequeueAfter: time.Second * 5,
}, nil
}
return ctrl.Result{}, fmt.Errorf("update admission policy status error: %w", err)
}

Expand Down Expand Up @@ -144,8 +150,8 @@ func reconcilePolicy(ctx context.Context, client client.Client, reconciler admis
)

secret := corev1.Secret{}
if err := client.Get(ctx, types.NamespacedName{Namespace: reconciler.DeploymentsNamespace, Name: constants.PolicyServerCARootSecretName}, &secret); err != nil {
return ctrl.Result{}, errors.Wrap(err, "cannot find policy server secret")
if err := client.Get(ctx, types.NamespacedName{Namespace: reconciler.DeploymentsNamespace, Name: constants.KubewardenCARootSecretName}, &secret); err != nil {
return ctrl.Result{}, errors.Wrap(err, "cannot find root CA secret")
}

if policy.IsMutating() {
Expand Down
29 changes: 29 additions & 0 deletions controllers/policyserver_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/kubewarden/kubewarden-controller/internal/pkg/constants"
policiesv1 "github.com/kubewarden/kubewarden-controller/pkg/apis/policies/v1"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -80,6 +81,12 @@ func (r *PolicyServerReconciler) Reconcile(ctx context.Context, req ctrl.Request
reconcileResult, reconcileErr := r.reconcile(ctx, &policyServer, policies)

if err := r.Client.Status().Update(ctx, &policyServer); err != nil {
if apierrors.IsConflict(err) {
return ctrl.Result{
Requeue: true,
RequeueAfter: time.Second * 5,
}, nil
}
return ctrl.Result{}, fmt.Errorf("update policy server status error: %w", err)
}

Expand Down Expand Up @@ -191,6 +198,28 @@ func (r *PolicyServerReconciler) SetupWithManager(mgr ctrl.Manager) error {
},
}
})).
Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(func(object client.Object) []reconcile.Request {
// watches for secret to detect when a policy server
// secret change. Therefore, the certificate can be
// recreated and the webhooks updated
secret, ok := object.(*corev1.Secret)
if !ok {
r.Log.Info("object is not type of secret: %+v", "secret", secret)
return []ctrl.Request{}
}

if policyServerName, isPolicyServerSecret := secret.Labels[constants.PolicyServerLabelKey]; isPolicyServerSecret {
return []ctrl.Request{
{
NamespacedName: client.ObjectKey{
Name: policyServerName,
},
},
}
}
return []ctrl.Request{}

})).
Complete(r)

return errors.Wrap(err, "failed enrolling controller with manager")
Expand Down
93 changes: 92 additions & 1 deletion controllers/policyserver_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,110 @@ import (
policiesv1 "github.com/kubewarden/kubewarden-controller/pkg/apis/policies/v1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/kubewarden/kubewarden-controller/internal/pkg/constants"
)

var _ = Describe("Given a PolicyServer", func() {
var (
policyServerName = "policy-server"
policyServerName = "policy-server"
policyServerNameWithPrefix = policyServer(policyServerName).NameWithPrefix()
)
BeforeEach(func() {
Expect(
k8sClient.Create(ctx, policyServer(policyServerName)),
).To(HaveSucceededOrAlreadyExisted())
})
Context("policy server certificate", func() {
It("a secret for the policy server certificate should be created", func() {
Eventually(func(g Gomega) ([]string, error) {
secret := &corev1.Secret{}
err := k8sClient.Get(ctx, client.ObjectKey{Name: policyServerNameWithPrefix, Namespace: DeploymentsNamespace}, secret)
if err != nil {
return []string{}, err
}
dataKeys := []string{}
for key := range secret.Data {
dataKeys = append(dataKeys, key)
}
return dataKeys, err
}, 30*time.Second, 250*time.Millisecond).Should(Equal([]string{constants.PolicyServerTLSCert, constants.PolicyServerTLSKey}))
})
It("policy server should have a label with the latest certificate secret resource version", func() {
Eventually(func(g Gomega) bool {
secret := &corev1.Secret{}
err := k8sClient.Get(ctx, client.ObjectKey{Name: policyServerNameWithPrefix, Namespace: DeploymentsNamespace}, secret)
Expect(err).ToNot(HaveOccurred())

policyServerDeploy := &appsv1.Deployment{}
err = k8sClient.Get(ctx, client.ObjectKey{Name: policyServerNameWithPrefix, Namespace: DeploymentsNamespace}, policyServerDeploy)
Expect(err).ToNot(HaveOccurred())

return secret.GetResourceVersion() == policyServerDeploy.Spec.Template.Labels[constants.PolicyServerCertificateSecret]
}, 30*time.Second, 250*time.Millisecond).Should(Equal(true))
})
})
When("policy server secret is deleted", func() {
BeforeEach(func() {
Expect(
k8sClient.Delete(ctx, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: policyServerNameWithPrefix,
Namespace: DeploymentsNamespace,
},
})).To(Succeed())
Eventually(func(g Gomega) bool {
err := k8sClient.Get(ctx, client.ObjectKey{Name: policyServerNameWithPrefix, Namespace: DeploymentsNamespace}, &corev1.Secret{})
return apierrors.IsNotFound(err)
}, 30*time.Second, 250*time.Millisecond).Should(BeTrue())
})
It("it should be recreated", func() {
Eventually(func(g Gomega) ([]string, error) {
secret := &corev1.Secret{}
err := k8sClient.Get(ctx, client.ObjectKey{Name: policyServerNameWithPrefix, Namespace: DeploymentsNamespace}, secret)
if err != nil {
return []string{}, err
}
dataKeys := []string{}
for key := range secret.Data {
dataKeys = append(dataKeys, key)
}
return dataKeys, err
}, 30*time.Second, 250*time.Millisecond).Should(Equal([]string{constants.PolicyServerTLSCert, constants.PolicyServerTLSKey}))
})
It("policy server should have a label with the latest certificate secret resource version", func() {
Eventually(func(g Gomega) (bool, error) {
secret := &corev1.Secret{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: policyServerNameWithPrefix, Namespace: DeploymentsNamespace}, secret); err != nil {
return false, err
}

policyServerDeploy := &appsv1.Deployment{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: policyServerNameWithPrefix, Namespace: DeploymentsNamespace}, policyServerDeploy); err != nil {
return false, err
}

return secret.GetResourceVersion() == policyServerDeploy.Spec.Template.Labels[constants.PolicyServerCertificateSecret], nil
}, 30*time.Second, 250*time.Millisecond).Should(Equal(true))
})
})
When("policy server is deleted", func() {
It("its secret should be deleted as well", func() {
Expect(
k8sClient.Delete(ctx, policyServer(policyServerName)),
).To(Succeed())

Eventually(func(g Gomega) bool {
err := k8sClient.Get(ctx, client.ObjectKey{Name: policyServerNameWithPrefix, Namespace: DeploymentsNamespace}, &corev1.Secret{})
return apierrors.IsNotFound(err)
}, 30*time.Second, 250*time.Millisecond).Should(BeTrue())
})
})
When("it has no assigned policies", func() {
Context("and it is deleted", func() {
BeforeEach(func() {
Expand Down Expand Up @@ -64,6 +154,7 @@ var _ = Describe("Given a PolicyServer", func() {
k8sClient.Create(ctx, clusterAdmissionPolicyWithPolicyServerName(policyName, policyServerName)),
).To(HaveSucceededOrAlreadyExisted())
})

Context("and it is deleted", func() {
BeforeEach(func() {
Expect(
Expand Down
32 changes: 32 additions & 0 deletions controllers/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controllers

import (
"context"
"crypto/x509"
"log"
"path/filepath"
"testing"
Expand All @@ -36,6 +37,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"github.com/kubewarden/kubewarden-controller/internal/pkg/admission"
"github.com/kubewarden/kubewarden-controller/internal/pkg/admissionregistration"
"github.com/kubewarden/kubewarden-controller/internal/pkg/constants"
//+kubebuilder:scaffold:imports
)

Expand All @@ -48,6 +51,7 @@ var testEnv *envtest.Environment
var ctx context.Context
var cancel context.CancelFunc
var reconciler admission.Reconciler
var caRootSecret *corev1.Secret

const (
DeploymentsNamespace = "kubewarden-integration-tests"
Expand Down Expand Up @@ -125,6 +129,34 @@ var _ = BeforeSuite(func() {
log.Fatalf("could not create namespace %q needed for the integration tests", DeploymentsNamespace)
}

// Create the root CA generated in the main of the controller
caRoot, err := admissionregistration.GenerateCA()
if err != nil {
log.Fatalf("cannot generate policy-server secret CA")
}
caPEMEncoded, err := admissionregistration.PemEncodeCertificate(caRoot.CaCert)
if err != nil {
log.Fatalf("cannot encode policy-server secret CA")
}
caPrivateKeyBytes := x509.MarshalPKCS1PrivateKey(caRoot.CaPrivateKey)
caRootSecret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: constants.KubewardenCARootSecretName,
Namespace: DeploymentsNamespace,
},
Data: map[string][]byte{
constants.CARootCACert: caRoot.CaCert,
constants.CARootCACertPem: caPEMEncoded,
constants.CARootPrivateKeyCertName: caPrivateKeyBytes,
},
Type: corev1.SecretTypeOpaque,
}

// Create the integration tests deployments namespace
if err := k8sClient.Create(ctx, caRootSecret); err != nil {
log.Fatalf("could not create root CA secret: ")
}

go func() {
defer GinkgoRecover()
err = k8sManager.Start(ctx)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,5 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

replace github.com/ereslibre/kube-webhook-wrapper v0.0.2 => github.com/jvanz/kube-webhook-wrapper v0.0.0-20230822174616-a67c0d737762
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ereslibre/kube-webhook-wrapper v0.0.2 h1:GaSN5jfSPJV7KNuVzSFflS5Rr5wQNKVsZbL6Gq1ZHYw=
github.com/ereslibre/kube-webhook-wrapper v0.0.2/go.mod h1:vDSrlA2/6bFRlDdVCC0Woe21U5J9s9a7FfSgaxuTPv4=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
Expand Down Expand Up @@ -317,6 +315,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jvanz/kube-webhook-wrapper v0.0.0-20230822174616-a67c0d737762 h1:s++gelgq+Od1qgDPfYjJu9/CurV73FSkjXOLubW8w1g=
github.com/jvanz/kube-webhook-wrapper v0.0.0-20230822174616-a67c0d737762/go.mod h1:R4GtKm8O+QH4Bglxefern9eLkVpVKXGwje3uJkSR/T8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/admission/mutating-webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (r *Reconciler) mutatingWebhookConfiguration(
Name: fmt.Sprintf("%s.kubewarden.admission", policy.GetUniqueName()),
ClientConfig: admissionregistrationv1.WebhookClientConfig{
Service: &service,
CABundle: admissionSecret.Data[constants.PolicyServerCARootPemName],
CABundle: admissionSecret.Data[constants.CARootCACertPem],
},
Rules: policy.GetRules(),
FailurePolicy: policy.GetFailurePolicy(),
Expand Down
Loading

0 comments on commit 862f4af

Please sign in to comment.