feat: expose container-level securityContext for api, frontend, taskProcessor, sse, and migrate jobs - #571
Conversation
…rocessor, sse, and migrate jobs pgbouncer is the only component that renders allowPrivilegeEscalation and capabilities.drop on its container; every other component only exposes pod-level podSecurityContext/defaultPodSecurityContext, which can't set those two fields (allowPrivilegeEscalation and capabilities are container-only fields). That leaves api, frontend, taskProcessor, sse, jobs.migrateDb, the migrate-db/bootstrap init containers, and jobs.migrateAnalyticsData unable to satisfy Kubernetes' "restricted" Pod Security Standard, regardless of user-supplied values. Adds securityContext/defaultSecurityContext (enabled: true by default, same shape and defaults as pgbouncer's) to each of those components and wires it into their container specs. jobs.migrateAnalyticsData also gains the pod-level podSecurityContext/defaultPodSecurityContext it was missing entirely. Verified with `helm lint` and `helm template` against the chart's own ci/*.yaml value files plus a spread of ad-hoc values enabling every optional component (bootstrap, taskProcessor, sse, both migrate jobs) — every container in the resulting manifests carries the new securityContext except the pre-existing django-secret-init/ sse-secret-init helper jobs' `secret-creator` container, which has no existing securityContext plumbing to hook into and is left for a follow-up. Defaults to `allowPrivilegeEscalation: false` and `capabilities.drop: [ALL]`, which is safe regardless of the UID a container runs as, so this is a non-breaking default (unlike runAsNonRoot, which the chart already leaves opt-in per-component since some tags may still run as root).
|
@germangarces Can I have a review here in this PR? |
amir-haji-usmobile
left a comment
There was a problem hiding this comment.
This unblocks completing a Pod Security Standards (restricted) rollout for downstream users: pod-level podSecurityContext is already exposed, but allowPrivilegeEscalation: false and capabilities.drop are container-level fields, so without this hook charts consumers can't fully satisfy the restricted profile. Additive and defaults-preserving across api, frontend, taskProcessor, sse, and the migrate jobs — we've verified the api/frontend images (2.238.0) run as nobody, so the stricter contexts are compatible.
|
Hi @abbassoltanian-usmobile , thanks for this! Just a nit: the same 6-line merge block is now repeated 8 times across the templates. A named template in {{/*
Security context: chart defaults with user values merged on top (user wins).
Usage: (dict "component" .Values.api "key" "securityContext"|"podSecurityContext")
*/}}
{{- define "flagsmith.mergedSecurityContext" -}}
{{- $defaultKey := printf "default%s" (.key | title) -}}
{{- $ctx := index .component .key | default dict | deepCopy -}}
{{- $defaults := index .component $defaultKey | default dict -}}
{{- if $defaults.enabled -}}
{{- $ctx = $ctx | merge (omit $defaults "enabled") -}}
{{- end -}}
{{- toYaml $ctx -}}
{{- end -}}Call sites securityContext: {{- include "flagsmith.mergedSecurityContext" (dict "component" .Values.api "key" "securityContext") | nindent 10 }}
securityContext: {{- include "flagsmith.mergedSecurityContext" (dict "component" .Values.api "key" "podSecurityContext") | nindent 8 }} |
Thank you @germangarces for your perfect feedback. I will fix it and update the PR. |
Collapse repeated securityContext merge blocks into flagsmith.mergedSecurityContext in _helpers.tpl per review feedback on PR Flagsmith#571.
|
@germangarces thanks again for your feedback. |
|
@abbassoltanian-usmobile Thanks, helper's cleaner than what I sketched. Retested: all five pods admitted under One blocker, all from the 79974a9 commit. Per-component override doesn't work. --set api.defaultSecurityContext.enabled=false # still drop: [ALL] ← no effect
--set global.defaultSecurityContext.enabled=false # {} ← works
Also regresses --set api.defaultPodSecurityContext.enabled=false --set api.defaultPodSecurityContext.fsGroup=1111
main → securityContext: {} PR → securityContext: {fsGroup: 1111}That key is gone from The 79974a9 move is a separate concern from exposing container-level |
79974a9 to
a10667b
Compare
|
@germangarces good catch, thanks for the thorough retest. Agreed — dropping the global-defaults move rather than trying to patch the boolean-merge issue in place. Force-pushed the branch back to |
germangarces
left a comment
There was a problem hiding this comment.
Thanks for your work @abbassoltanian-usmobile
Changes
pgbounceris currently the only component in this chart whose container renderssecurityContext— it's the only one with bothsecurityContext/defaultSecurityContextinvalues.yamland the corresponding container-level block in its Deployment template. Every other component (api,frontend,taskProcessor,sse, themigrate-db/bootstrapinit containers,jobs.migrateDb,jobs.migrateAnalyticsData) only exposespodSecurityContext/defaultPodSecurityContext(pod-level). SinceallowPrivilegeEscalationandcapabilities.dropare container-only fields, there is currently no way — viavalues.yamlor otherwise — to make any of those components satisfy Kubernetes' restricted Pod Security Standard, which requires both.This PR mirrors the existing
pgbouncerpattern onto the rest of the chart:securityContext: {}/defaultSecurityContext: {enabled: true, allowPrivilegeEscalation: false, capabilities.drop: [ALL]}toapi,frontend,taskProcessor,sse,jobs.migrateDb, andjobs.migrateAnalyticsDatainvalues.yaml.deployment-api.yaml(main container +migrate-dbandbootstrapinit containers, which shareapi's image and now share itssecurityContext),deployment-frontend.yaml,deployment-task-processor.yaml,deployment-sse.yaml,jobs-migrate-db.yaml, andjobs-migrate-analytics-data.yaml.jobs.migrateAnalyticsDatawas also missingpodSecurityContext/defaultPodSecurityContextentirely (unlikejobs.migrateDb, which already had it) — added that too, so the Job now sets pod-level security context at all.Defaults to
enabled: true, matchingpgbouncer's own default.allowPrivilegeEscalation: false+ dropping all capabilities is safe regardless of which UID a container runs as, so this shouldn't be breaking — unlikerunAsNonRoot(left commented out chart-wide, presumably because some image tags may still run as root).Not included: the
secret-creatorcontainer in thedjango-secret-init/sse-secret-inithelper Jobs has the same gap but no existingsecurityContextvalues plumbing to extend (nopodSecurityContextequivalent either), so it's a separate shaped change — happy to follow up in another PR if useful.How did you test this code?
helm lintandhelm templatelocally against:charts/flagsmith/ci/*.yamlapi.bootstrap,taskProcessor,sse,jobs.migrateDb,jobs.migrateAnalyticsData,databaseExternal) to render every container this PR touches in one passThen walked the rendered manifests and confirmed every targeted container (
flagsmith-api,bootstrap,migrate-dbinit container,flagsmith-frontend,flagsmith-task-processor,flagsmith-sse, and bothmigrate-db/migrate-analytics-dataJob containers) carriessecurityContext: {allowPrivilegeEscalation: false, capabilities: {drop: [ALL]}}, and that the default (no extra values) render is unaffected.Motivated by a real deployment: we run this chart in an EKS namespace with
pod-security.kubernetes.io/{audit,warn}=restricted, andflagsmith-frontendand themigrate-dbinit container currently show up as PodSecurity violations with no way to resolve them throughvalues.yaml.