Skip to content

Commit 9dd5d8f

Browse files
fabiendupontclaude
andcommitted
Add spiffe-helper sidecar injection webhook
Add a MutatingAdmissionWebhook that automatically injects spiffe-helper init containers and sidecars into pods annotated with spiffe.openshift.io/inject-helper: "true", eliminating manual sidecar configuration for applications that need on-disk TLS certificates from SPIRE. The webhook injects: - Init container (spiffe-helper-init) for initial SVID fetch - Sidecar container (spiffe-helper) for continuous certificate rotation - CSI volume (csi.spiffe.io) for SPIRE workload API access - EmptyDir volume for certificate storage - ConfigMap volume for helper configuration Optional annotations allow customizing the cert directory (spiffe.openshift.io/cert-dir) and ConfigMap name (spiffe.openshift.io/helper-config). Closes #96 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Fabien Dupont <fdupont@redhat.com>
1 parent 809eb75 commit 9dd5d8f

18 files changed

Lines changed: 2023 additions & 11 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
apiVersion: admissionregistration.k8s.io/v1
2+
kind: MutatingWebhookConfiguration
3+
metadata:
4+
name: spiffe-helper-sidecar-injector
5+
labels:
6+
app.kubernetes.io/name: spiffe-helper
7+
app.kubernetes.io/instance: spire
8+
app.kubernetes.io/managed-by: "zero-trust-workload-identity-manager"
9+
app.kubernetes.io/part-of: "zero-trust-workload-identity-manager"
10+
annotations:
11+
service.beta.openshift.io/inject-cabundle: "true"
12+
webhooks:
13+
- admissionReviewVersions: ["v1"]
14+
clientConfig:
15+
service:
16+
name: spiffe-helper-webhook
17+
namespace: zero-trust-workload-identity-manager
18+
path: /mutate-pods-spiffe-helper
19+
failurePolicy: Ignore
20+
name: spiffe-helper.spiffe.openshift.io
21+
rules:
22+
- apiGroups: [""]
23+
apiVersions: ["v1"]
24+
operations: ["CREATE"]
25+
resources: ["pods"]
26+
sideEffects: None
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: spiffe-helper-webhook
5+
namespace: zero-trust-workload-identity-manager
6+
labels:
7+
app.kubernetes.io/name: spiffe-helper
8+
app.kubernetes.io/instance: spire
9+
app.kubernetes.io/managed-by: "zero-trust-workload-identity-manager"
10+
app.kubernetes.io/part-of: "zero-trust-workload-identity-manager"
11+
spec:
12+
replicas: 1
13+
selector:
14+
matchLabels:
15+
app.kubernetes.io/name: spiffe-helper-webhook
16+
template:
17+
metadata:
18+
labels:
19+
app.kubernetes.io/name: spiffe-helper-webhook
20+
app.kubernetes.io/instance: spire
21+
app.kubernetes.io/managed-by: "zero-trust-workload-identity-manager"
22+
app.kubernetes.io/part-of: "zero-trust-workload-identity-manager"
23+
spec:
24+
serviceAccountName: controller-manager
25+
securityContext:
26+
runAsNonRoot: true
27+
seccompProfile:
28+
type: RuntimeDefault
29+
containers:
30+
- name: webhook
31+
image: controller:latest
32+
command:
33+
- /usr/bin/zero-trust-workload-identity-manager
34+
args:
35+
- --serve-webhook
36+
- --webhook-cert-dir=/tmp/k8s-webhook-server/serving-certs
37+
- --health-probe-bind-address=:8082
38+
ports:
39+
- containerPort: 9443
40+
name: webhook
41+
protocol: TCP
42+
- containerPort: 8082
43+
name: health
44+
protocol: TCP
45+
env:
46+
- name: OPERATOR_NAMESPACE
47+
valueFrom:
48+
fieldRef:
49+
fieldPath: metadata.namespace
50+
- name: RELATED_IMAGE_SPIFFE_HELPER
51+
value: ghcr.io/spiffe/spiffe-helper:0.11.0
52+
securityContext:
53+
readOnlyRootFilesystem: true
54+
allowPrivilegeEscalation: false
55+
capabilities:
56+
drop:
57+
- "ALL"
58+
livenessProbe:
59+
httpGet:
60+
path: /healthz
61+
port: 8082
62+
initialDelaySeconds: 10
63+
periodSeconds: 20
64+
readinessProbe:
65+
httpGet:
66+
path: /readyz
67+
port: 8082
68+
initialDelaySeconds: 5
69+
periodSeconds: 10
70+
resources:
71+
requests:
72+
cpu: 50m
73+
memory: 64Mi
74+
volumeMounts:
75+
- name: webhook-certs
76+
mountPath: /tmp/k8s-webhook-server/serving-certs
77+
readOnly: true
78+
volumes:
79+
- name: webhook-certs
80+
secret:
81+
secretName: spiffe-helper-webhook-certs
82+
terminationGracePeriodSeconds: 10
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: spiffe-helper-webhook
5+
namespace: zero-trust-workload-identity-manager
6+
labels:
7+
app.kubernetes.io/name: spiffe-helper
8+
app.kubernetes.io/instance: spire
9+
app.kubernetes.io/managed-by: "zero-trust-workload-identity-manager"
10+
app.kubernetes.io/part-of: "zero-trust-workload-identity-manager"
11+
annotations:
12+
service.beta.openshift.io/serving-cert-secret-name: spiffe-helper-webhook-certs
13+
spec:
14+
type: ClusterIP
15+
ports:
16+
- name: https
17+
port: 443
18+
targetPort: 9443
19+
protocol: TCP
20+
selector:
21+
app.kubernetes.io/name: spiffe-helper-webhook

bundle/manifests/zero-trust-workload-identity-manager.clusterserviceversion.yaml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ metadata:
211211
capabilities: Basic Install
212212
console.openshift.io/disable-operand-delete: "true"
213213
containerImage: openshift.io/zero-trust-workload-identity-manager
214-
createdAt: "2026-02-23T10:55:35Z"
214+
createdAt: "2026-03-04T19:58:03Z"
215215
features.operators.openshift.io/cnf: "false"
216216
features.operators.openshift.io/cni: "false"
217217
features.operators.openshift.io/csi: "true"
@@ -344,6 +344,7 @@ spec:
344344
- apiGroups:
345345
- ""
346346
resourceNames:
347+
- spiffe-helper-webhook
347348
- spire-agent
348349
- spire-controller-manager-webhook
349350
- spire-server
@@ -354,6 +355,25 @@ spec:
354355
- delete
355356
- get
356357
- update
358+
- apiGroups:
359+
- admissionregistration.k8s.io
360+
resources:
361+
- mutatingwebhookconfigurations
362+
verbs:
363+
- create
364+
- get
365+
- list
366+
- watch
367+
- apiGroups:
368+
- admissionregistration.k8s.io
369+
resourceNames:
370+
- spiffe-helper-sidecar-injector
371+
resources:
372+
- mutatingwebhookconfigurations
373+
verbs:
374+
- delete
375+
- patch
376+
- update
357377
- apiGroups:
358378
- admissionregistration.k8s.io
359379
resources:
@@ -397,6 +417,7 @@ spec:
397417
- apiGroups:
398418
- apps
399419
resourceNames:
420+
- spiffe-helper-webhook
400421
- spire-spiffe-oidc-discovery-provider
401422
resources:
402423
- deployments
@@ -707,6 +728,10 @@ spec:
707728
value: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.9.4
708729
- name: RELATED_IMAGE_SPIFFE_CSI_INIT_CONTAINER
709730
value: registry.access.redhat.com/ubi9:latest
731+
- name: RELATED_IMAGE_SPIFFE_HELPER
732+
value: ghcr.io/spiffe/spiffe-helper:0.11.0
733+
- name: RELATED_IMAGE_SPIFFE_HELPER_WEBHOOK
734+
value: controller:latest
710735
- name: OPERATOR_LOG_LEVEL
711736
value: "2"
712737
- name: METRICS_BIND_ADDRESS
@@ -799,4 +824,8 @@ spec:
799824
name: node-driver-registrar
800825
- image: registry.access.redhat.com/ubi9:latest
801826
name: spiffe-csi-init-container
827+
- image: ghcr.io/spiffe/spiffe-helper:0.11.0
828+
name: spiffe-helper
829+
- image: controller:latest
830+
name: spiffe-helper-webhook
802831
version: 1.0.0

cmd/zero-trust-workload-identity-manager/main.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
operatoropenshiftiov1alpha1 "github.com/openshift/zero-trust-workload-identity-manager/api/v1alpha1"
4444
customClient "github.com/openshift/zero-trust-workload-identity-manager/pkg/client"
4545
spiffeCsiDriverController "github.com/openshift/zero-trust-workload-identity-manager/pkg/controller/spiffe-csi-driver"
46+
spiffeHelperController "github.com/openshift/zero-trust-workload-identity-manager/pkg/controller/spiffe-helper"
4647
spireAgentController "github.com/openshift/zero-trust-workload-identity-manager/pkg/controller/spire-agent"
4748
spireOIDCDiscoveryProviderController "github.com/openshift/zero-trust-workload-identity-manager/pkg/controller/spire-oidc-discovery-provider"
4849
spireServerController "github.com/openshift/zero-trust-workload-identity-manager/pkg/controller/spire-server"
@@ -88,6 +89,8 @@ func main() {
8889
enableHTTP2 bool
8990
logLevel int
9091
metricsCerts string
92+
serveWebhook bool
93+
webhookCertDir string
9194
metricsTLSOpts []func(*tls.Config)
9295
webhookTLSOpts []func(*tls.Config)
9396
)
@@ -105,6 +108,10 @@ func main() {
105108
flag.StringVar(&metricsCerts, "metrics-cert-dir", "",
106109
"Secret name containing the certificates for the metrics server which should be present in operator namespace. "+
107110
"If not provided self-signed certificates will be used")
111+
flag.BoolVar(&serveWebhook, "serve-webhook", false,
112+
"Run in webhook server mode: only serve admission webhooks, skip controllers")
113+
flag.StringVar(&webhookCertDir, "webhook-cert-dir", "/tmp/k8s-webhook-server/serving-certs",
114+
"Directory containing TLS certificates for the webhook server")
108115
opts := zap.Options{
109116
Development: true,
110117
}
@@ -122,6 +129,12 @@ func main() {
122129
}
123130
setupLog.Info("Operator namespace configured", "namespace", operatorNamespace)
124131

132+
// Webhook-only mode: serve admission webhooks without controllers
133+
if serveWebhook {
134+
runWebhookServer(webhookCertDir, probeAddr, enableHTTP2, logLevel)
135+
return
136+
}
137+
125138
if !enableHTTP2 {
126139
// if the enable-http2 flag is false (the default), http/2 should be disabled
127140
// due to its vulnerabilities.
@@ -270,6 +283,14 @@ func main() {
270283
exitOnError(err, "unable to setup spire OIDC discovery provider controller manager")
271284
}
272285

286+
spiffeHelperControllerManager, err := spiffeHelperController.New(mgr)
287+
if err != nil {
288+
exitOnError(err, "unable to set up spiffe helper controller manager")
289+
}
290+
if err = spiffeHelperControllerManager.SetupWithManager(mgr); err != nil {
291+
exitOnError(err, "unable to setup spiffe helper controller manager")
292+
}
293+
273294
if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
274295
exitOnError(err, "unable to set up health check")
275296
}
@@ -290,3 +311,43 @@ func exitOnError(err error, logMessage string) {
290311
os.Exit(1)
291312
}
292313
}
314+
315+
// runWebhookServer starts a minimal manager that only serves admission webhooks
316+
func runWebhookServer(certDir, probeAddr string, enableHTTP2 bool, logLevel int) {
317+
setupLog.Info("starting in webhook server mode")
318+
319+
var webhookTLSOpts []func(*tls.Config)
320+
if !enableHTTP2 {
321+
webhookTLSOpts = append(webhookTLSOpts, func(c *tls.Config) {
322+
c.NextProtos = []string{"http/1.1"}
323+
})
324+
}
325+
326+
webhookServer := webhook.NewServer(webhook.Options{
327+
TLSOpts: webhookTLSOpts,
328+
CertDir: certDir,
329+
})
330+
331+
config := ctrl.GetConfigOrDie()
332+
mgr, err := ctrl.NewManager(config, ctrl.Options{
333+
Scheme: scheme,
334+
WebhookServer: webhookServer,
335+
HealthProbeBindAddress: probeAddr,
336+
Metrics: metricsserver.Options{BindAddress: "0"},
337+
})
338+
exitOnError(err, "unable to start webhook manager")
339+
340+
spiffeHelperInjector := spiffeHelperController.NewSpiffeHelperInjector()
341+
mgr.GetWebhookServer().Register("/mutate-pods-spiffe-helper", &webhook.Admission{Handler: spiffeHelperInjector})
342+
343+
if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
344+
exitOnError(err, "unable to set up health check")
345+
}
346+
if err = mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
347+
exitOnError(err, "unable to set up ready check")
348+
}
349+
350+
setupLog.Info("starting webhook server")
351+
err = mgr.Start(ctrl.SetupSignalHandler())
352+
exitOnError(err, "problem running webhook server")
353+
}

config/manager/manager.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ spec:
9292
value: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.9.4
9393
- name: RELATED_IMAGE_SPIFFE_CSI_INIT_CONTAINER
9494
value: registry.access.redhat.com/ubi9:latest
95+
- name: RELATED_IMAGE_SPIFFE_HELPER
96+
value: ghcr.io/spiffe/spiffe-helper:0.11.0
97+
- name: RELATED_IMAGE_SPIFFE_HELPER_WEBHOOK
98+
value: controller:latest
9599
- name: OPERATOR_LOG_LEVEL
96100
value: "2"
97101
- name: METRICS_BIND_ADDRESS

config/rbac/role.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ rules:
6767
- apiGroups:
6868
- ""
6969
resourceNames:
70+
- spiffe-helper-webhook
7071
- spire-agent
7172
- spire-controller-manager-webhook
7273
- spire-server
@@ -77,6 +78,25 @@ rules:
7778
- delete
7879
- get
7980
- update
81+
- apiGroups:
82+
- admissionregistration.k8s.io
83+
resources:
84+
- mutatingwebhookconfigurations
85+
verbs:
86+
- create
87+
- get
88+
- list
89+
- watch
90+
- apiGroups:
91+
- admissionregistration.k8s.io
92+
resourceNames:
93+
- spiffe-helper-sidecar-injector
94+
resources:
95+
- mutatingwebhookconfigurations
96+
verbs:
97+
- delete
98+
- patch
99+
- update
80100
- apiGroups:
81101
- admissionregistration.k8s.io
82102
resources:
@@ -120,6 +140,7 @@ rules:
120140
- apiGroups:
121141
- apps
122142
resourceNames:
143+
- spiffe-helper-webhook
123144
- spire-spiffe-oidc-discovery-provider
124145
resources:
125146
- deployments

0 commit comments

Comments
 (0)