rstudio / rstudio/helm

rstudio-workbench: `pod.defaultSecurityContext` renders nested fields (e.g. `seccompProfile`) as invalid YAML, Kubernetes rejects the session Job

Open Beginner friendly
#908 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug 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 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.securityContext merge 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.defaultSecurityContext breaks the session Job.
  • seccompProfile: RuntimeDefault — required by the restricted PSS — cannot be set via defaultSecurityContext, so operators can't meet that profile through this key.
  • Workaround: put nested fields in pod.containerSecurityContext instead (the toYaml path at lines 274–277). Kubernetes accepts seccompProfile at the container level, so this is a full workaround for seccompProfile.
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

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 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.