diff --git a/examples/app/templates/daemonset.yaml b/examples/app/templates/daemonset.yaml index 1bc996a..d1e1c39 100644 --- a/examples/app/templates/daemonset.yaml +++ b/examples/app/templates/daemonset.yaml @@ -19,7 +19,7 @@ spec: containers: - env: - name: KUBERNETES_CLUSTER_DOMAIN - value: {{ .Values.kubernetesClusterDomain }} + value: {{ quote .Values.kubernetesClusterDomain }} image: {{ .Values.fluentdElasticsearch.fluentdElasticsearch.image.repository }}:{{ .Values.fluentdElasticsearch.fluentdElasticsearch.image.tag | default .Chart.AppVersion }} diff --git a/examples/app/templates/deployment.yaml b/examples/app/templates/deployment.yaml index 03d4703..625dd90 100644 --- a/examples/app/templates/deployment.yaml +++ b/examples/app/templates/deployment.yaml @@ -36,7 +36,7 @@ spec: key: VAR2 name: {{ include "app.fullname" . }}-my-secret-vars - name: KUBERNETES_CLUSTER_DOMAIN - value: {{ .Values.kubernetesClusterDomain }} + value: {{ quote .Values.kubernetesClusterDomain }} image: {{ .Values.myapp.app.image.repository }}:{{ .Values.myapp.app.image.tag | default .Chart.AppVersion }} livenessProbe: @@ -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 @@ -73,7 +73,7 @@ spec: - --v=10 env: - name: KUBERNETES_CLUSTER_DOMAIN - value: {{ .Values.kubernetesClusterDomain }} + value: {{ quote .Values.kubernetesClusterDomain }} image: {{ .Values.myapp.proxySidecar.image.repository }}:{{ .Values.myapp.proxySidecar.image.tag | default .Chart.AppVersion }} name: proxy-sidecar 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..ed13b5f 100644 --- a/examples/operator/templates/deployment.yaml +++ b/examples/operator/templates/deployment.yaml @@ -32,7 +32,7 @@ spec: - --v=10 env: - name: KUBERNETES_CLUSTER_DOMAIN - value: {{ .Values.kubernetesClusterDomain }} + value: {{ quote .Values.kubernetesClusterDomain }} image: {{ .Values.controllerManager.kubeRbacProxy.image.repository }}:{{ .Values.controllerManager.kubeRbacProxy.image.tag | default .Chart.AppVersion }} name: kube-rbac-proxy @@ -53,9 +53,9 @@ spec: key: VAR1 name: {{ include "operator.fullname" . }}-secret-vars - name: VAR2 - value: {{ .Values.controllerManager.manager.env.var2 }} + value: {{ quote .Values.controllerManager.manager.env.var2 }} - name: VAR3_MY_ENV - value: {{ .Values.controllerManager.manager.env.var3MyEnv }} + value: {{ quote .Values.controllerManager.manager.env.var3MyEnv }} - name: VAR4 valueFrom: configMapKeyRef: @@ -71,7 +71,7 @@ spec: divisor: "0" resource: limits.cpu - name: KUBERNETES_CLUSTER_DOMAIN - value: {{ .Values.kubernetesClusterDomain }} + value: {{ quote .Values.kubernetesClusterDomain }} image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag | default .Chart.AppVersion }} imagePullPolicy: {{ .Values.controllerManager.manager.imagePullPolicy }} @@ -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..85c75ca 100644 --- a/pkg/processor/deployment/deployment.go +++ b/pkg/processor/deployment/deployment.go @@ -9,6 +9,7 @@ import ( "github.com/arttor/helmify/pkg/cluster" "github.com/arttor/helmify/pkg/processor" "github.com/arttor/helmify/pkg/processor/imagePullSecrets" + securityContext "github.com/arttor/helmify/pkg/processor/security-context" "github.com/arttor/helmify/pkg/helmify" yamlformat "github.com/arttor/helmify/pkg/yaml" @@ -163,6 +164,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 +182,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: