From ecf6324d6757bb29e080d0729f8d970ce5048466 Mon Sep 17 00:00:00 2001 From: sabsari Date: Mon, 3 Aug 2026 21:21:14 +0900 Subject: [PATCH] Make the external server's downstream RBAC subject configurable (#899) Replace the hardcoded `User: spire-root` subject with an `externalServerSubject` block (`kind`/`name`/`namespace`) so the downstream RBAC can bind to a User, Group, or ServiceAccount. Defaults preserve the previous behavior. Signed-off-by: sabsari Co-authored-by: Claude Opus 4.8 --- charts/spire/charts/spire-server/README.md | 3 ++ .../spire-server/templates/_helpers.tpl | 19 ++++++++++-- charts/spire/charts/spire-server/values.yaml | 8 +++++ tests/unit/spire_test.go | 31 +++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/charts/spire/charts/spire-server/README.md b/charts/spire/charts/spire-server/README.md index a66b952..290b3fa 100644 --- a/charts/spire/charts/spire-server/README.md +++ b/charts/spire/charts/spire-server/README.md @@ -88,6 +88,9 @@ In order to run Tornjak with simple HTTP Connection only, make sure you don't cr | `image.tag` | Overrides the image tag whose default is the chart appVersion | `""` | | `kind` | Define SPIRE server deployment type. Can be statefulset/deployment. Defaults to statefulset if not set. This feature is experimental. | `statefulset` | | `externalServer` | Deploy only the bundle ConfigMap, RBAC rules, and identity documents but not the server. Use in a nested setup where the server is external. | `false` | +| `externalServerSubject.kind` | RBAC subject kind the external (nested) server's downstream bindings are granted to. One of "User" (client-certificate identity, the historical default), "Group", or "ServiceAccount" (e.g. for a static-token kubeconfig). Only used when externalServer is true. | `User` | +| `externalServerSubject.name` | Name of the subject. For kind "User" it must match the CN of the client certificate the external server presents; for kind "Group" it is the group name (e.g. a certificate O value); for kind "ServiceAccount" it is the name of the (operator-managed, out-of-band) ServiceAccount. | `spire-root` | +| `externalServerSubject.namespace` | Namespace of the ServiceAccount. Only used when kind is "ServiceAccount"; empty uses the server namespace. | `""` | | `imagePullSecrets` | Pull secrets for images | `[]` | | `nameOverride` | Name override | `""` | | `crNameOverride` | Name override for any custom resources | `""` | diff --git a/charts/spire/charts/spire-server/templates/_helpers.tpl b/charts/spire/charts/spire-server/templates/_helpers.tpl index ac56a29..42370b6 100644 --- a/charts/spire/charts/spire-server/templates/_helpers.tpl +++ b/charts/spire/charts/spire-server/templates/_helpers.tpl @@ -412,12 +412,27 @@ The code below determines what connection type should be used. {{- default .Values.caSubject.commonName $g }} {{- end }} +{{- define "spire-server.external-server-subject-kind" -}} +{{- $kind := .Values.externalServerSubject.kind | default "User" }} +{{- if not (has $kind (list "User" "Group" "ServiceAccount")) }} +{{- fail (printf "Unknown externalServerSubject.kind: %s (must be \"User\", \"Group\", or \"ServiceAccount\")" $kind) }} +{{- end }} +{{- $kind }} +{{- end }} + {{- define "spire-server.subject" }} subjects: {{- if .Values.externalServer }} +{{- $kind := include "spire-server.external-server-subject-kind" . }} +{{- if eq $kind "ServiceAccount" }} +- kind: ServiceAccount + name: {{ .Values.externalServerSubject.name | quote }} + namespace: {{ .Values.externalServerSubject.namespace | default (include "spire-server.namespace" .) | quote }} +{{- else }} - apiGroup: rbac.authorization.k8s.io - kind: User - name: spire-root + kind: {{ $kind }} + name: {{ .Values.externalServerSubject.name | quote }} +{{- end }} {{- else }} - kind: ServiceAccount name: {{ include "spire-server.serviceAccountName" . }} diff --git a/charts/spire/charts/spire-server/values.yaml b/charts/spire/charts/spire-server/values.yaml index a7a24cc..6766dc8 100644 --- a/charts/spire/charts/spire-server/values.yaml +++ b/charts/spire/charts/spire-server/values.yaml @@ -26,6 +26,14 @@ kind: statefulset ## @param externalServer Deploy only the bundle ConfigMap, RBAC rules, and identity documents but not the server. Use in a nested setup where the server is external. externalServer: false +## @param externalServerSubject.kind RBAC subject kind the external (nested) server's downstream bindings are granted to. One of "User" (client-certificate identity, the historical default), "Group", or "ServiceAccount" (e.g. for a static-token kubeconfig). Only used when externalServer is true. +## @param externalServerSubject.name Name of the subject. For kind "User" it must match the CN of the client certificate the external server presents; for kind "Group" it is the group name (e.g. a certificate O value); for kind "ServiceAccount" it is the name of the (operator-managed, out-of-band) ServiceAccount. +## @param externalServerSubject.namespace Namespace of the ServiceAccount. Only used when kind is "ServiceAccount"; empty uses the server namespace. +externalServerSubject: + kind: User + name: spire-root + namespace: "" + ## @param imagePullSecrets [array] Pull secrets for images imagePullSecrets: [] diff --git a/tests/unit/spire_test.go b/tests/unit/spire_test.go index 1d3e437..a7248e7 100644 --- a/tests/unit/spire_test.go +++ b/tests/unit/spire_test.go @@ -288,4 +288,35 @@ spire-server: Expect(objs[serverTmpl]).Should(ContainSubstring("path: clusterb")) }) }) + Describe("spire-server.externalServerSubject", func() { + It("binds the external server's downstream RBAC to a ServiceAccount subject", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + externalServer: true + externalServerSubject: + kind: ServiceAccount + name: spire-external + namespace: spire-ext +`) + Expect(err).Should(Succeed()) + roles := objs["spire/charts/spire-server/templates/roles.yaml"] + Expect(roles).Should(ContainSubstring("kind: ServiceAccount")) + Expect(roles).Should(ContainSubstring(`name: "spire-external"`)) + Expect(roles).Should(ContainSubstring(`namespace: "spire-ext"`)) + }) + It("binds the external server's downstream RBAC to a Group subject", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + externalServer: true + externalServerSubject: + kind: Group + name: spire-admins +`) + Expect(err).Should(Succeed()) + roles := objs["spire/charts/spire-server/templates/roles.yaml"] + Expect(roles).Should(ContainSubstring("apiGroup: rbac.authorization.k8s.io")) + Expect(roles).Should(ContainSubstring("kind: Group")) + Expect(roles).Should(ContainSubstring(`name: "spire-admins"`)) + }) + }) })