rstudio-workbench: launcher `pod.securityContext` is silently ignored on session pods unless `supplementalGroupIds` is set
Nobody has claimed this yet.
- Dominant language
- Markdown
- Stars
- 46
- Forks
- 40
- Avg merge
- 4h 3m
- Merged PRs (30d)
- 6
Description
Summary
In the rstudio-workbench chart, the session-pod launcher template charts/rstudio-workbench/files/job.tpl never applies launcher.templateValues.pod.securityContext to the rendered Job unless the session happens to have supplemental group IDs. In the common case (no supplemental groups) the entire pod.securityContext block is dropped from the pod spec, silently — no error, the session still launches, and the pod-level securityContext the operator configured simply isn't there.
This is a security-relevant miss: operators hardening session pods for a restricted Pod Security Standard / OPA Gatekeeper cluster set pod.securityContext expecting it to land on the pod, and it doesn't.
Companion issue for the related
pod.defaultSecurityContextnested-field bug in the same block: #908.
Affected versions
rstudio-workbench 0.20.4 through 0.21.2, and current main (files/job.tpl header # Version: 2.5.0). The block is byte-identical across all of these.
The code
charts/rstudio-workbench/files/job.tpl (lines 128–148):
{{- $securityContext := $templateData.pod.defaultSecurityContext }}
{{- if .Job.container.runAsUserId }}
{{- $_ := set $securityContext "runAsUser" .Job.container.runAsUserId }}
{{- end }}
{{- if .Job.container.runAsGroupId }}
{{- $_ := set $securityContext "runAsGroup" .Job.container.runAsGroupId }}
{{- end }}
{{- if .Job.container.supplementalGroupIds }}
{{- $groupIds := list }}
{{- range .Job.container.supplementalGroupIds }}
{{- $groupIds = append $groupIds . }}
{{- end }}
{{- $_ := set $securityContext "supplementalGroups" (cat "[" ($groupIds | join ", ") "]") }}
{{- $securityContext := mergeOverwrite $securityContext $templateData.pod.securityContext }} {{/* <-- line 141 */}}
{{- end }}
{{- if $securityContext }}
securityContext:
{{- range $key, $val := $securityContext }}
{{ $key }}: {{ $val }}
{{- end }}
{{- end }}
Two things combine here:
- The merge is gated on
supplementalGroupIds. The one and onlymergeOverwrite ... $templateData.pod.securityContext(line 141) lives inside{{- if .Job.container.supplementalGroupIds }}. If a session has no supplemental groups,pod.securityContextis never merged at all. - The merge is assigned with
:=(shadowing). Line 141 uses:=, which declares a new block-scoped$securityContextrather than reassigning the outer one that the render loop reads. The merge only takes visible effect at all because Sprig'smergeOverwritemutates its first argument in place — the returned value is discarded. Fragile, and clearly not the intent.
Minimal reproduction
job.tpl is a launcher runtime template (not rendered by helm template of the chart directly), so this reprex renders the exact block above through plain helm template. Only the data-source references are rewired ($templateData.pod.* → .Values.pod.*, .Job.container.* → .Values.job.container.*); the template logic is copied verbatim.
Chart.yaml:
apiVersion: v2
name: jobtpl-reprex
version: 0.0.1
templates/job-snippet.yaml (verbatim copy of job.tpl:128–148, data refs rewired):
spec:
template:
spec:
{{- $securityContext := .Values.pod.defaultSecurityContext }}
{{- if .Values.job.container.runAsUserId }}
{{- $_ := set $securityContext "runAsUser" .Values.job.container.runAsUserId }}
{{- end }}
{{- if .Values.job.container.runAsGroupId }}
{{- $_ := set $securityContext "runAsGroup" .Values.job.container.runAsGroupId }}
{{- end }}
{{- if .Values.job.container.supplementalGroupIds }}
{{- $groupIds := list }}
{{- range .Values.job.container.supplementalGroupIds }}
{{- $groupIds = append $groupIds . }}
{{- end }}
{{- $_ := set $securityContext "supplementalGroups" (cat "[" ($groupIds | join ", ") "]") }}
{{- $securityContext := mergeOverwrite $securityContext .Values.pod.securityContext }}
{{- end }}
{{- if $securityContext }}
securityContext:
{{- range $key, $val := $securityContext }}
{{ $key }}: {{ $val }}
{{- end }}
{{- end }}
Case A — no supplemental groups (pod.securityContext DROPPED):
$ helm template x . \
--set-json 'pod={"defaultSecurityContext":{"runAsUser":1000},"securityContext":{"runAsNonRoot":true,"fsGroup":2000}}' \
--set-json 'job={"container":{}}' \
--show-only templates/job-snippet.yaml
securityContext:
runAsUser: 1000
runAsNonRoot: true and fsGroup: 2000 from pod.securityContext are gone.
Case B — with supplemental groups (pod.securityContext applied):
$ helm template x . \
--set-json 'pod={"defaultSecurityContext":{"runAsUser":1000},"securityContext":{"runAsNonRoot":true}}' \
--set-json 'job={"container":{"supplementalGroupIds":[4000]}}' \
--show-only templates/job-snippet.yaml
securityContext:
runAsNonRoot: true
runAsUser: 1000
supplementalGroups: [ 4000 ]
Same pod.securityContext, but now it lands — the only difference is the presence of a supplemental group.
Impact
- Operators cannot reliably set a pod-level
securityContexton session pods. - The failure is silent (session launches; the hardening just isn't applied), so it's easy to believe a restricted-PSS session pod is hardened when it isn't.
Suggested fix
The Connect chart already does this correctly in charts/rstudio-connect/files/job.tpl — it merges pod.securityContext unconditionally inside {{- if $securityContext }} and renders with toYaml:
{{- if $securityContext }}
{{- $securityContext := mergeOverwrite $securityContext $templateData.pod.securityContext }}
securityContext:
{{- toYaml $securityContext | nindent 8 }}
{{- end }}
Porting that pattern to rstudio-workbench/files/job.tpl fixes this and the companion toYaml bug in the same block (#908).
Related historical context: #293 ("Add securityContext to templating") and #319.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with charts/rstudio-workbench/files/job.tpl, especially the securityContext block around lines 128–148, and compare it with charts/rstudio-connect/files/job.tpl. Run the issue’s Helm reproduction for a session without supplementalGroupIds; done means pod.securityContext is rendered in that case while supplemental groups and existing defaults still render correctly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- helm, kubernetes
- Domain
- infrastructure, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100