rstudio / rstudio/helm

rstudio-workbench: launcher `pod.securityContext` is silently ignored on session pods unless `supplementalGroupIds` is set

Open Beginner friendly
#907 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug team: launcher team: workbench
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.defaultSecurityContext nested-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:

  1. The merge is gated on supplementalGroupIds. The one and only mergeOverwrite ... $templateData.pod.securityContext (line 141) lives inside {{- if .Job.container.supplementalGroupIds }}. If a session has no supplemental groups, pod.securityContext is never merged at all.
  2. The merge is assigned with := (shadowing). Line 141 uses :=, which declares a new block-scoped $securityContext rather than reassigning the outer one that the render loop reads. The merge only takes visible effect at all because Sprig's mergeOverwrite mutates 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 securityContext on 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.