From 90dabe31ed9ffeb72f75816c4b9d5c4ccf0c3c59 Mon Sep 17 00:00:00 2001
From: mikaeld <mducharme@mozilla.com>
Date: Mon, 17 Jul 2023 17:59:16 -0400
Subject: [PATCH 1/2] feat(chart): Add pod security context to pgbouncer.

---
 .../pgbouncer/pgbouncer-deployment.yaml       |  8 ++---
 chart/values.schema.json                      | 16 ++++++++-
 chart/values.yaml                             |  1 +
 helm_tests/security/test_security_context.py  | 33 ++++++++++++++-----
 4 files changed, 44 insertions(+), 14 deletions(-)

diff --git a/chart/templates/pgbouncer/pgbouncer-deployment.yaml b/chart/templates/pgbouncer/pgbouncer-deployment.yaml
index fde07807c4e63..14665692fcf61 100644
--- a/chart/templates/pgbouncer/pgbouncer-deployment.yaml
+++ b/chart/templates/pgbouncer/pgbouncer-deployment.yaml
@@ -26,8 +26,9 @@
 {{- $tolerations := or .Values.pgbouncer.tolerations .Values.tolerations }}
 {{- $topologySpreadConstraints := or .Values.pgbouncer.topologySpreadConstraints .Values.topologySpreadConstraints }}
 {{- $revisionHistoryLimit := or .Values.pgbouncer.revisionHistoryLimit .Values.revisionHistoryLimit }}
-{{- $containerSecurityContext := include "containerSecurityContext" (list . .Values.pgbouncer) }}
-{{- $containerSecurityContextMetricsExporter := include "containerSecurityContext" (list . .Values.pgbouncer.metricsExporterSidecar) }}
+{{- $securityContext := include "localPodSecurityContext" .Values.pgbouncer }}
+{{- $containerSecurityContext := include "externalContainerSecurityContext" .Values.pgbouncer }}
+{{- $containerSecurityContextMetricsExporter := include "externalContainerSecurityContext" .Values.pgbouncer.metricsExporterSidecar }}
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -82,8 +83,7 @@ spec:
       tolerations: {{- toYaml $tolerations | nindent 8 }}
       topologySpreadConstraints: {{- toYaml $topologySpreadConstraints | nindent 8 }}
       serviceAccountName: {{ include "pgbouncer.serviceAccountName" . }}
-      securityContext:
-        runAsUser: {{ .Values.pgbouncer.uid }}
+      securityContext: {{ $securityContext | nindent 8 }}
       restartPolicy: Always
       {{- if or .Values.registry.secretName .Values.registry.connection }}
       imagePullSecrets:
diff --git a/chart/values.schema.json b/chart/values.schema.json
index a4a329321a2f1..ce116a2ea77a4 100644
--- a/chart/values.schema.json
+++ b/chart/values.schema.json
@@ -5080,10 +5080,24 @@
                     "$ref": "#/definitions/io.k8s.api.core.v1.ResourceRequirements"
                 },
                 "securityContexts": {
-                    "description": "Security context definition for the PgBouncer. If not set, the values from global `securityContexts` will be used.",
+                    "description": "Security context definition for the PgBouncer.",
                     "type": "object",
                     "x-docsSection": "Kubernetes",
                     "properties": {
+                        "pod": {
+                            "description": "Pod security context definition for the PgBouncer.",
+                            "type": "object",
+                            "$ref": "#/definitions/io.k8s.api.core.v1.PodSecurityContext",
+                            "default": {},
+                            "x-docsSection": "Kubernetes",
+                            "examples": [
+                                {
+                                    "runAsUser": 65534,
+                                    "runAsGroup": 0,
+                                    "fsGroup": 0
+                                }
+                            ]
+                        },
                         "container": {
                             "description": "Container security context definition for the PgBouncer.",
                             "type": "object",
diff --git a/chart/values.yaml b/chart/values.yaml
index 53d9630007971..9569ec2d82f79 100644
--- a/chart/values.yaml
+++ b/chart/values.yaml
@@ -1833,6 +1833,7 @@ pgbouncer:
 
   # Detailed default security context for pgbouncer for container level
   securityContexts:
+    pod: {}
     container: {}
 
   metricsExporterSidecar:
diff --git a/helm_tests/security/test_security_context.py b/helm_tests/security/test_security_context.py
index f7693af198053..25b14c41bc1ad 100644
--- a/helm_tests/security/test_security_context.py
+++ b/helm_tests/security/test_security_context.py
@@ -57,6 +57,14 @@ def test_check_statsd_uid(self):
 
         assert 3000 == jmespath.search("spec.template.spec.securityContext.runAsUser", docs[0])
 
+    def test_check_pgbouncer_uid(self):
+        docs = render_chart(
+            values={"pgbouncer": {"enabled": True, "uid": 3000}},
+            show_only=["templates/pgbouncer/pgbouncer-deployment.yaml"],
+        )
+
+        assert 3000 == jmespath.search("spec.template.spec.securityContext.runAsUser", docs[0])
+
     def test_check_cleanup_job(self):
         docs = render_chart(
             values={"uid": 3000, "gid": 30, "cleanup": {"enabled": True}},
@@ -219,7 +227,10 @@ def test_global_security_context(self):
         ctx_value_pod = {"runAsUser": 7000}
         ctx_value_container = {"allowPrivilegeEscalation": False}
         docs = render_chart(
-            values={"securityContexts": {"containers": ctx_value_container, "pod": ctx_value_pod}},
+            values={
+                "securityContexts": {"containers": ctx_value_container, "pod": ctx_value_pod},
+                "pgbouncer": {"enabled": True},
+            },
             show_only=[
                 "templates/flower/flower-deployment.yaml",
                 "templates/scheduler/scheduler-deployment.yaml",
@@ -228,31 +239,35 @@ def test_global_security_context(self):
                 "templates/jobs/create-user-job.yaml",
                 "templates/jobs/migrate-database-job.yaml",
                 "templates/triggerer/triggerer-deployment.yaml",
+                "templates/pgbouncer/pgbouncer-deployment.yaml",
                 "templates/statsd/statsd-deployment.yaml",
                 "templates/redis/redis-statefulset.yaml",
             ],
         )
-
-        for index in range(len(docs) - 2):
+        for index in range(len(docs) - 3):
             assert ctx_value_container == jmespath.search(
                 "spec.template.spec.containers[0].securityContext", docs[index]
             )
             assert ctx_value_pod == jmespath.search("spec.template.spec.securityContext", docs[index])
 
-        # Global security context is not propagated to redis and statsd, so we test default value
+        # Global security context is not propagated to pgbouncer, redis and statsd, so we test default value
         default_ctx_value_container = {"allowPrivilegeEscalation": False, "capabilities": {"drop": ["ALL"]}}
+        default_ctx_value_pod_pgbouncer = {"runAsUser": 65534}
         default_ctx_value_pod_statsd = {"runAsUser": 65534}
         default_ctx_value_pod_redis = {"runAsUser": 0}
-        for index in range(len(docs) - 2, len(docs)):
+        for index in range(len(docs) - 3, len(docs)):
             assert default_ctx_value_container == jmespath.search(
                 "spec.template.spec.containers[0].securityContext", docs[index]
             )
-        assert default_ctx_value_pod_statsd == jmespath.search(
-            "spec.template.spec.securityContext", docs[len(docs) - 2]
+        # Test pgbouncer metrics-exporter container
+        assert default_ctx_value_container == jmespath.search(
+            "spec.template.spec.containers[1].securityContext", docs[-3]
         )
-        assert default_ctx_value_pod_redis == jmespath.search(
-            "spec.template.spec.securityContext", docs[len(docs) - 1]
+        assert default_ctx_value_pod_pgbouncer == jmespath.search(
+            "spec.template.spec.securityContext", docs[-3]
         )
+        assert default_ctx_value_pod_statsd == jmespath.search("spec.template.spec.securityContext", docs[-2])
+        assert default_ctx_value_pod_redis == jmespath.search("spec.template.spec.securityContext", docs[-1])
 
     # Test securityContexts for main containers
     def test_main_container_setting(self):

From 30b2b4a8ee53bd7ee1aa817edcd365c9aa83e7df Mon Sep 17 00:00:00 2001
From: mikaeld <mducharme@mozilla.com>
Date: Tue, 18 Jul 2023 10:52:22 -0400
Subject: [PATCH 2/2] refactor test_global_security_context with pythonic loops

---
 helm_tests/security/test_security_context.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/helm_tests/security/test_security_context.py b/helm_tests/security/test_security_context.py
index 25b14c41bc1ad..72fba4a4fbe81 100644
--- a/helm_tests/security/test_security_context.py
+++ b/helm_tests/security/test_security_context.py
@@ -244,20 +244,20 @@ def test_global_security_context(self):
                 "templates/redis/redis-statefulset.yaml",
             ],
         )
-        for index in range(len(docs) - 3):
+        for doc in docs[:-3]:
             assert ctx_value_container == jmespath.search(
-                "spec.template.spec.containers[0].securityContext", docs[index]
+                "spec.template.spec.containers[0].securityContext", doc
             )
-            assert ctx_value_pod == jmespath.search("spec.template.spec.securityContext", docs[index])
+            assert ctx_value_pod == jmespath.search("spec.template.spec.securityContext", doc)
 
         # Global security context is not propagated to pgbouncer, redis and statsd, so we test default value
         default_ctx_value_container = {"allowPrivilegeEscalation": False, "capabilities": {"drop": ["ALL"]}}
         default_ctx_value_pod_pgbouncer = {"runAsUser": 65534}
         default_ctx_value_pod_statsd = {"runAsUser": 65534}
         default_ctx_value_pod_redis = {"runAsUser": 0}
-        for index in range(len(docs) - 3, len(docs)):
+        for doc in docs[-3:]:
             assert default_ctx_value_container == jmespath.search(
-                "spec.template.spec.containers[0].securityContext", docs[index]
+                "spec.template.spec.containers[0].securityContext", doc
             )
         # Test pgbouncer metrics-exporter container
         assert default_ctx_value_container == jmespath.search(
