From 7fc8e2e15124c060a0bd08b4018e0b66cf9f7c5d Mon Sep 17 00:00:00 2001 From: Ravi Singal <62086374+ravisingal@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:57:56 +0530 Subject: [PATCH] chore: add steps to validate pod templates (#59) * chore: add steps to validate pod templates * remove pip cache * add debug stmt * use action_path * rename values file * update with sub-charts values --- validate-charts/action.yml | 23 ++++++ validate-charts/check.py | 127 ++++++++++++++++++++++++++++++++ validate-charts/values.tpl | 146 +++++++++++++++++++++++++++++++++++++ 3 files changed, 296 insertions(+) create mode 100644 validate-charts/check.py create mode 100644 validate-charts/values.tpl diff --git a/validate-charts/action.yml b/validate-charts/action.yml index 393a6f1..9c84965 100644 --- a/validate-charts/action.yml +++ b/validate-charts/action.yml @@ -13,6 +13,10 @@ inputs: description: 'Check no trailing spaces in helm template' required: false default: 'true' + check-pod-template: + description: 'Check labels, annotations and security context in a pod' + required: false + default: 'true' runs: using: "composite" steps: @@ -34,3 +38,22 @@ runs: echo $output exit 1 fi + + - name: Setup python + if: inputs.check-pod-template == 'true' + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install pyyaml + if: inputs.check-pod-template == 'true' + shell: bash + run: pip install pyyaml + + - name: Check pod template + if: inputs.check-pod-template == 'true' + shell: bash + run: | + CHART_NAME=$(awk '/^name:/ {print $2}' ${{ inputs.chart-path }}/Chart.yaml) + helm template $CHART_NAME ${{ inputs.chart-path }} ${{ inputs.extra-args }} -f ${{ github.action_path }}/values.tpl > /tmp/${CHART_NAME}.yaml + python ${{ github.action_path }}/check.py /tmp/${CHART_NAME}.yaml diff --git a/validate-charts/check.py b/validate-charts/check.py new file mode 100644 index 0000000..81c5ced --- /dev/null +++ b/validate-charts/check.py @@ -0,0 +1,127 @@ +import yaml +import sys + +def check_pod_metadata(kind, name, template): + if not 'metadata' in template: + print(f'"metadata" is missing from {kind}/{name} template.') + return False + if not 'labels' in template['metadata']: + print(f'"labels" is missing from {kind}/{name} metadata.') + return False + if not 'applicationid' in template['metadata']['labels']: + print(f'commonPodLabels is missing from {kind}/{name} metadata.') + return False + if template['metadata']['labels']['applicationid'] != 'ABCD1234': + print(f'commonPodLabels is not matching {kind}/{name} metadata.') + return False + if not 'annotations' in template['metadata']: + print(f'"annotations" is missing from {kind}/{name} metadata.') + return False + if not 'traceable.ai/test' in template['metadata']['annotations']: + print(f'commonPodAnnotations is missing from {kind}/{name} metadata.') + return False + if template['metadata']['annotations']['traceable.ai/test'] != '1234567890': + print(f'commonPodAnnotations is not matching {kind}/{name} metadata.') + return False + return True + +def check_pod_security_context(kind, name, template): + spec = template['spec'] + if not 'securityContext' in spec: + print(f'Pod security context is missing from {kind}/{name} spec.') + return False + security_context = spec['securityContext'] + # runAsUser + if not 'runAsUser' in security_context: + print(f'"runAsUser" is missing from {kind}/{name} pod security context.') + return False + if security_context['runAsUser'] != 55555: + print(f'"runAsUser" is not matching in {kind}/{name} pod security context.') + return False + # fsGroup + if not 'fsGroup' in security_context: + print(f'"fsGroup" is missing from {kind}/{name} pod security context.') + return False + if security_context['fsGroup'] != 44444: + print(f'"fsGroup" is not matching in {kind}/{name} pod security context.') + return False + # runAsNonRoot + if not 'runAsNonRoot' in security_context: + print(f'"runAsNonRoot" is missing from {kind}/{name} pod security context.') + return False + if security_context['runAsNonRoot'] != True: + print(f'"runAsNonRoot" is not matching in {kind}/{name} pod security context.') + return False + return True + +def check_container_security_context(kind, name, container): + container_name = container['name'] + if not 'securityContext' in container: + print(f'Container security context is missing from {kind}/{name}/{container_name} spec.') + return False + security_context = container['securityContext'] + # runAsUser + if not 'runAsUser' in security_context: + print(f'"runAsUser" is missing from {kind}/{name}/{container_name} container security context.') + return False + if security_context['runAsUser'] != 22222: + print(f'"runAsUser" is not matching in {kind}/{name}/{container_name} container security context.') + return False + # runAsGroup + if not 'runAsGroup' in security_context: + print(f'"runAsGroup" is missing from {kind}/{name}/{container_name} container security context.') + return False + if security_context['runAsGroup'] != 333333: + print(f'"runAsGroup" is not matching in {kind}/{name}/{container_name} container security context.') + return False + # runAsNonRoot + if not 'runAsNonRoot' in security_context: + print(f'"runAsNonRoot" is missing from {kind}/{name}/{container_name} container security context.') + return False + if security_context['runAsNonRoot'] != True: + print(f'"runAsNonRoot" is not matching in {kind}/{name}/{container_name} container security context.') + return False + # allowPrivilegeEscalation + if not 'allowPrivilegeEscalation' in security_context: + print(f'"allowPrivilegeEscalation" is missing from {kind}/{name}/{container_name} container security context.') + return False + if security_context['allowPrivilegeEscalation'] != False: + print(f'"allowPrivilegeEscalation" is not matching in {kind}/{name}/{container_name} container security context.') + return False + return True + +def check_helm_template(doc): + validated = True + kind = doc['kind'] + name = doc['metadata']['name'] + print(f'\nchecking {kind}/{name} template.') + if kind in ['Deployment', 'StatefulSet', 'Job']: + template = doc['spec']['template'] + if kind in ['CronJob']: + template = doc['spec']['jobTemplate']['spec']['template'] + if not check_pod_metadata(kind, name, template): + validated = False + if not check_pod_security_context(kind, name, template): + validated = False + containers = template['spec']['containers'] + for container in containers: + if not check_container_security_context(kind, name, container): + validated = False + if 'initContainers' in template['spec']: + for container in template['spec']['initContainers']: + if not check_container_security_context(kind, name, container): + validated = False + print(f'validated: {validated}') + return validated + +filepath = sys.argv[1] +validated = True +with open(filepath, 'r') as fd: + docs = yaml.safe_load_all(fd) + for doc in docs: + if doc['kind'] in ['Deployment', 'StatefulSet', 'Job', 'CronJob']: + if not check_helm_template(doc): + validated = False + +if not validated: + sys.exit(1) diff --git a/validate-charts/values.tpl b/validate-charts/values.tpl new file mode 100644 index 0000000..8340597 --- /dev/null +++ b/validate-charts/values.tpl @@ -0,0 +1,146 @@ +commonPodAnnotations: + traceable.ai/test: "1234567890" + +commonPodLabels: + applicationid: ABCD1234 + +containerSecurityContext: + runAsUser: 22222 + runAsGroup: 333333 + runAsNonRoot: true + allowPrivilegeEscalation: false + +podSecurityContext: + runAsUser: 55555 + fsGroup: 44444 + runAsNonRoot: true + +# sub-charts +kstreams-app-version-checker: + commonPodAnnotations: + traceable.ai/test: "1234567890" + + commonPodLabels: + applicationid: ABCD1234 + + containerSecurityContext: + runAsUser: 22222 + runAsGroup: 333333 + runAsNonRoot: true + allowPrivilegeEscalation: false + + podSecurityContext: + runAsUser: 55555 + fsGroup: 44444 + runAsNonRoot: true + +hypertrace-pinot: + commonPodAnnotations: + traceable.ai/test: "1234567890" + + commonPodLabels: + applicationid: ABCD1234 + + containerSecurityContext: + runAsUser: 22222 + runAsGroup: 333333 + runAsNonRoot: true + allowPrivilegeEscalation: false + + podSecurityContext: + runAsUser: 55555 + fsGroup: 44444 + runAsNonRoot: true + +config-bootstrapper: + commonPodAnnotations: + traceable.ai/test: "1234567890" + + commonPodLabels: + applicationid: ABCD1234 + + containerSecurityContext: + runAsUser: 22222 + runAsGroup: 333333 + runAsNonRoot: true + allowPrivilegeEscalation: false + + podSecurityContext: + runAsUser: 55555 + fsGroup: 44444 + runAsNonRoot: true + +db-schema-manager: + enabled: false + +redis: + master: + commonPodAnnotations: + traceable.ai/test: "1234567890" + + commonPodLabels: + applicationid: ABCD1234 + + containerSecurityContext: + runAsUser: 22222 + runAsGroup: 333333 + runAsNonRoot: true + allowPrivilegeEscalation: false + + podSecurityContext: + runAsUser: 55555 + fsGroup: 44444 + runAsNonRoot: true + metrics: + commonPodAnnotations: + traceable.ai/test: "1234567890" + + commonPodLabels: + applicationid: ABCD1234 + + containerSecurityContext: + runAsUser: 22222 + runAsGroup: 333333 + runAsNonRoot: true + allowPrivilegeEscalation: false + + podSecurityContext: + runAsUser: 55555 + fsGroup: 44444 + runAsNonRoot: true + +postgresql: + primary: + commonPodAnnotations: + traceable.ai/test: "1234567890" + + commonPodLabels: + applicationid: ABCD1234 + + containerSecurityContext: + runAsUser: 22222 + runAsGroup: 333333 + runAsNonRoot: true + allowPrivilegeEscalation: false + + podSecurityContext: + runAsUser: 55555 + fsGroup: 44444 + runAsNonRoot: true + metrics: + commonPodAnnotations: + traceable.ai/test: "1234567890" + + commonPodLabels: + applicationid: ABCD1234 + + containerSecurityContext: + runAsUser: 22222 + runAsGroup: 333333 + runAsNonRoot: true + allowPrivilegeEscalation: false + + podSecurityContext: + runAsUser: 55555 + fsGroup: 44444 + runAsNonRoot: true