diff --git a/charts/spire/charts/spire-server/templates/_helpers.tpl b/charts/spire/charts/spire-server/templates/_helpers.tpl index 47479ed..91969a5 100644 --- a/charts/spire/charts/spire-server/templates/_helpers.tpl +++ b/charts/spire/charts/spire-server/templates/_helpers.tpl @@ -179,6 +179,13 @@ Create the name of the service account to use {{ include "spire-server.fullname" . | trimSuffix "-server" }}-controller-manager {{- end }} +{{/* +Name of the chart-generated Secret holding the inline kubeConfigs entries. +*/}} +{{- define "spire-server.kubeconfigs-secret-name" -}} +{{ include "spire-server.fullname" . }}-kubeconfigs +{{- end }} + {{- define "spire-server.serviceAccountAllowedList" }} {{- $releaseNamespace := include "spire-server.agent-namespace" . }} {{- if ne (len .Values.nodeAttestor.k8sPSAT.serviceAccountAllowList) 0 }} diff --git a/charts/spire/charts/spire-server/templates/kubeconfig-secret.yaml b/charts/spire/charts/spire-server/templates/kubeconfig-secret.yaml index 1a553d7..eb87c6e 100644 --- a/charts/spire/charts/spire-server/templates/kubeconfig-secret.yaml +++ b/charts/spire/charts/spire-server/templates/kubeconfig-secret.yaml @@ -1,19 +1,37 @@ {{- $root := . }} -{{- with .Values.kubeConfigs }} +{{- $secretName := include "spire-server.kubeconfigs-secret-name" $root }} +{{- $inline := dict }} +{{- range $name, $value := .Values.kubeConfigs }} +{{- $present := list }} +{{- if hasKey $value "kubeConfig" }}{{ $present = append $present "kubeConfig" }}{{- end }} +{{- if hasKey $value "kubeConfigBase64" }}{{ $present = append $present "kubeConfigBase64" }}{{- end }} +{{- if hasKey $value "externalSecret" }}{{ $present = append $present "externalSecret" }}{{- end }} +{{- if ne (len $present) 1 }} +{{- fail (printf "kubeConfigs entry %q must set exactly one of kubeConfig, kubeConfigBase64, or externalSecret (got: %v)" $name $present) }} +{{- end }} +{{- if hasKey $value "externalSecret" }} +{{- if not $value.externalSecret.name }} +{{- fail (printf "kubeConfigs entry %q externalSecret.name cannot be empty" $name) }} +{{- end }} +{{- if eq $value.externalSecret.name $secretName }} +{{- fail (printf "kubeConfigs entry %q externalSecret.name %q is reserved for the chart-generated Secret; use a different name" $name $secretName) }} +{{- end }} +{{- else }} +{{- $_ := set $inline $name $value }} +{{- end }} +{{- end }} +{{- if gt (len $inline) 0 }} apiVersion: v1 kind: Secret metadata: - name: {{ include "spire-server.fullname" $root }}-kubeconfigs + name: {{ include "spire-server.kubeconfigs-secret-name" $root }} namespace: {{ include "spire-server.namespace" $root }} data: - {{- range $name, $value := . }} - {{- if and (hasKey . "kubeConfig") (hasKey . "kubeConfigBase64") }} - {{- fail "You can not use both kubeConfig and kubeConfigBase64" }} - {{- end }} - {{- if (hasKey . "kubeConfig") }} - {{ $name }}: {{ .kubeConfig | b64enc }} + {{- range $name, $value := $inline }} + {{- if hasKey $value "kubeConfig" }} + {{ $name }}: {{ $value.kubeConfig | b64enc }} {{- else }} - {{ $name }}: {{ .kubeConfigBase64 | nospace }} + {{ $name }}: {{ $value.kubeConfigBase64 | nospace }} {{- end }} {{- end }} {{- end }} diff --git a/charts/spire/charts/spire-server/templates/server-resource.yaml b/charts/spire/charts/spire-server/templates/server-resource.yaml index ecb31ad..fa918b9 100644 --- a/charts/spire/charts/spire-server/templates/server-resource.yaml +++ b/charts/spire/charts/spire-server/templates/server-resource.yaml @@ -601,8 +601,32 @@ spec: emptyDir: {} {{- if gt (len .Values.kubeConfigs) 0 }} - name: kubeconfigs - secret: - secretName: {{ include "spire-server.fullname" . }}-kubeconfigs + projected: + sources: + {{- $inlineNames := list }} + {{- range $name, $value := .Values.kubeConfigs }} + {{- if not (hasKey $value "externalSecret") }} + {{- $inlineNames = append $inlineNames $name }} + {{- end }} + {{- end }} + {{- if gt (len $inlineNames) 0 }} + - secret: + name: {{ include "spire-server.kubeconfigs-secret-name" . }} + items: + {{- range $name := ($inlineNames | sortAlpha) }} + - key: {{ $name }} + path: {{ $name }} + {{- end }} + {{- end }} + {{- range $name, $value := .Values.kubeConfigs }} + {{- if hasKey $value "externalSecret" }} + - secret: + name: {{ $value.externalSecret.name }} + items: + - key: {{ $value.externalSecret.key | default $name }} + path: {{ $name }} + {{- end }} + {{- end }} {{- end }} {{- if $pluginLoaderNeeded }} - name: plugins diff --git a/charts/spire/charts/spire-server/values.yaml b/charts/spire/charts/spire-server/values.yaml index fc27a51..49b7c2e 100644 --- a/charts/spire/charts/spire-server/values.yaml +++ b/charts/spire/charts/spire-server/values.yaml @@ -1544,6 +1544,9 @@ tests: tag: latest@sha256:90041f375e30f41aa7e0390075d8a69dc61900771d52fa98d63ee5d03d866a58 ## @param kubeConfigs [object] Manage additional kubeconfig files to talk to external Kubernetes clusters +## Each entry sets exactly one of kubeConfig, kubeConfigBase64, or externalSecret. Use externalSecret to +## reference a kubeconfig from an externally-managed Secret instead of embedding it in values; +## entries may reference different Secrets and mix with inline ones. kubeConfigs: {} # clustera: # kubeConfig: | @@ -1551,6 +1554,10 @@ kubeConfigs: {} # xxxxx # clusterb: # kubeConfigBase64: eXl5Cnl5eQo= +# clusterc: +# externalSecret: +# name: my-kubeconfigs-secret # name of the externally-managed Secret to read from +# key: clusterc # optional, defaults to the entry name spireIdentityExchange: ## @param spireIdentityExchange.enabled Enable the server side of the SPIRE Identity Exchange system diff --git a/tests/unit/spire_test.go b/tests/unit/spire_test.go index bee685c..1d3e437 100644 --- a/tests/unit/spire_test.go +++ b/tests/unit/spire_test.go @@ -257,4 +257,35 @@ spiffe-oidc-discovery-provider: Expect(serverCM).Should(ContainSubstring(`"jwt_issuer": "https://oidc-discovery.example.org"`)) }) }) + Describe("spire-server.kubeConfigs", func() { + secretTmpl := "spire/charts/spire-server/templates/kubeconfig-secret.yaml" + serverTmpl := "spire/charts/spire-server/templates/server-resource.yaml" + It("inline entry generates a Secret and a projected volume source referencing it", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + kubeConfigs: + clustera: + kubeConfig: | + apiVersion: v1 + kind: Config +`) + Expect(err).Should(Succeed()) + Expect(objs[secretTmpl]).Should(ContainSubstring("kind: Secret")) + Expect(objs[serverTmpl]).Should(ContainSubstring("projected:")) + Expect(objs[serverTmpl]).Should(ContainSubstring("path: clustera")) + }) + It("externalSecret entry wires a projected source and skips the generated Secret", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + kubeConfigs: + clusterb: + externalSecret: + name: my-ext-secret +`) + Expect(err).Should(Succeed()) + Expect(objs[secretTmpl]).ShouldNot(ContainSubstring("kind: Secret")) + Expect(objs[serverTmpl]).Should(ContainSubstring("name: my-ext-secret")) + Expect(objs[serverTmpl]).Should(ContainSubstring("path: clusterb")) + }) + }) })