diff --git a/charts/spire/charts/spire-server/README.md b/charts/spire/charts/spire-server/README.md index e7b71c4..55decff 100644 --- a/charts/spire/charts/spire-server/README.md +++ b/charts/spire/charts/spire-server/README.md @@ -501,7 +501,10 @@ In order to run Tornjak with simple HTTP Connection only, make sure you don't cr | `nodeAttestor.gcpIIT.metadataValueMaxSize` | Sets the maximum metadata value size considered by the plugin for selectors | `0` | | `nodeAttestor.gcpIIT.agentPathTemplate` | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | `""` | | `nodeAttestor.x509POP.enabled` | Enable the x509_popg node attestor | `false` | -| `nodeAttestor.x509POP.mode` | What mode to set the plugin to. Currently only spiffe mode is supported | `spiffe` | +| `nodeAttestor.x509POP.mode` | Plugin mode: spiffe (exchange) or externalPKI (enrollment CA bundle) | `spiffe` | +| `nodeAttestor.x509POP.caBundle` | CA bundle for externalPKI mode. Provide inline PEM contents or reference an existing ConfigMap. | | +| `nodeAttestor.x509POP.caBundle.bundle` | PEM CA bundle contents. When set, the chart creates and mounts a ConfigMap. | `""` | +| `nodeAttestor.x509POP.caBundle.existingConfigMap` | Name of a ConfigMap containing a `ca-bundle.pem` key with the PEM CA bundle. | `""` | | `nodeAttestor.x509POP.spiffePrefix` | What prefix to use when mode is spiffe | `/spire-exchange/k8s${HELM_ADD_CLUSTER_NAME}/` | | `nodeAttestor.x509POP.agentPathTemplate` | Override the default agent path template | `""` | | `nodeAttestor.x509POP.maxIntermediates` | Maximum number of intermediate certificates allowed in the certificate chain | `4` | diff --git a/charts/spire/charts/spire-server/templates/configmap.yaml b/charts/spire/charts/spire-server/templates/configmap.yaml index a029070..63d9f96 100644 --- a/charts/spire/charts/spire-server/templates/configmap.yaml +++ b/charts/spire/charts/spire-server/templates/configmap.yaml @@ -281,6 +281,12 @@ plugins: {{- if or (eq (.enabled | toString) "true") $root.Values.spireIdentityExchange.enabled }} x509pop: plugin_data: + {{- if eq .mode "externalPKI" }} + mode: external_pki + ca_bundle_path: "/run/spire/data/x509pop-ca-bundle.pem" + max_intermediates: {{ .maxIntermediates }} + max_rsa_key_size: {{ .maxRSAKeySize }} + {{- else }} mode: {{ .mode }} spiffe_prefix: {{ include "spire-server.identity-exchange-spiffe-prefix" $root | quote }} max_intermediates: {{ .maxIntermediates }} @@ -298,6 +304,7 @@ plugins: {{- $cn = printf "/%s" (include "spire-lib.cluster-name" $root) }} {{- end }} agent_path_template: {{ replace "${HELM_ADD_CLUSTER_NAME}" $cn $agentPathTemplate | quote }} + {{- end }} {{- end }} {{- end }} {{- with .Values.nodeAttestor.awsIID }} diff --git a/charts/spire/charts/spire-server/templates/server-resource.yaml b/charts/spire/charts/spire-server/templates/server-resource.yaml index fa918b9..1a5b969 100644 --- a/charts/spire/charts/spire-server/templates/server-resource.yaml +++ b/charts/spire/charts/spire-server/templates/server-resource.yaml @@ -408,6 +408,14 @@ spec: mountPath: /tmp-direct-hashes {{- end }} {{- end }} + {{- if and .Values.nodeAttestor.x509POP.enabled (eq .Values.nodeAttestor.x509POP.mode "externalPKI") }} + {{- with .Values.nodeAttestor.x509POP }} + - name: x509pop-ca-bundle + mountPath: /run/spire/data/x509pop-ca-bundle.pem + subPath: ca-bundle.pem + readOnly: true + {{- end }} + {{- end }} {{- if or .Values.federation.tls.certManager.enabled .Values.federation.tls.externalSecret.enabled }} - name: bundle-endpoint-tls mountPath: /bundle-endpoint-tls @@ -646,6 +654,17 @@ spec: name: {{ include "spire-server.fullname" . }}-tpm-direct-hash {{- end }} {{- end }} + {{- if and .Values.nodeAttestor.x509POP.enabled (eq .Values.nodeAttestor.x509POP.mode "externalPKI") }} + {{- with .Values.nodeAttestor.x509POP }} + - name: x509pop-ca-bundle + configMap: + {{- if .caBundle.bundle }} + name: {{ $fullname }}-x509pop-ca + {{- else if .caBundle.existingConfigMap }} + name: {{ .caBundle.existingConfigMap }} + {{- end }} + {{- end }} + {{- end }} {{- if .Values.federation.tls.certManager.enabled }} - name: bundle-endpoint-tls secret: diff --git a/charts/spire/charts/spire-server/templates/x509pop-configmap.yaml b/charts/spire/charts/spire-server/templates/x509pop-configmap.yaml new file mode 100644 index 0000000..f98d443 --- /dev/null +++ b/charts/spire/charts/spire-server/templates/x509pop-configmap.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.nodeAttestor.x509POP.enabled .Values.nodeAttestor.x509POP.caBundle.bundle }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "spire-server.fullname" . }}-x509pop-ca + namespace: {{ include "spire-server.namespace" . }} +data: + ca-bundle.pem: | + {{ .Values.nodeAttestor.x509POP.caBundle.bundle | nindent 4 }} +{{- end }} diff --git a/charts/spire/charts/spire-server/values.yaml b/charts/spire/charts/spire-server/values.yaml index e3fac03..151019e 100644 --- a/charts/spire/charts/spire-server/values.yaml +++ b/charts/spire/charts/spire-server/values.yaml @@ -1239,8 +1239,14 @@ nodeAttestor: x509POP: ## @param nodeAttestor.x509POP.enabled Enable the x509_popg node attestor enabled: false - ## @param nodeAttestor.x509POP.mode What mode to set the plugin to. Currently only spiffe mode is supported + ## @param nodeAttestor.x509POP.mode Plugin mode: spiffe (exchange) or externalPKI (enrollment CA bundle) mode: spiffe + ## @extra nodeAttestor.x509POP.caBundle CA bundle for externalPKI mode. Provide inline PEM contents or reference an existing ConfigMap. + caBundle: + ## @param nodeAttestor.x509POP.caBundle.bundle [nullable] PEM CA bundle contents. When set, the chart creates and mounts a ConfigMap. + bundle: "" + ## @param nodeAttestor.x509POP.caBundle.existingConfigMap [nullable] Name of a ConfigMap containing a `ca-bundle.pem` key with the PEM CA bundle. + existingConfigMap: "" ## @param nodeAttestor.x509POP.spiffePrefix What prefix to use when mode is spiffe spiffePrefix: "/spire-exchange/k8s${HELM_ADD_CLUSTER_NAME}/" ## @param nodeAttestor.x509POP.agentPathTemplate Override the default agent path template diff --git a/tests/unit/spire_test.go b/tests/unit/spire_test.go index a7248e7..eb6cabf 100644 --- a/tests/unit/spire_test.go +++ b/tests/unit/spire_test.go @@ -187,6 +187,52 @@ spire-server: Expect(notes).Should(ContainSubstring("Installed")) }) }) + Describe("spire-server.nodeAttestor.x509POP", func() { + It("renders externalPKI mode with chart-managed ca bundle", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + nodeAttestor: + k8sPSAT: + enabled: false + x509POP: + enabled: true + mode: externalPKI + caBundle: + bundle: | + -----BEGIN CERTIFICATE----- + MIIB... + -----END CERTIFICATE----- +`) + Expect(err).Should(Succeed()) + serverCM := objs["spire/charts/spire-server/templates/configmap.yaml"] + Expect(serverCM).Should(ContainSubstring(`"mode": "external_pki"`)) + Expect(serverCM).Should(ContainSubstring(`"ca_bundle_path": "/run/spire/data/x509pop-ca-bundle.pem"`)) + Expect(objs).Should(HaveKey("spire/charts/spire-server/templates/x509pop-configmap.yaml")) + serverResource := objs["spire/charts/spire-server/templates/server-resource.yaml"] + Expect(serverResource).Should(ContainSubstring("x509pop-ca-bundle")) + Expect(serverResource).Should(ContainSubstring("/run/spire/data/x509pop-ca-bundle.pem")) + }) + It("renders externalPKI mode with existing ConfigMap reference", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + nodeAttestor: + k8sPSAT: + enabled: false + x509POP: + enabled: true + mode: externalPKI + caBundle: + existingConfigMap: my-enrollment-ca +`) + Expect(err).Should(Succeed()) + serverCM := objs["spire/charts/spire-server/templates/configmap.yaml"] + Expect(serverCM).Should(ContainSubstring(`"mode": "external_pki"`)) + Expect(serverCM).Should(ContainSubstring(`"ca_bundle_path": "/run/spire/data/x509pop-ca-bundle.pem"`)) + Expect(objs["spire/charts/spire-server/templates/x509pop-configmap.yaml"]).ShouldNot(ContainSubstring("kind: ConfigMap")) + serverResource := objs["spire/charts/spire-server/templates/server-resource.yaml"] + Expect(serverResource).Should(ContainSubstring("name: my-enrollment-ca")) + }) + }) Describe("spire-server.nodeAttestor.awsIID.verifyOrganization", func() { It("emits verify_organization in server config JSON", func() { objs, err := ValueStringRender(chart, `