From f9103f66197ade3a06f4d48c9ea85f48d8c1ff1e Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Tue, 19 Dec 2023 19:01:16 +0000 Subject: [PATCH] Adding securityContext (#216) * Adding securityContext * Implementing comments from @brandond * Removing deep merge * Fixing test Signed-off-by: Jiri Tyr --- pkg/apis/helm.cattle.io/v1/types.go | 3 + pkg/controllers/chart/chart.go | 72 +++++++++ test/suite/helm_test.go | 229 +++++++++++++++++++++++++++- 3 files changed, 302 insertions(+), 2 deletions(-) diff --git a/pkg/apis/helm.cattle.io/v1/types.go b/pkg/apis/helm.cattle.io/v1/types.go index 8e01cc39..3172ae9a 100644 --- a/pkg/apis/helm.cattle.io/v1/types.go +++ b/pkg/apis/helm.cattle.io/v1/types.go @@ -39,6 +39,9 @@ type HelmChartSpec struct { AuthPassCredentials bool `json:"authPassCredentials,omitempty"` DockerRegistrySecret *corev1.LocalObjectReference `json:"dockerRegistrySecret,omitempty"` + + PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` } type HelmChartStatus struct { diff --git a/pkg/controllers/chart/chart.go b/pkg/controllers/chart/chart.go index b7997bb7..291652d1 100644 --- a/pkg/controllers/chart/chart.go +++ b/pkg/controllers/chart/chart.go @@ -56,6 +56,22 @@ var ( DefaultJobImage = "rancher/klipper-helm:v0.8.2-build20230815" DefaultFailurePolicy = FailurePolicyReinstall defaultBackOffLimit = pointer.Int32(1000) + + defaultPodSecurityContext = &corev1.PodSecurityContext{ + RunAsNonRoot: pointer.BoolPtr(true), + SeccompProfile: &corev1.SeccompProfile{ + Type: "RuntimeDefault", + }, + } + defaultSecurityContext = &corev1.SecurityContext{ + AllowPrivilegeEscalation: pointer.BoolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + ReadOnlyRootFilesystem: pointer.BoolPtr(true), + } ) type Controller struct { @@ -377,6 +393,9 @@ func job(chart *v1.HelmChart, apiServerPort string) (*batch.Job, *corev1.Secret, chartName = chart.Name + "/" + chart.Spec.Chart } + podSecurityContext := defaultPodSecurityContext.DeepCopy() + securityContext := defaultSecurityContext.DeepCopy() + job := &batch.Job{ TypeMeta: metav1.TypeMeta{ APIVersion: "batch/v1", @@ -443,9 +462,51 @@ func job(chart *v1.HelmChart, apiServerPort string) (*batch.Job, *corev1.Secret, Value: fmt.Sprintf("%t", chart.Spec.AuthPassCredentials), }, }, + SecurityContext: securityContext, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "klipper-helm", + MountPath: "/home/klipper-helm/.helm", + }, + { + Name: "klipper-cache", + MountPath: "/home/klipper-helm/.cache", + }, + { + Name: "klipper-config", + MountPath: "/home/klipper-helm/.config", + }, + }, }, }, ServiceAccountName: fmt.Sprintf("helm-%s", chart.Name), + SecurityContext: podSecurityContext, + Volumes: []corev1.Volume{ + { + Name: "klipper-helm", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + }, + { + Name: "klipper-cache", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + }, + { + Name: "klipper-config", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + }, + }, }, }, }, @@ -507,6 +568,7 @@ func job(chart *v1.HelmChart, apiServerPort string) (*batch.Job, *corev1.Secret, setAuthSecret(job, chart) setDockerRegistrySecret(job, chart) setRepoCAConfigMap(job, chart) + setSecurityContext(job, chart) valuesSecret := setValuesSecret(job, chart) contentConfigMap := setContentConfigMap(job, chart) @@ -831,3 +893,13 @@ func hashObjects(job *batch.Job, objs ...metav1.Object) { func setBackOffLimit(job *batch.Job, backOffLimit *int32) { job.Spec.BackoffLimit = backOffLimit } + +func setSecurityContext(job *batch.Job, chart *v1.HelmChart) { + if chart.Spec.PodSecurityContext != nil { + job.Spec.Template.Spec.SecurityContext = chart.Spec.PodSecurityContext + } + + if chart.Spec.SecurityContext != nil { + job.Spec.Template.Spec.Containers[0].SecurityContext = chart.Spec.SecurityContext + } +} diff --git a/test/suite/helm_test.go b/test/suite/helm_test.go index 411e961f..340d858f 100644 --- a/test/suite/helm_test.go +++ b/test/suite/helm_test.go @@ -15,6 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" ) var _ = Describe("Helm Tests", Ordered, func() { @@ -362,7 +363,6 @@ var _ = Describe("Helm Tests", Ordered, func() { _, err := framework.GetHelmChart(chart.Name, framework.Namespace) return err != nil && apierrors.IsNotFound(err) }, 120*time.Second, 5*time.Second).Should(BeTrue()) - }) }) @@ -474,7 +474,6 @@ var _ = Describe("Helm Tests", Ordered, func() { return err != nil && apierrors.IsNotFound(err) }, 120*time.Second, 5*time.Second).Should(BeTrue()) }) - }) Context("When a no backoffLimit is specified", func() { @@ -528,6 +527,232 @@ var _ = Describe("Helm Tests", Ordered, func() { return err != nil && apierrors.IsNotFound(err) }, 120*time.Second, 5*time.Second).Should(BeTrue()) }) + }) + + Context("When a custom podSecurityContext is specified", func() { + var ( + err error + chart *v1.HelmChart + job *batchv1.Job + expectedPodSecurityContext = &corev1.PodSecurityContext{ + RunAsNonRoot: pointer.BoolPtr(false), + } + ) + BeforeEach(func() { + chart = framework.NewHelmChart("traefik-example-custom-podsecuritycontext", + "stable/traefik", + "1.86.1", + "v3", + map[string]intstr.IntOrString{ + "rbac.enabled": { + Type: intstr.String, + StrVal: "true", + }, + "ssl.enabled": { + Type: intstr.String, + StrVal: "true", + }, + }) + chart.Spec.PodSecurityContext = &corev1.PodSecurityContext{ + RunAsNonRoot: pointer.BoolPtr(false), + } + chart, err = framework.CreateHelmChart(chart, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + + labelSelector := labels.SelectorFromSet(labels.Set{ + "owner": "helm", + "name": chart.Name, + }) + _, err = framework.WaitForRelease(chart, labelSelector, 120*time.Second, 1) + Expect(err).ToNot(HaveOccurred()) + + chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) + Expect(err).ToNot(HaveOccurred()) + job, err = framework.GetJob(chart) + Expect(err).ToNot(HaveOccurred()) + }) + It("Should have correct pod securityContext", func() { + Expect(*job.Spec.Template.Spec.SecurityContext).To(Equal(*expectedPodSecurityContext)) + }) + AfterEach(func() { + err = framework.DeleteHelmChart(chart.Name, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + + Eventually(func() bool { + _, err := framework.GetHelmChart(chart.Name, framework.Namespace) + return err != nil && apierrors.IsNotFound(err) + }, 120*time.Second, 5*time.Second).Should(BeTrue()) + }) + }) + + Context("When a no podSecurityContext is specified", func() { + var ( + err error + chart *v1.HelmChart + job *batchv1.Job + defaultPodSecurityContext = &corev1.PodSecurityContext{ + RunAsNonRoot: pointer.BoolPtr(true), + SeccompProfile: &corev1.SeccompProfile{ + Type: "RuntimeDefault", + }, + } + ) + BeforeEach(func() { + chart = framework.NewHelmChart("traefik-example-default-podsecuritycontext", + "stable/traefik", + "1.86.1", + "v3", + map[string]intstr.IntOrString{ + "rbac.enabled": { + Type: intstr.String, + StrVal: "true", + }, + "ssl.enabled": { + Type: intstr.String, + StrVal: "true", + }, + }) + chart, err = framework.CreateHelmChart(chart, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + + labelSelector := labels.SelectorFromSet(labels.Set{ + "owner": "helm", + "name": chart.Name, + }) + _, err = framework.WaitForRelease(chart, labelSelector, 120*time.Second, 1) + Expect(err).ToNot(HaveOccurred()) + + chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) + Expect(err).ToNot(HaveOccurred()) + job, err = framework.GetJob(chart) + Expect(err).ToNot(HaveOccurred()) + }) + It("Should have correct pod securityContext", func() { + Expect(*job.Spec.Template.Spec.SecurityContext).To(Equal(*defaultPodSecurityContext)) + }) + AfterEach(func() { + err = framework.DeleteHelmChart(chart.Name, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + + Eventually(func() bool { + _, err := framework.GetHelmChart(chart.Name, framework.Namespace) + return err != nil && apierrors.IsNotFound(err) + }, 120*time.Second, 5*time.Second).Should(BeTrue()) + }) + }) + + Context("When a custom securityContext is specified", func() { + var ( + err error + chart *v1.HelmChart + job *batchv1.Job + expectedSecurityContext = &corev1.SecurityContext{ + AllowPrivilegeEscalation: pointer.BoolPtr(true), + } + ) + BeforeEach(func() { + chart = framework.NewHelmChart("traefik-example-custom-securitycontext", + "stable/traefik", + "1.86.1", + "v3", + map[string]intstr.IntOrString{ + "rbac.enabled": { + Type: intstr.String, + StrVal: "true", + }, + "ssl.enabled": { + Type: intstr.String, + StrVal: "true", + }, + }) + chart.Spec.SecurityContext = &corev1.SecurityContext{ + AllowPrivilegeEscalation: pointer.BoolPtr(true), + } + chart, err = framework.CreateHelmChart(chart, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + + labelSelector := labels.SelectorFromSet(labels.Set{ + "owner": "helm", + "name": chart.Name, + }) + _, err = framework.WaitForRelease(chart, labelSelector, 120*time.Second, 1) + Expect(err).ToNot(HaveOccurred()) + + chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) + Expect(err).ToNot(HaveOccurred()) + job, err = framework.GetJob(chart) + Expect(err).ToNot(HaveOccurred()) + }) + It("Should have correct container securityContext", func() { + Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext).To(Equal(*expectedSecurityContext)) + }) + AfterEach(func() { + err = framework.DeleteHelmChart(chart.Name, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + Eventually(func() bool { + _, err := framework.GetHelmChart(chart.Name, framework.Namespace) + return err != nil && apierrors.IsNotFound(err) + }, 120*time.Second, 5*time.Second).Should(BeTrue()) + }) + }) + + Context("When a no securityContext is specified", func() { + var ( + err error + chart *v1.HelmChart + job *batchv1.Job + defaultSecurityContext = &corev1.SecurityContext{ + AllowPrivilegeEscalation: pointer.BoolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + ReadOnlyRootFilesystem: pointer.BoolPtr(true), + } + ) + BeforeEach(func() { + chart = framework.NewHelmChart("traefik-example-default-securitycontext", + "stable/traefik", + "1.86.1", + "v3", + map[string]intstr.IntOrString{ + "rbac.enabled": { + Type: intstr.String, + StrVal: "true", + }, + "ssl.enabled": { + Type: intstr.String, + StrVal: "true", + }, + }) + chart, err = framework.CreateHelmChart(chart, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + + labelSelector := labels.SelectorFromSet(labels.Set{ + "owner": "helm", + "name": chart.Name, + }) + _, err = framework.WaitForRelease(chart, labelSelector, 120*time.Second, 1) + Expect(err).ToNot(HaveOccurred()) + + chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) + Expect(err).ToNot(HaveOccurred()) + job, err = framework.GetJob(chart) + Expect(err).ToNot(HaveOccurred()) + }) + It("Should have correct container securityContext", func() { + Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext).To(Equal(*defaultSecurityContext)) + }) + AfterEach(func() { + err = framework.DeleteHelmChart(chart.Name, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + + Eventually(func() bool { + _, err := framework.GetHelmChart(chart.Name, framework.Namespace) + return err != nil && apierrors.IsNotFound(err) + }, 120*time.Second, 5*time.Second).Should(BeTrue()) + }) }) })