From 7687339420ddde842c86e90fffc20d6f88e08f33 Mon Sep 17 00:00:00 2001 From: kfox1111 Date: Thu, 25 Jun 2026 22:49:42 -0700 Subject: [PATCH] Enable easy plugin loading (#859) * Enable easy plugin loading customPluings have to be loaded into the main container somehow. Extend the existing cel plugin loader to allow users to easily specify an image to load it from. Signed-off-by: Kevin Fox * Add some missing bits Signed-off-by: Kevin Fox * Incorperate feedback Signed-off-by: Kevin Fox * Fix formatting Signed-off-by: Kevin Fox * Fix formatting Signed-off-by: Kevin Fox --------- Signed-off-by: Kevin Fox --- charts/spire-lib/templates/_helpers.tpl | 36 ++++++++-- charts/spire/charts/spire-agent/README.md | 4 ++ .../spire-agent/templates/configmap.yaml | 2 +- .../spire-agent/templates/daemonset.yaml | 62 ++++++++++++++++ charts/spire/charts/spire-agent/values.yaml | 11 +++ .../spire-server/templates/configmap.yaml | 2 +- .../templates/server-resource.yaml | 72 ++++++++++++++----- .../spire-plugins/node-agent-tpm/values.yaml | 10 +++ 8 files changed, 172 insertions(+), 27 deletions(-) diff --git a/charts/spire-lib/templates/_helpers.tpl b/charts/spire-lib/templates/_helpers.tpl index e705980..179c5c5 100644 --- a/charts/spire-lib/templates/_helpers.tpl +++ b/charts/spire-lib/templates/_helpers.tpl @@ -200,14 +200,20 @@ Take a copy of the config and merge in .Values.customPlugins and .Values.unsuppo */}} {{- define "spire-lib.config_merge" }} {{- $pluginsToMerge := dict "plugins" dict }} -{{- range $type, $val := .root.Values.customPlugins }} -{{- if . }} -{{- if eq $type "svidStore" }} -{{- $_ := set $pluginsToMerge.plugins "SVIDStore" (deepCopy $val) }} -{{- else }} -{{- $nt := printf "%s%s" (substr 0 1 $type | upper) (substr 1 -1 $type) }} -{{- $_ := set $pluginsToMerge.plugins $nt (deepCopy $val) }} +{{- range $type, $instances := .root.Values.customPlugins }} +{{- if $instances }} +{{- $nt := (eq $type "svidStore") | ternary "SVIDStore" (printf "%s%s" (substr 0 1 $type | upper) (substr 1 -1 $type)) }} +{{- $processedInstances := dict }} +{{- range $instanceName, $config := $instances }} +{{- $pluginData := deepCopy $config }} +{{- $hasImage := hasKey $pluginData "image" }} +{{- $_ := unset $pluginData "image" }} +{{- if and $hasImage $pluginData.plugin_cmd }} +{{- $_ := set $pluginData "plugin_cmd" (printf "/plugins/%s/%s" $type $instanceName) }} +{{- end }} +{{- $_ := set $processedInstances $instanceName $pluginData }} {{- end }} +{{- $_ := set $pluginsToMerge.plugins $nt $processedInstances }} {{- end }} {{- end }} {{- range $type, $val := .root.Values.unsupportedBuiltInPlugins }} @@ -224,6 +230,22 @@ Take a copy of the config and merge in .Values.customPlugins and .Values.unsuppo {{- $newConfig | toYaml }} {{- end }} +{{/* +Find all customPlugins that specify an image, and build a list of binaries to copy into the plugin volume in the plugin loader. +*/}} +{{- define "spire-lib.extract_custom_plugin_images" }} +{{- $pluginList := list }} +{{- range $type, $instances := .Values.customPlugins }} +{{- range $instanceName, $config := $instances }} +{{- if $config.image }} +{{- $entry := dict "plugin_cmd" $config.plugin_cmd "image" $config.image "name" (printf "%s/%s" $type $instanceName) }} +{{- $pluginList = append $pluginList $entry }} +{{- end }} +{{- end }} +{{- end }} +{{- $pluginList | toYaml }} +{{- end }} + {{/* Take a copy of the plugin section and return a yaml string based version reformatted from a dict of dicts to a dict of lists of dicts diff --git a/charts/spire/charts/spire-agent/README.md b/charts/spire/charts/spire-agent/README.md index bac3e38..4764adb 100644 --- a/charts/spire/charts/spire-agent/README.md +++ b/charts/spire/charts/spire-agent/README.md @@ -165,6 +165,10 @@ A Helm chart to install the SPIRE agent. | `tools.kubectl.image.repository` | The repository within the registry | `kubectl` | | `tools.kubectl.image.pullPolicy` | The image pull policy | `IfNotPresent` | | `tools.kubectl.image.tag` | Overrides the image tag whose default is the chart appVersion | `""` | +| `tools.busybox.image.registry` | The OCI registry to pull the image from | `""` | +| `tools.busybox.image.repository` | The repository within the registry | `busybox` | +| `tools.busybox.image.pullPolicy` | The image pull policy | `IfNotPresent` | +| `tools.busybox.image.tag` | Overrides the image tag whose default is the chart appVersion | `1.37.0-uclibc` | | `sockets.hostBasePath` | Path on which the agent socket is made available when admin.mountOnHost is true | `/run/spire/agent/sockets` | | `sockets.admin.enabled` | Enable the admin socket. Useful for admin tasks or the Delegated Identity API. | `false` | | `sockets.admin.mountOnHost` | Enable the admin socket to be visible on the host. | `false` | diff --git a/charts/spire/charts/spire-agent/templates/configmap.yaml b/charts/spire/charts/spire-agent/templates/configmap.yaml index 7ff8a6b..77252bb 100644 --- a/charts/spire/charts/spire-agent/templates/configmap.yaml +++ b/charts/spire/charts/spire-agent/templates/configmap.yaml @@ -14,7 +14,7 @@ {{- fail (printf "plugin_checksum is a required field.") }} {{- end }} {{- range $sname, $svals := $nval }} -{{- if not (has $sname (list "plugin_cmd" "plugin_checksum" "plugin_data")) }} +{{- if not (has $sname (list "plugin_cmd" "plugin_checksum" "plugin_data" "image")) }} {{- fail (printf "Unknown plugin setting specified: %s" $sname) }} {{- end }} {{- end }} diff --git a/charts/spire/charts/spire-agent/templates/daemonset.yaml b/charts/spire/charts/spire-agent/templates/daemonset.yaml index 477c05b..1668a3a 100644 --- a/charts/spire/charts/spire-agent/templates/daemonset.yaml +++ b/charts/spire/charts/spire-agent/templates/daemonset.yaml @@ -40,6 +40,8 @@ {{- end }} {{- $socketAlternateNames := index (include "spire-agent.socket-alternate-names" . | fromYaml) "names" }} {{- $socketPath := include "spire-agent.socket-path" . }} +{{- $pluginsToLoad := include "spire-lib.extract_custom_plugin_images" . | fromYamlArray }} +{{- $pluginLoaderNeeded := gt (len $pluginsToLoad) 0 }} --- apiVersion: apps/v1 kind: DaemonSet @@ -100,6 +102,56 @@ spec: {{- toYaml .Values.hostAliases | nindent 8 }} {{- end }} initContainers: + {{- if $pluginLoaderNeeded }} + - name: init-plugins + securityContext: + {{- include "spire-lib.securitycontext" . | nindent 12 }} + image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" .Values.tools.busybox.image "global" .Values.global) }} + # SPIRE must be able to fork the plugin directly within its container. First copy a busybox so that the plugin can be copied into the right place. + command: + - busybox + - sh + - -ec + - | + cp -a /bin/busybox /plugins/busybox + volumeMounts: + - name: plugins + mountPath: /plugins + imagePullPolicy: {{ .Values.tools.busybox.image.pullPolicy }} + {{- end }} + {{- range $idx, $plugin := $pluginsToLoad }} + - name: {{ printf "init-plugin-%d" $idx }} + securityContext: + {{- include "spire-lib.securitycontext" $ | nindent 12 }} + image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" $plugin.image "global" $.Values.global) }} + command: + - /plugins/busybox + - sh + - -ec + - | + /plugins/busybox mkdir -p /plugins/{{ dir $plugin.name }} + /plugins/busybox cp -a {{ $plugin.plugin_cmd }} /plugins/{{ $plugin.name }} + volumeMounts: + - name: plugins + mountPath: /plugins + imagePullPolicy: {{ $plugin.image.pullPolicy }} + {{- end }} + {{- if $pluginLoaderNeeded }} + - name: finalize-plugins + securityContext: + {{- include "spire-lib.securitycontext" . | nindent 12 }} + image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" .Values.tools.busybox.image "global" .Values.global) }} + command: + - busybox + - sh + - -ec + - | + rm -f /plugins/busybox + volumeMounts: + - name: plugins + mountPath: /plugins + imagePullPolicy: {{ .Values.tools.busybox.image.pullPolicy }} + {{- end }} {{- if or (eq .Values.workloadAttestors.k8s.verification.type "hostCert") (eq .Values.workloadAttestors.k8s.verification.type "auto") }} - name: gather-host-cert securityContext: @@ -326,6 +378,11 @@ spec: mountPath: /run/spire/bundle {{- end }} {{- end }} + {{- if $pluginLoaderNeeded }} + - name: plugins + mountPath: /plugins + readOnly: true + {{- end }} {{- if .Values.nodeAttestor.tpmDirect.enabled }} - name: tpm-direct mountPath: /tpm @@ -419,6 +476,11 @@ spec: emptyDir: {} {{- end }} {{- end }} + {{- if $pluginLoaderNeeded }} + - name: plugins + mountPath: /plugins + readOnly: true + {{- end }} {{- if .Values.sockets.admin.mountOnHost }} - name: spire-agent-admin-socket-dir hostPath: diff --git a/charts/spire/charts/spire-agent/values.yaml b/charts/spire/charts/spire-agent/values.yaml index 2f82e29..b6663a5 100644 --- a/charts/spire/charts/spire-agent/values.yaml +++ b/charts/spire/charts/spire-agent/values.yaml @@ -447,6 +447,17 @@ tools: repository: kubectl pullPolicy: IfNotPresent tag: "" + busybox: + ## @param tools.busybox.image.registry The OCI registry to pull the image from + ## @param tools.busybox.image.repository The repository within the registry + ## @param tools.busybox.image.pullPolicy The image pull policy + ## @param tools.busybox.image.tag Overrides the image tag whose default is the chart appVersion + ## + image: + registry: "" + repository: busybox + pullPolicy: IfNotPresent + tag: 1.37.0-uclibc sockets: ## @param sockets.hostBasePath Path on which the agent socket is made available when admin.mountOnHost is true diff --git a/charts/spire/charts/spire-server/templates/configmap.yaml b/charts/spire/charts/spire-server/templates/configmap.yaml index c433543..c0e107f 100644 --- a/charts/spire/charts/spire-server/templates/configmap.yaml +++ b/charts/spire/charts/spire-server/templates/configmap.yaml @@ -16,7 +16,7 @@ {{- fail (printf "plugin_checksum is a required field.") }} {{- end }} {{- range $sname, $svals := $nval }} -{{- if not (has $sname (list "plugin_cmd" "plugin_checksum" "plugin_data")) }} +{{- if not (has $sname (list "plugin_cmd" "plugin_checksum" "plugin_data" "image")) }} {{- fail (printf "Unknown plugin setting specified: %s" $sname) }} {{- 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 b849916..6fcb3ed 100644 --- a/charts/spire/charts/spire-server/templates/server-resource.yaml +++ b/charts/spire/charts/spire-server/templates/server-resource.yaml @@ -64,6 +64,8 @@ {{- fail "clientKeyPath can only be set with database type mysql or aws_mysql." }} {{- end }} {{- end }} +{{- $pluginsToLoad := include "spire-lib.extract_custom_plugin_images" . | fromYamlArray }} +{{- $pluginLoaderNeeded := or .Values.credentialComposer.cel.enabled (gt (len $pluginsToLoad) 0) }} {{- if not .Values.externalServer }} apiVersion: apps/v1 {{- if eq .Values.kind "statefulset" }} @@ -121,10 +123,10 @@ spec: securityContext: {{- $podSecurityContext | toYaml | nindent 8 }} {{- include "spire-lib.default_cluster_priority_class_name" . | nindent 6 }} - {{- if or (gt (len .Values.initContainers) 0) (and .Values.upstreamAuthority.certManager.enabled .Values.upstreamAuthority.certManager.ca.create) .Values.nodeAttestor.tpmDirect.enabled .Values.credentialComposer.cel.enabled $needsChown }} + {{- if or (gt (len .Values.initContainers) 0) (and .Values.upstreamAuthority.certManager.enabled .Values.upstreamAuthority.certManager.ca.create) .Values.nodeAttestor.tpmDirect.enabled $pluginLoaderNeeded $needsChown }} initContainers: - {{- if .Values.credentialComposer.cel.enabled }} - - name: init-cel + {{- if $pluginLoaderNeeded }} + - name: init-plugins securityContext: {{- include "spire-lib.securitycontext" . | nindent 12 }} image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" .Values.tools.busybox.image "global" .Values.global) }} @@ -134,28 +136,62 @@ spec: - sh - -ec - | - cp -a /bin/busybox /cel/busybox + cp -a /bin/busybox /plugins/busybox volumeMounts: - - name: cel - mountPath: /cel - imagePullPolicy: {{ .Values.credentialComposer.cel.image.pullPolicy }} - - name: init-cel2 + - name: plugins + mountPath: /plugins + imagePullPolicy: {{ .Values.tools.busybox.image.pullPolicy }} + {{- end }} + {{- if .Values.credentialComposer.cel.enabled }} + - name: init-cel securityContext: {{- include "spire-lib.securitycontext" . | nindent 12 }} image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" .Values.credentialComposer.cel.image "global" .Values.global) }} # Second, use the previously copied busybox to copy the plugin into a volume that can be mounted where SPIRE can execute it. command: - - /cel/busybox + - /plugins/busybox - sh - -ec - | - /cel/busybox cp -a {{ .Values.credentialComposer.cel.pluginPath }} /cel/credentialcomposer-cel - /cel/busybox rm -f /cel/busybox + /plugins/busybox cp -a {{ .Values.credentialComposer.cel.pluginPath }} /plugins/credentialcomposer-cel volumeMounts: - - name: cel - mountPath: /cel + - name: plugins + mountPath: /plugins imagePullPolicy: {{ .Values.credentialComposer.cel.image.pullPolicy }} {{- end }} + {{- range $idx, $plugin := $pluginsToLoad }} + - name: {{ printf "init-plugin-%d" $idx }} + securityContext: + {{- include "spire-lib.securitycontext" $ | nindent 12 }} + image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" $plugin.image "global" $.Values.global) }} + command: + - /plugins/busybox + - sh + - -ec + - | + /plugins/busybox mkdir -p /plugins/{{ dir $plugin.name }} + /plugins/busybox cp -a {{ $plugin.plugin_cmd }} /plugins/{{ $plugin.name }} + volumeMounts: + - name: plugins + mountPath: /plugins + imagePullPolicy: {{ $plugin.image.pullPolicy }} + {{- end }} + {{- if $pluginLoaderNeeded }} + - name: finalize-plugins + securityContext: + {{- include "spire-lib.securitycontext" . | nindent 12 }} + image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" .Values.tools.busybox.image "global" .Values.global) }} + command: + - busybox + - sh + - -ec + - | + rm -f /plugins/busybox + volumeMounts: + - name: plugins + mountPath: /plugins + imagePullPolicy: {{ .Values.tools.busybox.image.pullPolicy }} + {{- end }} {{- if .Values.nodeAttestor.tpmDirect.enabled }} - name: init-tpm-direct securityContext: @@ -337,9 +373,9 @@ spec: mountPath: /kubeconfigs readOnly: true {{- end }} - {{- if .Values.credentialComposer.cel.enabled }} - - name: cel - mountPath: /cel + {{- if $pluginLoaderNeeded }} + - name: plugins + mountPath: /plugins readOnly: true {{- end }} {{- if .Values.nodeAttestor.tpmDirect.enabled }} @@ -546,8 +582,8 @@ spec: secret: secretName: {{ include "spire-server.fullname" . }}-kubeconfigs {{- end }} - {{- if .Values.credentialComposer.cel.enabled }} - - name: cel + {{- if $pluginLoaderNeeded }} + - name: plugins emptyDir: {} {{- end }} {{- if .Values.nodeAttestor.tpmDirect.enabled }} diff --git a/examples/spire-plugins/node-agent-tpm/values.yaml b/examples/spire-plugins/node-agent-tpm/values.yaml index 7024773..3d0d8a7 100644 --- a/examples/spire-plugins/node-agent-tpm/values.yaml +++ b/examples/spire-plugins/node-agent-tpm/values.yaml @@ -18,6 +18,11 @@ spire-server: customPlugins: nodeAttestor: tpm: + image: + registry: docker.io + repository: + tag: + pullPolicy: Always plugin_cmd: /bin/tpm_attestor_server plugin_checksum: 97442358ae946e3fb8f2464432b8c23efdc0b5d44ec1eea27babe59ef646cc2f plugin_data: @@ -32,6 +37,11 @@ spire-agent: customPlugins: nodeAttestor: tpm: + image: + registry: docker.io + repository: + tag: + pullPolicy: Always plugin_cmd: /bin/tpm_attestor_agent plugin_checksum: bb7be714c27452231a6c7764b65912ce0cdeb66ff2a2c688d3e88bd0bd17d138 plugin_data: {}