From 8642244b92d0f62f374719a970b176bd67a783d9 Mon Sep 17 00:00:00 2001 From: Moritz Wiesinger Date: Thu, 16 Feb 2023 14:11:01 +0100 Subject: [PATCH] feat: add support for security context Signed-off-by: Moritz Wiesinger --- examples/app/templates/deployment.yaml | 4 +- examples/app/values.yaml | 2 + examples/operator/templates/deployment.yaml | 4 +- examples/operator/values.yaml | 11 ++ pkg/processor/deployment/deployment.go | 3 + .../imagePullSecrets/imagePullSecrets.go | 1 - .../container_security_context.go | 40 +++++ .../container_security_context_test.go | 147 ++++++++++++++++++ test_data/k8s-operator-kustomize.output | 9 ++ 9 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 pkg/processor/security-context/container_security_context.go create mode 100644 pkg/processor/security-context/container_security_context_test.go diff --git a/examples/app/templates/deployment.yaml b/examples/app/templates/deployment.yaml index 03d4703..8e31a1c 100644 --- a/examples/app/templates/deployment.yaml +++ b/examples/app/templates/deployment.yaml @@ -53,8 +53,8 @@ spec: initialDelaySeconds: 5 periodSeconds: 10 resources: {{- toYaml .Values.myapp.app.resources | nindent 10 }} - securityContext: - allowPrivilegeEscalation: false + securityContext: {{- toYaml .Values.myapp.app.containerSecurityContext | nindent + 8 }} volumeMounts: - mountPath: /my_config.yaml name: manager-config diff --git a/examples/app/values.yaml b/examples/app/values.yaml index 2f06243..b0f92da 100644 --- a/examples/app/values.yaml +++ b/examples/app/values.yaml @@ -43,6 +43,8 @@ mySecretVars: var2: "" myapp: app: + containerSecurityContext: + allowPrivilegeEscalation: false image: repository: controller tag: latest diff --git a/examples/operator/templates/deployment.yaml b/examples/operator/templates/deployment.yaml index b943293..1ec487f 100644 --- a/examples/operator/templates/deployment.yaml +++ b/examples/operator/templates/deployment.yaml @@ -90,8 +90,8 @@ spec: periodSeconds: 10 resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10 }} - securityContext: - allowPrivilegeEscalation: false + securityContext: {{- toYaml .Values.controllerManager.manager.containerSecurityContext + | nindent 8 }} volumeMounts: - mountPath: /controller_manager_config.yaml name: manager-config diff --git a/examples/operator/values.yaml b/examples/operator/values.yaml index 3a9d17e..c40b651 100644 --- a/examples/operator/values.yaml +++ b/examples/operator/values.yaml @@ -6,6 +6,17 @@ controllerManager: repository: gcr.io/kubebuilder/kube-rbac-proxy tag: v0.8.0 manager: + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault env: var2: ciao var3MyEnv: ciao diff --git a/pkg/processor/deployment/deployment.go b/pkg/processor/deployment/deployment.go index 09bf082..d2603e3 100644 --- a/pkg/processor/deployment/deployment.go +++ b/pkg/processor/deployment/deployment.go @@ -163,6 +163,8 @@ func (d deployment) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstr imagePullSecrets.ProcessSpecMap(specMap, &values) } + securityContext.ProcessContainerSecurityContext(nameCamel, specMap, &values) + // process nodeSelector if presented: if len(depl.Spec.Template.Spec.NodeSelector) != 0 { err = unstructured.SetNestedField(specMap, fmt.Sprintf(`{{- toYaml .Values.%s.nodeSelector | nindent 8 }}`, nameCamel), "nodeSelector") @@ -179,6 +181,7 @@ func (d deployment) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstr if err != nil { return true, nil, err } + spec = strings.ReplaceAll(spec, "'", "") return true, &result{ diff --git a/pkg/processor/imagePullSecrets/imagePullSecrets.go b/pkg/processor/imagePullSecrets/imagePullSecrets.go index 0278dd5..07fb792 100644 --- a/pkg/processor/imagePullSecrets/imagePullSecrets.go +++ b/pkg/processor/imagePullSecrets/imagePullSecrets.go @@ -12,5 +12,4 @@ func ProcessSpecMap(specMap map[string]interface{}, values *helmify.Values) { specMap["imagePullSecrets"] = helmExpression (*values)["imagePullSecrets"] = []string{} } - } diff --git a/pkg/processor/security-context/container_security_context.go b/pkg/processor/security-context/container_security_context.go new file mode 100644 index 0000000..2d651e7 --- /dev/null +++ b/pkg/processor/security-context/container_security_context.go @@ -0,0 +1,40 @@ +package security_context + +import ( + "fmt" + + "github.com/arttor/helmify/pkg/helmify" + "github.com/iancoleman/strcase" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +const ( + sc = "securityContext" + cscValueName = "containerSecurityContext" + helmTemplate = "{{- toYaml .Values.%[1]s.%[2]s.containerSecurityContext | nindent 8 }}" +) + +// ProcessContainerSecurityContext adds 'securityContext' to the podSpec in specMap, if it doesn't have one already defined. +func ProcessContainerSecurityContext(nameCamel string, specMap map[string]interface{}, values *helmify.Values) { + if _, defined := specMap["containers"]; defined { + containers, _, _ := unstructured.NestedSlice(specMap, "containers") + for _, container := range containers { + castedContainer := container.(map[string]interface{}) + containerName := strcase.ToLowerCamel(castedContainer["name"].(string)) + if _, defined2 := castedContainer["securityContext"]; defined2 { + setSecContextValue(nameCamel, containerName, castedContainer, values) + } + } + unstructured.SetNestedSlice(specMap, containers, "containers") + } +} + +func setSecContextValue(resourceName string, containerName string, castedContainer map[string]interface{}, values *helmify.Values) { + if castedContainer["securityContext"] != nil { + unstructured.SetNestedField(*values, castedContainer["securityContext"], resourceName, containerName, cscValueName) + + valueString := fmt.Sprintf(helmTemplate, resourceName, containerName) + + unstructured.SetNestedField(castedContainer, valueString, sc) + } +} diff --git a/pkg/processor/security-context/container_security_context_test.go b/pkg/processor/security-context/container_security_context_test.go new file mode 100644 index 0000000..8f072f3 --- /dev/null +++ b/pkg/processor/security-context/container_security_context_test.go @@ -0,0 +1,147 @@ +package security_context + +import ( + "testing" + + "github.com/arttor/helmify/pkg/helmify" + "github.com/stretchr/testify/assert" +) + +func TestProcessContainerSecurityContext(t *testing.T) { + type args struct { + nameCamel string + specMap map[string]interface{} + values *helmify.Values + } + tests := []struct { + name string + args args + want *helmify.Values + }{ + { + name: "test with empty specMap", + args: args{ + nameCamel: "someResourceName", + specMap: map[string]interface{}{}, + values: &helmify.Values{}, + }, + want: &helmify.Values{}, + }, + { + name: "test with single container", + args: args{ + nameCamel: "someResourceName", + specMap: map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "SomeContainerName", + "securityContext": map[string]interface{}{ + "privileged": true, + }, + }, + }, + }, + values: &helmify.Values{}, + }, + want: &helmify.Values{ + "someResourceName": map[string]interface{}{ + "someContainerName": map[string]interface{}{ + "containerSecurityContext": map[string]interface{}{ + "privileged": true, + }, + }, + }, + }, + }, + { + name: "test with multiple containers", + args: args{ + nameCamel: "someResourceName", + specMap: map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "FirstContainer", + "securityContext": map[string]interface{}{ + "privileged": true, + }, + }, + map[string]interface{}{ + "name": "SecondContainer", + "securityContext": map[string]interface{}{ + "allowPrivilegeEscalation": true, + }, + }, + }, + }, + values: &helmify.Values{}, + }, + want: &helmify.Values{ + "someResourceName": map[string]interface{}{ + "firstContainer": map[string]interface{}{ + "containerSecurityContext": map[string]interface{}{ + "privileged": true, + }, + }, + "secondContainer": map[string]interface{}{ + "containerSecurityContext": map[string]interface{}{ + "allowPrivilegeEscalation": true, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ProcessContainerSecurityContext(tt.args.nameCamel, tt.args.specMap, tt.args.values) + assert.Equal(t, tt.want, tt.args.values) + }) + } +} + +func Test_setSecContextValue(t *testing.T) { + type args struct { + resourceName string + containerName string + castedContainer map[string]interface{} + values *helmify.Values + fieldName string + useRenderedHelmTemplate bool + } + tests := []struct { + name string + args args + want *helmify.Values + }{ + { + name: "simple test with single container and single value", + args: args{ + resourceName: "someResource", + containerName: "someContainer", + castedContainer: map[string]interface{}{ + "securityContext": map[string]interface{}{ + "someField": "someValue", + }, + }, + values: &helmify.Values{}, + fieldName: "someField", + useRenderedHelmTemplate: false, + }, + want: &helmify.Values{ + "someResource": map[string]interface{}{ + "someContainer": map[string]interface{}{ + "containerSecurityContext": map[string]interface{}{ + "someField": "someValue", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setSecContextValue(tt.args.resourceName, tt.args.containerName, tt.args.castedContainer, tt.args.values) + assert.Equal(t, tt.want, tt.args.values) + }) + } +} diff --git a/test_data/k8s-operator-kustomize.output b/test_data/k8s-operator-kustomize.output index 782e44e..5d23fb5 100644 --- a/test_data/k8s-operator-kustomize.output +++ b/test_data/k8s-operator-kustomize.output @@ -665,6 +665,15 @@ spec: memory: 20Mi securityContext: allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault securityContext: runAsNonRoot: true nodeSelector: