rstudio-workbench: `pod.defaultSecurityContext` renders nested fields (e.g. `seccompProfile`) as invalid YAML, Kubernetes rejects the session Job
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 renders launcher.templateValues.pod.defaultSecurityContext with a manual {{ $key }}: {{ $val }} loop instead of toYaml. This works for scalar fields (runAsUser, runAsNonRoot, fsGroup, …) but corrupts any nested field. A nested value such as seccompProfile: {type: RuntimeDefault} is stringified via Go's default map formatting into seccompProfile: map[type:RuntimeDefault], which the Kubernetes API then rejects at admission — the session Job is never created.
seccompProfile: RuntimeDefault is required by the Kubernetes restricted Pod Security Standard, so operators targeting that profile hit this immediately.
Companion issue for the related
pod.securityContextmerge bug in the same block: #907.
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 143–148):
{{- if $securityContext }}
securityContext:
{{- range $key, $val := $securityContext }}
{{ $key }}: {{ $val }} {{/* <-- manual loop, not toYaml */}}
{{- end }}
{{- end }}
$securityContext starts as $templateData.pod.defaultSecurityContext (line 128). Any nested value in it goes through {{ $val }}, which for a map produces Go's map[...] representation rather than valid YAML.
Note the container-level path a few lines down (lines 274–277) already does this correctly with toYaml:
{{- with $templateData.pod.containerSecurityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
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 through plain helm template. Only the data-source references are rewired; 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 }}
Render:
$ helm template x . \
--set-json 'pod={"defaultSecurityContext":{"allowPrivilegeEscalation":false,"seccompProfile":{"type":"RuntimeDefault"}},"securityContext":{}}' \
--set-json 'job={"container":{"runAsUserId":0}}' \
--show-only templates/job-snippet.yaml
securityContext:
allowPrivilegeEscalation: false
seccompProfile: map[type:RuntimeDefault] # <-- invalid
Kubernetes rejects it (server-side dry-run of that rendered securityContext on a real cluster):
$ kubectl apply --dry-run=server -f broken-job.yaml
Error from server (BadRequest): error when creating "broken-job.yaml": Job in
version "v1" cannot be handled as a Job: json: cannot unmarshal string into Go
struct field PodSecurityContext.spec.template.spec.securityContext.seccompProfile
of type v1.SeccompProfile
(In the launcher this manifests as the session failing to start; the launcher's debug log shows the same admission error on the POST .../jobs request.)
Impact
- Any nested field in
pod.defaultSecurityContextbreaks the session Job. seccompProfile: RuntimeDefault— required by the restricted PSS — cannot be set viadefaultSecurityContext, so operators can't meet that profile through this key.- Workaround: put nested fields in
pod.containerSecurityContextinstead (thetoYamlpath at lines 274–277). Kubernetes acceptsseccompProfileat the container level, so this is a full workaround forseccompProfile.
Suggested fix
Render with toYaml, matching the container-level path (274–277) and the Connect chart's job.tpl:
{{- if $securityContext }}
{{- $securityContext := mergeOverwrite $securityContext $templateData.pod.securityContext }}
securityContext:
{{- toYaml $securityContext | nindent 8 }}
{{- end }}
This is the same one-block change that also fixes #907 (the gated / shadowed pod.securityContext merge). The Connect chart (charts/rstudio-connect/files/job.tpl) already implements exactly this pattern.
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 in charts/rstudio-workbench/files/job.tpl around lines 128–148 and compare the default security-context block with the container-level toYaml block around lines 274–277. Render the supplied nested seccompProfile reproduction with helm template, then verify the output with kubectl server-side dry-run; done means nested fields are valid YAML and the Kubernetes Job is accepted.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- helm, kubernetes
- Domain
- devops, infrastructure
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100