Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ test: manifests generate fmt vet setup-envtest ## Run tests.
# CertManager is installed by default; skip with:
# - CERT_MANAGER_INSTALL_SKIP=true
K3D_CLUSTER ?= rt-bootstrapper-test-e2e
K3D_IMAGE ?= rancher/k3s:v1.33.6-k3s1
K3D_ARGS ?= --k3s-arg '--kube-apiserver-arg=feature-gates=ClusterTrustBundle=true,ClusterTrustBundleProjection=true@server:*' \
--k3s-arg '--kube-apiserver-arg=runtime-config=certificates.k8s.io/v1beta1/clustertrustbundles=true@server:*'

.PHONY: setup-test-e2e
setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
Expand All @@ -77,7 +80,7 @@ setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
echo "K3D cluster '$(K3D_CLUSTER)' already exists. Skipping creation." ;; \
*) \
echo "Creating K3D cluster '$(K3D_CLUSTER)'..."; \
$(K3D) cluster create $(K3D_CLUSTER) ;; \
$(K3D) cluster create --image=$(K3D_IMAGE) $(K3D_ARGS) $(K3D_CLUSTER) ;; \
esac

.PHONY: test-e2e
Expand Down
6 changes: 6 additions & 0 deletions config/config/rt-bootstrapper-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ data:
"overrides": {
"replace.me": "ghcr.io",
"example.com": "ghcr.io"
},
"clusterTrustBundleMapping": {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fully convinced to the name of the element. Instead of using clusterTrustBundleMapping we could do something like:

"clusterTrustBundle": {
       "name": "rt-bootstrapper-k3d.test:ctb:1",
        "certWritePath": "kube-apiserver-serving.pem",
        "volumeMountPath": "/etc/ssl/certs",
        "volumeName": "rt-bootstrapper-certs"
      }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets do this refactor in the this PR just to avoid collisions.

"clusterTrustBundleName": "rt-bootstrapper-k3d.test:ctb:1",
"certWritePath": "kube-apiserver-serving.pem",
"volumeMountPath": "/etc/ssl/certs",
"volumeName": "rt-bootstrapper-certs"
}
}
kind: ConfigMap
Expand Down
18 changes: 18 additions & 0 deletions config/k3d/cluster_trust_bundle.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: certificates.k8s.io/v1beta1
kind: ClusterTrustBundle
metadata:
name: k3d.test:ctb:1
spec:
signerName: rt-bootstrapper-k3d.test/ctb
trustBundle: |
-----BEGIN CERTIFICATE-----
MIIBdzCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
dmVyLWNhQDE3NjcwMTMxNjUwHhcNMjUxMjI5MTI1OTI1WhcNMzUxMjI3MTI1OTI1
WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE3NjcwMTMxNjUwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAASDZGb8hHA4r7/tLECdLLLtOQpfA0W+5FXdc4xJI7Zi
dwXz4WiliqVIxi77ow+c39EOe29X8yuNtbOouWsqn1Vho0IwQDAOBgNVHQ8BAf8E
BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUoqr1Zk9sh+WqFtLhFgUe
e0m5zGEwCgYIKoZIzj0EAwIDSAAwRQIhAK5eY2h5Ui8OivvqqpmWPx7rJYiEWR1g
+K3J/5+FXUv2AiBQUtMXc/FlAHWT3u4j98v4XukRZftEVbrVK6+zn6EaFQ==
-----END CERTIFICATE-----

1 change: 1 addition & 0 deletions config/k3d/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ resources:
- ../manager
- ../certmanager
- metrics_service.yaml
- cluster_trust_bundle.yaml
- ../config
- ../webhook

Expand Down
45 changes: 45 additions & 0 deletions internal/webhook/k8s/utilz.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package k8s
import (
"fmt"
"strings"

corev1 "k8s.io/api/core/v1"
)

func fixRegistry(registry string, overrides map[string]string) string {
Expand Down Expand Up @@ -38,3 +40,46 @@ func Contains(l map[string]string, r map[string]string) bool {
}
return true
}

type ClusterTrustBundleMapping struct {
ClusterTrustBundleName string `json:"clusterTrustBundleName" validate:"required"`
CertWritePath string `json:"certWritePath" validate:"required"`
VolumeMountPath string `json:"volumeMountPath" validate:"required"`
VolumeName string `json:"volumeName" validate:"required"`
}

func (r ClusterTrustBundleMapping) ClusterTrustedBundle() corev1.Volume {
return corev1.Volume{
Name: r.VolumeName,
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
Sources: []corev1.VolumeProjection{
{
ClusterTrustBundle: &corev1.ClusterTrustBundleProjection{
Name: &r.ClusterTrustBundleName,
Path: r.CertWritePath,
},
},
},
},
},
}

}

func (r ClusterTrustBundleMapping) VolumeMount() corev1.VolumeMount {
return corev1.VolumeMount{
Name: r.VolumeName,
ReadOnly: true,
MountPath: r.VolumeMountPath,
}
}

func (r ClusterTrustBundleMapping) KeysAndValues() []any {
return []any{
"name", r.VolumeName,
"signer", r.ClusterTrustBundleName,
"certWritePath", r.CertWritePath,
"volumeMountPath", r.VolumeMountPath,
}
}
75 changes: 75 additions & 0 deletions internal/webhook/v1/pod_defaulters.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package v1

import (
"log/slog"
"reflect"
"slices"

"github.com/kyma-project/rt-bootstrapper/internal/webhook/k8s"
Expand All @@ -18,6 +19,9 @@ var (
annotationsSetPullSecret = map[string]string{
apiv1.AnnotationSetPullSecret: "false",
}
annotationAddClusterTrustBundle = map[string]string{
apiv1.AnnotationAddClusterTrustBundle: "false",
}
)

func defaultPod(update func(*corev1.Pod) bool, features map[string]string) PodDefaulter {
Expand Down Expand Up @@ -96,3 +100,74 @@ func BuildPodDefaulterAddImagePullSecrets(secretName string) PodDefaulter {

return defaultPod(addImgPullSecret, annotationsSetPullSecret)
}

func BuildDefaulterAddClusterTrustBundle(mapping k8s.ClusterTrustBundleMapping) PodDefaulter {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a general remark: the defaulters could be tested by unit tests. I don't think we must add such unit tests right now, but it would nicely fit into the entire testing idea for the webhook.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fully agree, we will add tests for all defaulters.
I was planning a small refactor afer we merge everything that is under development, I would add the tests then.

slog.Debug("building volume", mapping.KeysAndValues()...)

vol := mapping.ClusterTrustedBundle()

handleVolumeMount := func(cs []corev1.Container) bool {
// stores information if any container was modified
var result bool

for i, c := range cs {
index := slices.IndexFunc(c.VolumeMounts, func(vm corev1.VolumeMount) bool {
return vm.Name == mapping.VolumeName
})

if index == -1 {
vm := mapping.VolumeMount()
cs[i].VolumeMounts = append(c.VolumeMounts, vm)
result = true
slog.Debug("volume mount added")
continue
}

if reflect.DeepEqual(c.VolumeMounts[index], vol) {
slog.Debug("volume already mounted, nothing to do")
continue
}

vm := mapping.VolumeMount()
cs[i].VolumeMounts[index] = vm
slog.Debug("volume mount replaced")
result = true
}

return result
}

handleVolumeMounts := func(modified bool, p *corev1.Pod) bool {
for _, cs := range [][]corev1.Container{p.Spec.Containers, p.Spec.InitContainers} {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case we will have two or more containers the volume mounts will be modified only for the first one. || is a conditional operator so that if the first argument is true the second will not be evaluated.

I think the code should be modified as follows:

 if handleVolumeMount(cs) {
        result = true
    }

if handleVolumeMount(cs) {
modified = true
}
}
return modified
}

handleClusterTrustBundle := func(p *corev1.Pod) bool {
index := slices.IndexFunc(p.Spec.Volumes, func(v corev1.Volume) bool {
return v.Name == mapping.VolumeName
})

if index == -1 {
// volume does not exist, add it
p.Spec.Volumes = append(p.Spec.Volumes, vol)
slog.Debug("volume added")
return handleVolumeMounts(true, p)
}

if reflect.DeepEqual(p.Spec.Volumes[index], vol) {
slog.Debug("volume already added, nothing to do")
return handleVolumeMounts(false, p)
}

p.Spec.Volumes[index] = vol
slog.Debug("volume replaced")

return handleVolumeMounts(true, p)
}

return defaultPod(handleClusterTrustBundle, annotationAddClusterTrustBundle)
}
7 changes: 7 additions & 0 deletions internal/webhook/v1/pod_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ func SetupPodWebhookWithManager(mgr ctrl.Manager, cfg *apiv1.Config) error {
GetNsAnnotations: getNamespace,
}

// conditional defaulters

if cfg.ClusterTrustBundleMapping != nil {
d3 := BuildDefaulterAddClusterTrustBundle(*cfg.ClusterTrustBundleMapping)
defaulter.defaulters = append(defaulter.defaulters, d3)
}

return ctrl.NewWebhookManagedBy(mgr).For(&corev1.Pod{}).
WithDefaulter(&defaulter).
Complete()
Expand Down
19 changes: 11 additions & 8 deletions pkg/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ import (
"time"

"github.com/go-playground/validator/v10"
"github.com/kyma-project/rt-bootstrapper/internal/webhook/k8s"
)

const (
AnnotationAlterImgRegistry = "rt-cfg.kyma-project.io/alter-img-registry"
AnnotationSetPullSecret = "rt-cfg.kyma-project.io/add-img-pull-secret"
AnnotationDefaulted = "rt-bootstrapper.kyma-project.io/defaulted"
FiledManager = "rt-bootstrapper"
AnnotationAlterImgRegistry = "rt-cfg.kyma-project.io/alter-img-registry"
AnnotationSetPullSecret = "rt-cfg.kyma-project.io/add-img-pull-secret"
AnnotationAddClusterTrustBundle = "rt-cfg.kyma-project.io/add-add-cluster-trust-bundle"
AnnotationDefaulted = "rt-bootstrapper.kyma-project.io/defaulted"
FiledManager = "rt-bootstrapper"
)

type Config struct {
Overrides map[string]string `json:"overrides" validate:"required"`
ImagePullSecretName string `json:"imagePullSecretName" validate:"required"`
ImagePullSecretNamespace string `json:"imagePullSecretNamespace" validate:"required"`
SecretSyncInterval Duration `json:"secretSyncInterval" validate:"required"`
Overrides map[string]string `json:"overrides" validate:"required"`
ImagePullSecretName string `json:"imagePullSecretName" validate:"required"`
ImagePullSecretNamespace string `json:"imagePullSecretNamespace" validate:"required"`
SecretSyncInterval Duration `json:"secretSyncInterval" validate:"required"`
ClusterTrustBundleMapping *k8s.ClusterTrustBundleMapping `json:"clusterTrustBundleMapping,omitempty"`
}

type Duration time.Duration
Expand Down
60 changes: 59 additions & 1 deletion test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/ptr"
)

// namespace where the project is deployed in
Expand Down Expand Up @@ -308,6 +309,17 @@ var _ = Describe("Manager", Ordered, func() {
Eventually(verifyCAInjection).Should(Succeed())
})

It("should provisioned cluster-trust-bundle", func() {
By("checking rt-bootstrapper-k3d.test:ctb:1")
cmd := exec.Command("kubectl", "get",
"clustertrustbundles.certificates.k8s.io",
"rt-bootstrapper-k3d.test:ctb:1",
"-o", "go-template={{ .spec.signerName }}")
signerName, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
Expect(signerName).To(Equal("rt-bootstrapper-k3d.test/ctb"))
})

It("should alter the image name and add imagePullSecret property", func() {
testNamespace := "rt-bootstrapper-test1"

Expand Down Expand Up @@ -418,7 +430,7 @@ var _ = Describe("Manager", Ordered, func() {
}))
})

It("should not modify pod spec", func() {
It("should inject cluster-trust-bundle", func() {
By("applying the deployment")
cmd := exec.Command("kubectl", "apply",
"-f", "./test/e2e/testdata/test3.yaml",
Expand All @@ -443,6 +455,52 @@ var _ = Describe("Manager", Ordered, func() {
output, err := utils.Run(cmd)
Expect(err).ShouldNot(HaveOccurred())

pod, err := utils.ToPod(output)
Expect(err).ShouldNot(HaveOccurred())
Expect(pod.Spec.Containers[0].Image).Should(HavePrefix("k8s.gcr.io"))
Expect(pod.Annotations[apiv1.AnnotationDefaulted]).Should(Equal("true"))
Expect(pod.Spec.Containers[0].VolumeMounts).Should(ContainElement(corev1.VolumeMount{
Name: "rt-bootstrapper-certs",
ReadOnly: true,
MountPath: "/etc/ssl/certs",
}))
Expect(pod.Spec.Volumes[1].VolumeSource.Projected.Sources).Should(ContainElement(corev1.VolumeProjection{
ClusterTrustBundle: &corev1.ClusterTrustBundleProjection{
Name: ptr.To("rt-bootstrapper-k3d.test:ctb:1"),
Path: "kube-apiserver-serving.pem",
},
}))

Expect(pod.Spec.ImagePullSecrets).ShouldNot(ContainElement(corev1.LocalObjectReference{
Name: "registry-credentials",
}))
})

It("should not modify pod spec", func() {
By("applying the deployment")
cmd := exec.Command("kubectl", "apply",
"-f", "./test/e2e/testdata/test4.yaml",
"-n", testNamespace1)

_, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())

cmd = exec.Command("kubectl", "wait", "deployment.apps/pause-test4",
"--for", "condition=Available",
"--namespace", testNamespace1,
"--timeout", "20s",
)

_, err = utils.Run(cmd)
Expect(err).ShouldNot(HaveOccurred())

cmd = exec.Command("kubectl", "get", "pod",
"-l", "app=pause-test4",
"-n", testNamespace1,
"-o", "jsonpath={.items[0]}")
output, err := utils.Run(cmd)
Expect(err).ShouldNot(HaveOccurred())

pod, err := utils.ToPod(output)
Expect(err).ShouldNot(HaveOccurred())
Expect(pod.Spec.Containers[0].Image).Should(HavePrefix("k8s.gcr.io"))
Expand Down
25 changes: 25 additions & 0 deletions test/e2e/testdata/test4.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: pause-test4
namespace: rt-bootstrapper-test1
labels:
app: pause-test4
spec:
replicas: 1
selector:
matchLabels:
app: pause-test4
template:
metadata:
annotations:
rt-cfg.kyma-project.io/add-img-pull-secret: "false"
rt-cfg.kyma-project.io/add-add-cluster-trust-bundle: "false"
labels:
app: pause-test4
spec:
containers:
- name: pause
image: k8s.gcr.io/pause:latest


Loading