From 9042ebfb9bb1e18f6d4afc3c7182848e3a488094 Mon Sep 17 00:00:00 2001 From: Stavros kois Date: Wed, 8 Feb 2023 18:52:15 +0200 Subject: [PATCH] podsec context with some todo's to check --- .../common-test/tests/pod/hostname_test.yaml | 2 +- .../tests/pod/securityContext.yaml | 236 ++++++++++++++++++ library/common/1.0.0/docs/workload.md | 81 +++--- .../templates/lib/pod/_podSecurityContext.tpl | 19 +- .../1.0.0/templates/lib/workload/_pod.tpl | 2 +- library/common/1.0.0/values.yaml | 2 +- 6 files changed, 297 insertions(+), 45 deletions(-) create mode 100644 library/common-test/tests/pod/securityContext.yaml diff --git a/library/common-test/tests/pod/hostname_test.yaml b/library/common-test/tests/pod/hostname_test.yaml index 5ede7743aa..0e8e5cac4d 100644 --- a/library/common-test/tests/pod/hostname_test.yaml +++ b/library/common-test/tests/pod/hostname_test.yaml @@ -1,4 +1,4 @@ -suite: pod hostname name test +suite: pod hostname test templates: - common.yaml tests: diff --git a/library/common-test/tests/pod/securityContext.yaml b/library/common-test/tests/pod/securityContext.yaml new file mode 100644 index 0000000000..9b9ec45a19 --- /dev/null +++ b/library/common-test/tests/pod/securityContext.yaml @@ -0,0 +1,236 @@ +suite: pod securityContext test +templates: + - common.yaml +tests: + - it: should pass with securityContext from "global" + set: + securityContext: + pod: + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + supplementalGroups: + - 1000 + - 1001 + sysctls: + - name: net.ipv4.ip_forward + value: "1" + - name: net.ipv4.ping_group_range + value: "0 65535" + workload: + workload-name1: + enabled: true + primary: true + type: StatefulSet + podSpec: {} + asserts: + - documentIndex: &statefulSetDoc 0 + isKind: + of: StatefulSet + - documentIndex: *statefulSetDoc + equal: + path: spec.template.spec.securityContext + value: + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + supplementalGroups: + - 1000 + - 1001 + sysctls: + - name: net.ipv4.ip_forward + value: "1" + - name: net.ipv4.ping_group_range + value: "0 65535" + + - it: should pass with securityContext from "global" and partial override with "pod" + set: + securityContext: + pod: + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + supplementalGroups: + - 1000 + - 1001 + sysctls: + - name: net.ipv4.ip_forward + value: "1" + - name: net.ipv4.ping_group_range + value: "0 65535" + workload: + workload-name1: + enabled: true + primary: true + type: StatefulSet + podSpec: + securityContext: + fsGroup: 1001 + asserts: + - documentIndex: &statefulSetDoc 0 + isKind: + of: StatefulSet + - documentIndex: *statefulSetDoc + equal: + path: spec.template.spec.securityContext + value: + fsGroup: 1001 + fsGroupChangePolicy: OnRootMismatch + supplementalGroups: + - 1000 + - 1001 + sysctls: + - name: net.ipv4.ip_forward + value: "1" + - name: net.ipv4.ping_group_range + value: "0 65535" + + - it: should pass with securityContext from "global" and full override with "pod" + set: + some_sysctl_name: net.ipv4.ip_forward + some_sysctl_value: 2 + securityContext: + pod: + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + supplementalGroups: + - 1000 + - 1001 + sysctls: + - name: net.ipv4.ip_forward + value: "1" + - name: net.ipv4.ping_group_range + value: "0 65535" + workload: + workload-name1: + enabled: true + primary: true + type: StatefulSet + podSpec: + securityContext: + fsGroup: 1001 + fsGroupChangePolicy: Always + supplementalGroups: + - 1002 + - 1003 + sysctls: + - name: "{{ .Values.some_sysctl_name }}" + value: "{{ .Values.some_sysctl_value }}" + - name: net.ipv4.ping_group_range + value: "1 65534" + asserts: + - documentIndex: &statefulSetDoc 0 + isKind: + of: StatefulSet + - documentIndex: *statefulSetDoc + equal: + path: spec.template.spec.securityContext + value: + fsGroup: 1001 + fsGroupChangePolicy: Always + supplementalGroups: + - 1002 + - 1003 + sysctls: + - name: net.ipv4.ip_forward + value: "2" + - name: net.ipv4.ping_group_range + value: "1 65534" + +# Failures + + - it: should fail with empty securityContext from "global" + set: + securityContext: + pod: null + workload: + workload-name1: + enabled: true + primary: true + type: StatefulSet + podSpec: {} + asserts: + - failedTemplate: + errorMessage: Pod - Expected non-empty <.Values.securityContext.pod> + + - it: should fail with empty fsGroup + set: + securityContext: + pod: + fsGroup: "" + workload: + workload-name1: + enabled: true + primary: true + type: StatefulSet + podSpec: {} + asserts: + - failedTemplate: + errorMessage: Pod - Expected non-empty + + - it: should fail with empty fsGroupChangePolicy + set: + securityContext: + pod: + fsGroup: 568 + fsGroupChangePolicy: "" + workload: + workload-name1: + enabled: true + primary: true + type: StatefulSet + podSpec: {} + asserts: + - failedTemplate: + errorMessage: Pod - Expected non-empty + + - it: should fail with invalid fsGroupChangePolicy + set: + securityContext: + pod: + fsGroup: 568 + fsGroupChangePolicy: invalid + workload: + workload-name1: + enabled: true + primary: true + type: StatefulSet + podSpec: {} + asserts: + - failedTemplate: + errorMessage: Pod - Expected to be one of [Always, OnRootMismatch], but got [invalid] + + - it: should fail with empty name in sysctls + set: + securityContext: + pod: + fsGroup: 568 + fsGroupChangePolicy: OnRootMismatch + sysctls: + - name: "" + value: "1" + workload: + workload-name1: + enabled: true + primary: true + type: StatefulSet + podSpec: {} + asserts: + - failedTemplate: + errorMessage: Pod - Expected non-empty in + + - it: should fail with empty value in sysctls + set: + securityContext: + pod: + fsGroup: 568 + fsGroupChangePolicy: OnRootMismatch + sysctls: + - name: net.ipv4.ip_forward + value: "" + workload: + workload-name1: + enabled: true + primary: true + type: StatefulSet + podSpec: {} + asserts: + - failedTemplate: + errorMessage: Pod - Expected non-empty in diff --git a/library/common/1.0.0/docs/workload.md b/library/common/1.0.0/docs/workload.md index 433563c76c..4e3b04829c 100644 --- a/library/common/1.0.0/docs/workload.md +++ b/library/common/1.0.0/docs/workload.md @@ -1,38 +1,45 @@ # workload -| Key | Type | Required | Helm Template | Default | Description | -| :----------------------------------------------------------------- | :-------: | :------: | :----------------: | :-------------------------------------------------------------: | :--------------------------------------------------------------------------------- | -| workload | `dict` | ❌ | ❌ | `{}` | Define the workload as dicts | -| workload.[workload-name] | `dict` | ✅ | ❌ | `{}` | Holds workload definition | -| workload.[workload-name].enabled | `boolean` | ✅ | ❌ | `false` | Enables or Disables the workload | -| workload.[workload-name].primary | `boolean` | ✅ | ❌ | `false` | Sets the workload as primary | -| workload.[workload-name].labels | `dict` | ❌ | ✅ (On value only) | `{}` | Additional labels for workload | -| workload.[workload-name].annotations | `dict` | ❌ | ✅ (On value only) | `{}` | Additional annotations for workload | -| workload.[workload-name].type | `string` | ✅ | ❌ | `""` | Define the kind of the workload (Deployment, DaemonSet, StatefulSet, CronJob, Job) | -| workload.[workload-name].podSpec | `dict` | ✅ | ❌ | `{}` | Holds the pod definition | -| workload.[workload-name].podSpec.labels | `dict` | ❌ | ✅ (On value only) | `{}` | Additional Pod Labels | -| workload.[workload-name].podSpec.annotations | `dict` | ❌ | ✅ (On value only) | `{}` | Pod Annotations | -| workload.[workload-name].podSpec.automountServiceAccountToken | `boolean` | ❌ | ❌ | `{{ .Values.podOptions.automountServiceAccoutnToken }}` (false) | Pod's automountServiceAccountToken | -| workload.[workload-name].podSpec.hostNetwork | `boolean` | ❌ | ❌ | `{{ .Values.podOptions.hostNetwork }}` (false) | Pod's hostNetwork | -| workload.[workload-name].podSpec.enableServiceLinks | `boolean` | ❌ | ❌ | `{{ .Values.podOptions.enableServiceLinks }}` (false) | Pod's enableServiceLinks | -| workload.[workload-name].podSpec.restartPolicy | `string` | ❌ | ✅ | `{{ .Values.podOptions.restartPolicy }}` (Always) | Pod's restartPolicy. (Always, Never, OnFailure) | -| workload.[workload-name].podSpec.schedulerName | `string` | ❌ | ✅ | `{{ .Values.podOptions.schedulerName }}` ("") | Pod's schedulerName | -| workload.[workload-name].podSpec.priorityClassName | `string` | ❌ | ✅ | `{{ .Values.podOptions.priorityClassName }}` ("") | Pod's priorityClassName | -| workload.[workload-name].podSpec.hostname | `string` | ❌ | ✅ | `""` | Pod's hostname | -| workload.[workload-name].podSpec.terminationGracePeriodSeconds | `int` | ❌ | ✅ | `{{ .Values.podOptions.terminationGracePeriodSeconds }}` (120) | Pod's terminationGracePeriodSeconds | -| workload.[workload-name].podSpec.nodeSelector | `dict` | ❌ | ✅ (On value only) | `{{ .Values.podOptions.nodeSelector }}` ({}) | Pod's nodeSelector | -| workload.[workload-name].podSpec.hostAliases | `list` | ❌ | ❌ | | Pod's host aliases | -| workload.[workload-name].podSpec.hostAliases.ip | `string` | ❌ | ✅ | | Value for `ip` in hosts aliases | -| workload.[workload-name].podSpec.hostAliases.hostnames | `list` | ❌ | ❌ | | Hostnames for the `ip` in hosts aliases | -| workload.[workload-name].podSpec.hostAliases.hostnames.[host-name] | `string` | ❌ | ✅ | | [Value] for `hostnames` for the `ip` in hosts aliases | -| workload.[workload-name].podSpec.dnsPolicy | `string` | ❌ | ✅ | `{{ .Values.podOptions.dnsPolicy }}` (ClusterFirst) | Pod's DNS Policy (ClusterFirst, ClusterFirstWithHostNet, Default, None). | -| workload.[workload-name].podSpec.tolerations | `list` | ❌ | ❌ | `{{ .Values.podOptions.tolerations }}` ([]) | Pod's Tolerations | -| workload.[workload-name].podSpec.tolerations.operator | `string` | ✅ | ✅ | | Toleration's `operator` (Equal, Exists) | -| workload.[workload-name].podSpec.tolerations.key | `string` | ❌/✅ | ✅ | | Toleration's `key`. Required only when `operator` = `Equal` | -| workload.[workload-name].podSpec.tolerations.value | `string` | ❌/✅ | ✅ | | Toleration's `value`. Required only when `operator` = `Equal` | -| workload.[workload-name].podSpec.tolerations.effect | `string` | ❌ | ✅ | | Toleration's `effect`.(NoExecute, NoSchedule, PreferNoSchedule) | -| workload.[workload-name].podSpec.tolerations.tolerationSeconds | `int` | ❌ | ❌ | | Toleration's `tolerationSeconds`. | -| workload.[workload-name].podSpec.runtimeClassName | `string` | ✅ | ❌ | `{{ .Values.podOptions.runtimeClassName }}` ("") | Pod's runtimeClassName | +| Key | Type | Required | Helm Template | Default | Description | +| :------------------------------------------------------------------- | :-------: | :------: | :----------------: | :-------------------------------------------------------------: | :--------------------------------------------------------------------------------- | +| workload | `dict` | ❌ | ❌ | `{}` | Define the workload as dicts | +| workload.[workload-name] | `dict` | ✅ | ❌ | `{}` | Holds workload definition | +| workload.[workload-name].enabled | `boolean` | ✅ | ❌ | `false` | Enables or Disables the workload | +| workload.[workload-name].primary | `boolean` | ✅ | ❌ | `false` | Sets the workload as primary | +| workload.[workload-name].labels | `dict` | ❌ | ✅ (On value only) | `{}` | Additional labels for workload | +| workload.[workload-name].annotations | `dict` | ❌ | ✅ (On value only) | `{}` | Additional annotations for workload | +| workload.[workload-name].type | `string` | ✅ | ❌ | `""` | Define the kind of the workload (Deployment, DaemonSet, StatefulSet, CronJob, Job) | +| workload.[workload-name].podSpec | `dict` | ✅ | ❌ | `{}` | Holds the pod definition | +| workload.[workload-name].podSpec.labels | `dict` | ❌ | ✅ (On value only) | `{}` | Additional Pod Labels | +| workload.[workload-name].podSpec.annotations | `dict` | ❌ | ✅ (On value only) | `{}` | Pod Annotations | +| workload.[workload-name].podSpec.automountServiceAccountToken | `boolean` | ❌ | ❌ | `{{ .Values.podOptions.automountServiceAccoutnToken }}` (false) | Pod's automountServiceAccountToken | +| workload.[workload-name].podSpec.hostNetwork | `boolean` | ❌ | ❌ | `{{ .Values.podOptions.hostNetwork }}` (false) | Pod's hostNetwork | +| workload.[workload-name].podSpec.enableServiceLinks | `boolean` | ❌ | ❌ | `{{ .Values.podOptions.enableServiceLinks }}` (false) | Pod's enableServiceLinks | +| workload.[workload-name].podSpec.restartPolicy | `string` | ❌ | ✅ | `{{ .Values.podOptions.restartPolicy }}` (Always) | Pod's restartPolicy. (Always, Never, OnFailure) | +| workload.[workload-name].podSpec.schedulerName | `string` | ❌ | ✅ | `{{ .Values.podOptions.schedulerName }}` ("") | Pod's schedulerName | +| workload.[workload-name].podSpec.priorityClassName | `string` | ❌ | ✅ | `{{ .Values.podOptions.priorityClassName }}` ("") | Pod's priorityClassName | +| workload.[workload-name].podSpec.hostname | `string` | ❌ | ✅ | `""` | Pod's hostname | +| workload.[workload-name].podSpec.terminationGracePeriodSeconds | `int` | ❌ | ✅ | `{{ .Values.podOptions.terminationGracePeriodSeconds }}` (120) | Pod's terminationGracePeriodSeconds | +| workload.[workload-name].podSpec.nodeSelector | `dict` | ❌ | ✅ (On value only) | `{{ .Values.podOptions.nodeSelector }}` ({}) | Pod's nodeSelector | +| workload.[workload-name].podSpec.hostAliases | `list` | ❌ | ❌ | | Pod's host aliases | +| workload.[workload-name].podSpec.hostAliases.ip | `string` | ❌ | ✅ | | Value for `ip` in hosts aliases | +| workload.[workload-name].podSpec.hostAliases.hostnames | `list` | ❌ | ❌ | | Hostnames for the `ip` in hosts aliases | +| workload.[workload-name].podSpec.hostAliases.hostnames.[host-name] | `string` | ❌ | ✅ | | [Value] for `hostnames` for the `ip` in hosts aliases | +| workload.[workload-name].podSpec.dnsPolicy | `string` | ❌ | ✅ | `{{ .Values.podOptions.dnsPolicy }}` (ClusterFirst) | Pod's DNS Policy (ClusterFirst, ClusterFirstWithHostNet, Default, None). | +| workload.[workload-name].podSpec.tolerations | `list` | ❌ | ❌ | `{{ .Values.podOptions.tolerations }}` ([]) | Pod's Tolerations | +| workload.[workload-name].podSpec.tolerations.operator | `string` | ✅ | ✅ | | Toleration's `operator` (Equal, Exists) | +| workload.[workload-name].podSpec.tolerations.key | `string` | ❌/✅ | ✅ | | Toleration's `key`. Required only when `operator` = `Equal` | +| workload.[workload-name].podSpec.tolerations.value | `string` | ❌/✅ | ✅ | | Toleration's `value`. Required only when `operator` = `Equal` | +| workload.[workload-name].podSpec.tolerations.effect | `string` | ❌ | ✅ | | Toleration's `effect`.(NoExecute, NoSchedule, PreferNoSchedule) | +| workload.[workload-name].podSpec.tolerations.tolerationSeconds | `int` | ❌ | ❌ | | Toleration's `tolerationSeconds`. | +| workload.[workload-name].podSpec.runtimeClassName | `string` | ❌ | ✅ | `{{ .Values.podOptions.runtimeClassName }}` ("") | Pod's runtimeClassName | +| workload.[workload-name].podSpec.securityContext | `dict` | ❌ | ❌ | `{{ .Values.securityContext.pod }}` | Pod's securityContext | +| workload.[workload-name].podSpec.securityContext.fsGroup | `int` | ❌ | ❌ | `568` | Pod's fsGroup | +| workload.[workload-name].podSpec.securityContext.fsGroupChangePolicy | `string` | ❌ | ❌ | `OnRootMismatch` | Pod's fsGroupChangePolicy (Always, OnRootMismatch) | +| workload.[workload-name].podSpec.securityContext.supplementalGroups | `list` | ❌ | ❌ | `[]` | Pod's supplementalGroups (list of `int`) | +| workload.[workload-name].podSpec.securityContext.sysctls | `list` | ❌ | ❌ | `[]` | Pod's sysctls | +| workload.[workload-name].podSpec.securityContext.sysctls.name | `string` | ✅ | ✅ | `""` | `name` of the sysctl | +| workload.[workload-name].podSpec.securityContext.sysctls.value | `string` | ✅ | ✅ | `""` | `value` of the sysctl | --- @@ -119,4 +126,12 @@ workload: effect: NoExecute tolerationSeconds: 3600 runtimeClassName: some-runtime-class + securityContext: + fsGroup: 568 + fsGroupChangePolicy: OnRootMismatch + supplementalGroups: + - 568 + sysctls: + - name: net.ipv4.ip_local_port_range + value: 1024 65535 ``` diff --git a/library/common/1.0.0/templates/lib/pod/_podSecurityContext.tpl b/library/common/1.0.0/templates/lib/pod/_podSecurityContext.tpl index 928d3c84cb..25556d7af9 100644 --- a/library/common/1.0.0/templates/lib/pod/_podSecurityContext.tpl +++ b/library/common/1.0.0/templates/lib/pod/_podSecurityContext.tpl @@ -8,12 +8,12 @@ objectData: The object data to be used to render the Pod. {{- $rootCtx := .rootCtx -}} {{- $objectData := .objectData -}} - {{- $secContext := dict -}} + {{- if not $rootCtx.Values.securityContext.pod -}} + {{- fail "Pod - Expected non-empty <.Values.securityContext.pod>" -}} + {{- end -}} {{/* Initialize from the "global" option */}} - {{- with $rootCtx.Values.securityContext.pod -}} - {{- $secContext = (mustDeepCopy .) -}} - {{- end -}} + {{- $secContext := mustDeepCopy $rootCtx.Values.securityContext.pod -}} {{/* Override with pod's option */}} {{- with $objectData.podSpec.securityContext -}} @@ -26,6 +26,7 @@ objectData: The object data to be used to render the Pod. {{/* TODO: Add sysctls net.ipv4.ip_unprivileged_port_start: (Set to the lowest port on the pod's containers) net.ipv4.ping_group_range: (Set to the lowest port and highest port on the pod's containers) + TODO: Unit Test the above cases */}} {{- if not $secContext.fsGroup -}} @@ -52,15 +53,15 @@ supplementalGroups: [] {{- end -}} {{- with $secContext.sysctls }} sysctls: - {{- range $name, $value := . }} - {{- if not $name -}} + {{- range . }} + {{- if not .name -}} {{- fail "Pod - Expected non-empty in " -}} {{- end -}} - {{- if not $value -}} + {{- if not .value -}} {{- fail "Pod - Expected non-empty in " -}} {{- end }} - - name: {{ $name }} - value: {{ $value }} + - name: {{ tpl .name $rootCtx | quote }} + value: {{ tpl .value $rootCtx | quote }} {{- end -}} {{- else }} sysctls: [] diff --git a/library/common/1.0.0/templates/lib/workload/_pod.tpl b/library/common/1.0.0/templates/lib/workload/_pod.tpl index e2b4da03e7..553de11445 100644 --- a/library/common/1.0.0/templates/lib/workload/_pod.tpl +++ b/library/common/1.0.0/templates/lib/workload/_pod.tpl @@ -42,7 +42,7 @@ terminationGracePeriodSeconds: {{ . }} tolerations: {{- . | nindent 2 }} {{- end }} -securityContext: {{/* TODO: Unit Tests */}} +securityContext: {{- include "ix.v1.common.lib.pod.securityContext" (dict "rootCtx" $rootCtx "objectData" $objectData) | trim | nindent 2 }} #TODO:containers: #TODO:initContainers: diff --git a/library/common/1.0.0/values.yaml b/library/common/1.0.0/values.yaml index dcda15aff0..22ea0feb68 100644 --- a/library/common/1.0.0/values.yaml +++ b/library/common/1.0.0/values.yaml @@ -75,7 +75,7 @@ securityContext: fsGroup: 568 fsGroupChangePolicy: OnRootMismatch supplementalGroups: [] - sysctls: {} + sysctls: [] # -- Resources # Can be overruled per container