diff --git a/helm/kanister-operator/templates/_helpers.tpl b/helm/kanister-operator/templates/_helpers.tpl index b9d9ab4688..3f537dc947 100644 --- a/helm/kanister-operator/templates/_helpers.tpl +++ b/helm/kanister-operator/templates/_helpers.tpl @@ -111,3 +111,12 @@ Define a custom kanister-tools image {{- define "kanister-tools.image" -}} {{- printf "%s:%s" (.Values.kanisterToolsImage.image) (.Values.kanisterToolsImage.tag) -}} {{- end -}} + +{{/* +Define a security Context at Container level +*/}} +{{- define "controller.containerSecurityContext" -}} + {{- if .Values.controller.containerSecurityContext -}} + {{ toYaml .Values.controller.containerSecurityContext | indent 10 }} + {{- end -}} +{{- end -}} diff --git a/helm/kanister-operator/templates/deployment.yaml b/helm/kanister-operator/templates/deployment.yaml index a560e5d0a6..e447296777 100644 --- a/helm/kanister-operator/templates/deployment.yaml +++ b/helm/kanister-operator/templates/deployment.yaml @@ -14,6 +14,10 @@ spec: labels: {{ include "kanister-operator.helmLabels" . | indent 8}} spec: +{{- if .Values.controller.podSecurityContext }} + securityContext: +{{ toYaml .Values.controller.podSecurityContext | indent 8 }} +{{- end }} serviceAccountName: {{ template "kanister-operator.serviceAccountName" . }} {{- if or .Values.bpValidatingWebhook.enabled .Values.validatingWebhook.repositoryserver.enabled }} volumes: @@ -29,6 +33,8 @@ spec: - name: {{ template "kanister-operator.fullname" . }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: +{{ include "controller.containerSecurityContext" . }} {{- if .Values.bpValidatingWebhook.enabled }} volumeMounts: - name: webhook-certs @@ -53,6 +59,8 @@ spec: - name: {{ template "repository-server-controller.name" . }} image: {{ .Values.repositoryServerControllerImage.registry }}/{{ .Values.repositoryServerControllerImage.name }}:{{ .Values.repositoryServerControllerImage.tag }} imagePullPolicy: {{ .Values.repositoryServerControllerImage.pullPolicy }} + securityContext: +{{ include "controller.containerSecurityContext" . }} {{- if .Values.validatingWebhook.repositoryserver.enabled }} volumeMounts: - name: webhook-certs diff --git a/helm/kanister-operator/values.yaml b/helm/kanister-operator/values.yaml index e50a4554a8..ef4a442cf8 100644 --- a/helm/kanister-operator/values.yaml +++ b/helm/kanister-operator/values.yaml @@ -89,3 +89,4 @@ tolerations: [] # # node labels for pod assignment. Evaluated as template nodeSelector: {} + diff --git a/pkg/testing/helm/helm_test.go b/pkg/testing/helm/helm_test.go index 029483fa75..a909da16a0 100644 --- a/pkg/testing/helm/helm_test.go +++ b/pkg/testing/helm/helm_test.go @@ -199,6 +199,101 @@ func (h *HelmTestSuite) TestSelectedDeploymentAttrFromKanisterHelmDryRunInstall( } } +// Test for Pod and Container-level securityContext in the Helm chart +func (h *HelmTestSuite) TestSecurityContextInHelmChart(c *check.C) { + podSecurity := corev1.PodSecurityContext{ + RunAsUser: intPtr(1000), + FSGroup: intPtr(2000), + RunAsNonRoot: boolPtr(true), + } + + containerSecurity := corev1.SecurityContext{ + RunAsNonRoot: boolPtr(true), + ReadOnlyRootFilesystem: boolPtr(true), + AllowPrivilegeEscalation: boolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + } + var testCases = []struct { + testName string + helmValues map[string]string + expectedPodSecurity *corev1.PodSecurityContext + expectedContainerSecurity *corev1.SecurityContext + }{ + { + testName: "Pod and Container security context are set", + helmValues: map[string]string{ + "controller.podSecurityContext.runAsUser": "1000", + "controller.podSecurityContext.fsGroup": "2000", + "controller.podSecurityContext.runAsNonRoot": "true", + "controller.containerSecurityContext.capabilities.drop[0]": "ALL", + "controller.containerSecurityContext.runAsNonRoot": "true", + "controller.containerSecurityContext.readOnlyRootFilesystem": "true", + "controller.containerSecurityContext.allowPrivilegeEscalation": "false", + }, + expectedPodSecurity: &podSecurity, + expectedContainerSecurity: &containerSecurity, + }, + { + testName: "Only Container security context is set", + helmValues: map[string]string{ + "controller.containerSecurityContext.capabilities.drop[0]": "ALL", + "controller.containerSecurityContext.runAsNonRoot": "true", + "controller.containerSecurityContext.readOnlyRootFilesystem": "true", + "controller.containerSecurityContext.allowPrivilegeEscalation": "false", + }, + expectedPodSecurity: nil, + expectedContainerSecurity: &containerSecurity, + }, + { + testName: "Only Pod security context is set", + helmValues: map[string]string{ + "controller.podSecurityContext.runAsUser": "1000", + "controller.podSecurityContext.fsGroup": "2000", + "controller.podSecurityContext.runAsNonRoot": "true", + }, + expectedPodSecurity: &podSecurity, + expectedContainerSecurity: nil, + }, + } + + for _, tc := range testCases { + c.Logf("Test name: %s", tc.testName) + defer func() { + h.helmApp.dryRun = false + }() + + testApp, err := NewHelmApp(tc.helmValues, kanisterName, "../../../helm/kanister-operator", kanisterName, "", true) + c.Assert(err, check.IsNil) + + out, err := testApp.Install() + c.Assert(err, check.IsNil) + + resources := helm.ResourcesFromRenderedManifest(out, func(kind helm.K8sObjectType) bool { + return kind == helm.K8sObjectTypeDeployment + }) + c.Assert(len(resources), check.Equals, 1) + + deployments, err := helm.K8sObjectsFromRenderedResources[*appsv1.Deployment](resources) + c.Assert(err, check.IsNil) + + var obj = deployments[h.deploymentName] + c.Assert(obj, check.NotNil) + + c.Assert(obj.Spec.Template.Spec.SecurityContext, check.DeepEquals, tc.expectedPodSecurity) + c.Assert(obj.Spec.Template.Spec.Containers[0].SecurityContext, check.DeepEquals, tc.expectedContainerSecurity) + } +} + +func boolPtr(b bool) *bool { + return &b +} + +func intPtr(i int64) *int64 { + return &i +} + func (h *HelmTestSuite) TearDownSuite(c *check.C) { c.Log("Uninstalling chart") err := h.helmApp.Uninstall()