feat: add helm var to support RBAC for deploying workspaces in extra namespaces (#19517)

This is a feature to create Role & RoleBinding entries on a per
namespace basis to support deploying workspaces in separate namespace to
where Coder is deployed. The idea behind this is to avoid the creation
of custom RBAC entries or the use of ClusterRoles (in order to maintain
priciple of least privilege).

> If you have used AI to produce some or all of this PR, please ensure
you have read our [AI Contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
before submitting.

This is a blink assisted PR.

Example `helm template` without
`coder.serviceAccount.workspaceNamespaces` enabled (existing behaviour
as of current release) is below. Outcome = 1 x SA, 1 x Role, 1 x
RoleBinding, all in the coder (`.Release.Namespace`) namespace.
```
➜  coder git:(feat/helm_namespace_rbac_improvements) ✗ helm template -n coder coder . --set coder.image.tag=v2.25.1
---
...
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: coder-workspace-perms
  namespace: coder
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs:
    - create
    - delete
    - deletecollection
    - get
    - list
    - patch
    - update
    - watch
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs:
    - create
    - delete
    - deletecollection
    - get
    - list
    - patch
    - update
    - watch
  - apiGroups:
    - apps
    resources:
    - deployments
    verbs:
    - create
    - delete
    - deletecollection
    - get
    - list
    - patch
    - update
    - watch
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: "coder"
  namespace: coder
subjects:
  - kind: ServiceAccount
    name: "coder"
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: coder-workspace-perms
---
```

Example `helm template` *with*
`coder.serviceAccount.workspaceNamespaces` enabled is below. Outcome = 1
x SA, 1 x Role, 1 x RoleBinding, all in the coder (`.Release.Namespace`)
namespace PLUS a Role and RoleBinding in the `dev-ws` namespace with
each of the RoleBindings referencing the coder SA in the coder
(`.Release.Namespace`) namespace:

```
➜  coder git:(feat/helm_namespace_rbac_improvements) ✗ helm template -n coder coder . --set coder.image.tag=v2.25.1 --set-json 'coder.serviceAccount.workspaceNamespaces=[{"name":"dev-ws","workspacePerms":true,"enableDeployments":true,"extraRules":[]}]' 
---
...
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: coder-workspace-perms
  namespace: coder
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs:
    - create
    - delete
    - deletecollection
    - get
    - list
    - patch
    - update
    - watch
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs:
    - create
    - delete
    - deletecollection
    - get
    - list
    - patch
    - update
    - watch
  - apiGroups:
    - apps
    resources:
    - deployments
    verbs:
    - create
    - delete
    - deletecollection
    - get
    - list
    - patch
    - update
    - watch
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: coder-workspace-perms
  namespace: dev-ws
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs:
    - create
    - delete
    - deletecollection
    - get
    - list
    - patch
    - update
    - watch
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs:
    - create
    - delete
    - deletecollection
    - get
    - list
    - patch
    - update
    - watch
  - apiGroups:
    - apps
    resources:
    - deployments
    verbs:
    - create
    - delete
    - deletecollection
    - get
    - list
    - patch
    - update
    - watch
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: "coder"
  namespace: coder
subjects:
  - kind: ServiceAccount
    name: "coder"
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: coder-workspace-perms
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: "coder"
  namespace: dev-ws
subjects:
  - kind: ServiceAccount
    name: "coder"
    namespace: coder
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: coder-workspace-perms
---
```
This commit is contained in:
Rowan Smith
2025-09-18 14:27:04 +10:00
committed by GitHub
parent 8a6852f095
commit 623893708b
10 changed files with 1035 additions and 50 deletions
+42
View File
@@ -198,3 +198,45 @@ Usage:
{{- tpl (.value | toYaml) .context }}
{{- end }}
{{- end -}}
{{- define "libcoder.rbac.rules.basic" -}}
- apiGroups: [""]
resources: ["pods"]
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
{{- end }}
{{- define "libcoder.rbac.rules.deployments" -}}
- apiGroups:
- apps
resources:
- deployments
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
{{- end }}
+72 -47
View File
@@ -1,64 +1,89 @@
{{- define "libcoder.rbac.tpl" -}}
{{- if .Values.coder.serviceAccount.workspacePerms }}
{{- define "libcoder.rbac.forNamespace" -}}
{{- $nsPerms := ternary .workspacePerms .Top.Values.coder.serviceAccount.workspacePerms (hasKey . "workspacePerms") -}}
{{- $nsDeploy := ternary .enableDeployments .Top.Values.coder.serviceAccount.enableDeployments (hasKey . "enableDeployments") -}}
{{- $nsExtra := ternary .extraRules .Top.Values.coder.serviceAccount.extraRules (hasKey . "extraRules") -}}
{{- if or $nsPerms (or $nsDeploy $nsExtra) }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Values.coder.serviceAccount.name }}-workspace-perms
namespace: {{ .Release.Namespace }}
name: {{ .Top.Values.coder.serviceAccount.name }}-workspace-perms
namespace: {{ .NS }}
rules:
- apiGroups: [""]
resources: ["pods"]
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
{{- if .Values.coder.serviceAccount.enableDeployments }}
- apiGroups:
- apps
resources:
- deployments
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
{{- if $nsPerms }}
{{ include "libcoder.rbac.rules.basic" .Top | trimPrefix "\n" | indent 2 }}
{{- end }}
{{- with .Values.coder.serviceAccount.extraRules }}
{{ toYaml . | nindent 2 }}
{{- if $nsDeploy }}
{{ include "libcoder.rbac.rules.deployments" .Top | trimPrefix "\n" | indent 2 }}
{{- end }}
{{- if $nsExtra }}
{{- if kindIs "slice" $nsExtra }}
{{ toYaml $nsExtra | trimPrefix "\n" | indent 2 }}
{{- else }}
{{ toYaml (list $nsExtra) | trimPrefix "\n" | indent 2 }}
{{- end }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Values.coder.serviceAccount.name | quote }}
namespace: {{ .Release.Namespace }}
name: {{ .Top.Values.coder.serviceAccount.name | quote }}
namespace: {{ .NS }}
subjects:
- kind: ServiceAccount
name: {{ .Values.coder.serviceAccount.name | quote }}
name: {{ .Top.Values.coder.serviceAccount.name | quote }}
{{- if ne .NS .Top.Release.Namespace }}
namespace: {{ .Top.Release.Namespace }}
{{- end }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Values.coder.serviceAccount.name }}-workspace-perms
{{- end }}
name: {{ .Top.Values.coder.serviceAccount.name }}-workspace-perms
{{- end }}
{{- end -}}
{{- define "libcoder.rbac.core" -}}
{{- $top := . -}}
{{- $rootPerms := $top.Values.coder.serviceAccount.workspacePerms | default false -}}
{{- $rootDeploy := $top.Values.coder.serviceAccount.enableDeployments | default false -}}
{{- $rootExtra := $top.Values.coder.serviceAccount.extraRules | default (list) -}}
{{- $rootParams := dict
"Top" $top
"NS" $top.Release.Namespace
"workspacePerms" $rootPerms
"enableDeployments" $rootDeploy
"extraRules" $rootExtra -}}
{{ include "libcoder.rbac.forNamespace" $rootParams }}
{{- $wsnsRaw := get $top.Values.coder.serviceAccount "workspaceNamespaces" -}}
{{- $extra := default (list) $wsnsRaw -}}
{{- range $_, $ns := $extra }}
{{- $nsName := ternary $ns.name $ns (kindIs "map" $ns) -}}
{{- if $nsName }}
{{- $params := dict "Top" $top "NS" $nsName -}}
{{- if kindIs "map" $ns }}
{{- if hasKey $ns "workspacePerms" }}{{- $_ := set $params "workspacePerms" $ns.workspacePerms }}{{- else }}{{- $_ := set $params "workspacePerms" $rootPerms }}{{- end }}
{{- if hasKey $ns "enableDeployments" }}{{- $_ := set $params "enableDeployments" $ns.enableDeployments }}{{- else }}{{- $_ := set $params "enableDeployments" $rootDeploy }}{{- end }}
{{- if hasKey $ns "extraRules" }}{{- $_ := set $params "extraRules" $ns.extraRules }}{{- else }}{{- $_ := set $params "extraRules" $rootExtra }}{{- end }}
{{- else }}
{{- $_ := set $params "workspacePerms" $rootPerms -}}
{{- $_ := set $params "enableDeployments" $rootDeploy -}}
{{- $_ := set $params "extraRules" $rootExtra -}}
{{- end }}
{{ include "libcoder.rbac.forNamespace" $params }}
{{- end }}
{{- end }}
{{- end -}}
{{- define "libcoder.rbac.tpl" -}}
{{- if not .Values.coder.serviceAccount.disableCreate -}}
{{ include "libcoder.rbac.core" . }}
{{- end }}
{{- end -}}
{{- define "libcoder.namespace.rbac.tpl" -}}
{{ include "libcoder.rbac.core" . }}
{{- end -}}