Skip to content

Commit 6e0d89b

Browse files
committed
coco: introducing the hello-coco app
Add hello-coco Helm chart demonstrating SPIRE agent deployment in confidential containers using x509pop node attestation. The chart deploys a test pod in a CoCo peer-pod (confidential VM with AMD SNP or Intel TDX) that fetches SPIRE agent certificates from KBS after TEE attestation, establishing hardware as the root of trust instead of Kubernetes. The pod contains three containers: init container fetches sealed secrets from KBS, SPIRE agent uses x509pop for node attestation, and test workload receives SPIFFE SVIDs via unix attestation. This validates the complete integration flow between ZTVP and CoCo components. Note: This could be dropped, if we stick with only the todoapp. Signed-off-by: Beraldo Leal <bleal@redhat.com>
1 parent eccf156 commit 6e0d89b

File tree

6 files changed

+313
-0
lines changed

6 files changed

+313
-0
lines changed

charts/hello-coco/Chart.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: v2
2+
name: hello-coco
3+
description: A Helm chart for SPIRE Agent CoCo test pod demonstrates x509pop attestation with KBS
4+
type: application
5+
version: 0.0.1
6+
maintainers:
7+
- name: Beraldo Leal
8+
email: bleal@redhat.com
9+
- name: Chris Butler
10+
email: chris.butler@redhat.com
11+
keywords:
12+
- spire
13+
- coco
14+
- confidentialcontainers
15+
- attestation
16+
- x509pop
17+
annotations:
18+
category: Test
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: spire-agent-coco
5+
namespace: zero-trust-workload-identity-manager
6+
data:
7+
agent.conf: |
8+
{
9+
"agent": {
10+
"data_dir": "/var/lib/spire",
11+
"log_level": "debug",
12+
"retry_bootstrap": true,
13+
"server_address": "spire-server.apps.{{ .Values.global.clusterDomain }}",
14+
"server_port": "443",
15+
"socket_path": "/tmp/spire-agent/public/spire-agent.sock",
16+
"trust_bundle_path": "/run/spire/bundle/bundle.crt",
17+
"trust_domain": "apps.{{ .Values.global.clusterDomain }}"
18+
},
19+
"health_checks": {
20+
"bind_address": "0.0.0.0",
21+
"bind_port": 9982,
22+
"listener_enabled": true,
23+
"live_path": "/live",
24+
"ready_path": "/ready"
25+
},
26+
"plugins": {
27+
"KeyManager": [
28+
{
29+
"disk": {
30+
"plugin_data": {
31+
"directory": "/var/lib/spire"
32+
}
33+
}
34+
}
35+
],
36+
"NodeAttestor": [
37+
{
38+
"x509pop": {
39+
"plugin_data": {
40+
"private_key_path": "/sealed/key.pem",
41+
"certificate_path": "/sealed/cert.pem"
42+
}
43+
}
44+
}
45+
],
46+
"WorkloadAttestor": [
47+
{
48+
"unix": {
49+
"plugin_data": {}
50+
}
51+
}
52+
]
53+
},
54+
"telemetry": {
55+
"Prometheus": {
56+
"host": "0.0.0.0",
57+
"port": "9402"
58+
}
59+
}
60+
}
61+
---
62+
apiVersion: v1
63+
kind: ConfigMap
64+
metadata:
65+
name: spiffe-helper-config
66+
namespace: zero-trust-workload-identity-manager
67+
data:
68+
helper.conf: |-
69+
agent_address = "/tmp/spire-agent/public/spire-agent.sock"
70+
cmd = ""
71+
cmd_args = ""
72+
cert_dir = "/svids"
73+
renew_signal = ""
74+
svid_file_name = "svid.pem"
75+
svid_key_file_name = "svid_key.pem"
76+
svid_bundle_file_name = "svid_bundle.pem"
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# SPIRE Agent with x509pop attestation running in CoCo peer pod.
2+
# Uses CDH sealed secrets for agent credentials (cert/key fetched from KBS after TEE attestation).
3+
apiVersion: v1
4+
kind: Pod
5+
metadata:
6+
name: spire-agent-cc
7+
namespace: zero-trust-workload-identity-manager
8+
labels:
9+
app: spire-agent-cc
10+
spec:
11+
runtimeClassName: kata-remote
12+
# shareProcessNamespace allows SPIRE agent to inspect workload processes for unix attestation
13+
# This is secure because the real isolation boundary is the confidential VM (peer-pod with TEE),
14+
# not individual containers. All containers in this pod are part of the same trust boundary.
15+
shareProcessNamespace: true
16+
serviceAccountName: spire-agent
17+
nodeSelector:
18+
workload-type: coco
19+
# TODO: Make imagePullSecrets configurable like qtodo chart pattern (values.yaml + conditional)
20+
# Currently hardcoded 'global-pull-secret' which must be manually created in the namespace
21+
# Should either: 1) use ServiceAccount.imagePullSecrets, or 2) be conditional from values
22+
imagePullSecrets:
23+
- name: global-pull-secret
24+
25+
containers:
26+
# SPIRE Agent Sidecar
27+
- name: spire-agent
28+
image: registry.redhat.io/zero-trust-workload-identity-manager/spiffe-spire-agent-rhel9@sha256:4073ef462525c2ea1326f3c44ec630e33cbab4b428e8314a85d38756c2460831
29+
command: ["/bin/sh", "-c"]
30+
args:
31+
- |
32+
echo "=== DEBUG: Checking /sealed mount ==="
33+
ls -laR /sealed || echo "/sealed does not exist"
34+
echo "=== DEBUG: Content of cert.pem (first 200 bytes) ==="
35+
head -c 200 /sealed/cert.pem 2>&1 || echo "Cannot read cert.pem"
36+
echo "=== DEBUG: Testing network connectivity to KBS (cluster-internal) ==="
37+
curl -k -I https://kbs-service.trustee-operator-system.svc.cluster.local:8080 2>&1 | head -20
38+
echo "=== DEBUG: Testing network connectivity to KBS (public route) ==="
39+
curl -k -I https://kbs.apps.bleal-vp.azure.sandboxedcontainers.com 2>&1 | head -20
40+
echo "=== DEBUG: Testing if CDH is running (HTTP on localhost:8006) ==="
41+
curl -v http://127.0.0.1:8006/cdh/resource/default/spire-cert-qtodo/cert 2>&1 | head -50
42+
echo "=== DEBUG: Starting spire-agent ==="
43+
/spire-agent run -config /opt/spire/conf/agent/agent.conf
44+
env:
45+
- name: PATH
46+
value: "/opt/spire/bin:/bin"
47+
- name: MY_NODE_NAME
48+
value: "coco-vm-node" # Virtual node name for CoCo
49+
ports:
50+
- containerPort: 9982
51+
name: healthz
52+
protocol: TCP
53+
livenessProbe:
54+
httpGet:
55+
path: /live
56+
port: healthz
57+
scheme: HTTP
58+
initialDelaySeconds: 15
59+
periodSeconds: 60
60+
readinessProbe:
61+
httpGet:
62+
path: /ready
63+
port: healthz
64+
scheme: HTTP
65+
initialDelaySeconds: 10
66+
periodSeconds: 30
67+
volumeMounts:
68+
- name: spire-config
69+
mountPath: /opt/spire/conf/agent
70+
readOnly: true
71+
- name: spire-bundle
72+
mountPath: /run/spire/bundle
73+
readOnly: true
74+
- name: spire-socket
75+
mountPath: /tmp/spire-agent/public
76+
- name: spire-persistence
77+
mountPath: /var/lib/spire
78+
- name: sealed-creds
79+
mountPath: /sealed
80+
readOnly: true
81+
securityContext:
82+
readOnlyRootFilesystem: true
83+
allowPrivilegeEscalation: false
84+
capabilities:
85+
drop:
86+
- ALL
87+
seccompProfile:
88+
type: RuntimeDefault
89+
90+
# SPIFFE Helper Sidecar
91+
- name: spiffe-helper
92+
image: ghcr.io/spiffe/spiffe-helper:0.10.1
93+
imagePullPolicy: IfNotPresent
94+
args:
95+
- "-config"
96+
- "/etc/helper.conf"
97+
volumeMounts:
98+
- name: spiffe-helper-config
99+
readOnly: true
100+
mountPath: /etc/helper.conf
101+
subPath: helper.conf
102+
- name: spire-socket
103+
readOnly: true
104+
mountPath: /tmp/spire-agent/public
105+
- name: svids
106+
mountPath: /svids
107+
securityContext:
108+
allowPrivilegeEscalation: false
109+
capabilities:
110+
drop:
111+
- ALL
112+
readOnlyRootFilesystem: false
113+
seccompProfile:
114+
type: RuntimeDefault
115+
116+
# Test Workload Container
117+
- name: test-workload
118+
image: registry.redhat.io/ubi9/ubi-minimal:latest
119+
command: ["/bin/sh", "-c"]
120+
args:
121+
- |
122+
echo "=== SPIRE Agent CoCo Test Started ==="
123+
echo "Waiting for SPIFFE certificates..."
124+
125+
# Wait for SPIFFE certificates
126+
while [ ! -f /svids/svid.pem ]; do
127+
echo "Waiting for SPIFFE certificates..."
128+
sleep 2
129+
done
130+
131+
echo "SPIFFE certificates found!"
132+
ls -la /svids/
133+
134+
echo "=== Testing SPIFFE X.509 certificates ==="
135+
echo "Certificate details:"
136+
openssl x509 -in /svids/svid.pem -text -noout | head -20
137+
138+
echo "=== Sleeping for manual inspection ==="
139+
sleep 3600
140+
volumeMounts:
141+
- name: svids
142+
mountPath: /svids
143+
readOnly: true
144+
securityContext:
145+
allowPrivilegeEscalation: false
146+
capabilities:
147+
drop:
148+
- ALL
149+
readOnlyRootFilesystem: false
150+
seccompProfile:
151+
type: RuntimeDefault
152+
153+
volumes:
154+
- name: spire-config
155+
configMap:
156+
name: spire-agent-coco
157+
- name: spiffe-helper-config
158+
configMap:
159+
name: spiffe-helper-config
160+
- name: spire-bundle
161+
configMap:
162+
name: spire-bundle
163+
- name: spire-socket
164+
emptyDir: {}
165+
- name: spire-persistence
166+
emptyDir: {}
167+
- name: sealed-creds
168+
secret:
169+
secretName: {{ .Values.sealedSecret.name }}
170+
- name: svids
171+
emptyDir: {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Sealed Secret for SPIRE agent x509pop attestation
2+
#
3+
# This creates a K8s Secret with sealed secret references that CDH will unseal
4+
# inside the TEE after successful hardware attestation.
5+
#
6+
# Format: sealed.fakejwsheader.<base64-json>.fakesignature
7+
# The JSON payload contains the KBS resource path, and CDH fetches the real secret.
8+
#
9+
{{- define "hello-coco.sealedRef" -}}
10+
{{- $json := printf `{"version":"0.1.0","type":"vault","name":"kbs:///%s","provider":"kbs","provider_settings":{},"annotations":{}}` . -}}
11+
sealed.fakejwsheader.{{ $json | b64enc }}.fakesignature
12+
{{- end }}
13+
apiVersion: v1
14+
kind: Secret
15+
metadata:
16+
name: {{ .Values.sealedSecret.name }}
17+
namespace: zero-trust-workload-identity-manager
18+
type: Opaque
19+
stringData:
20+
cert.pem: {{ include "hello-coco.sealedRef" .Values.sealedSecret.certPath | quote }}
21+
key.pem: {{ include "hello-coco.sealedRef" .Values.sealedSecret.keyPath | quote }}

charts/hello-coco/values.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Default values for hello coco
2+
3+
# SPIRE trust domain
4+
# The SPIRE agent must be configured with the same trust domain as the SPIRE Server
5+
# This ensures the agent can successfully authenticate and workloads receive valid SPIFFE IDs
6+
# Typically set to apps.<your cluster domain>
7+
trustDomain: "apps.example.com"
8+
9+
# KBS URL for CDH (Confidential Data Hub) to fetch sealed secrets after TEE attestation
10+
# Dev (single cluster): http://kbs-service.trustee-operator-system.svc.cluster.local:8080
11+
# Prod (separate trusted cluster): https://kbs.trusted-cluster.example.com
12+
kbsUrl: "http://kbs-service.trustee-operator-system.svc.cluster.local:8080"
13+
14+
# Sealed secret configuration for SPIRE agent x509pop attestation
15+
# These are KBS resource paths where the agent cert/key are stored
16+
sealedSecret:
17+
# Name of the K8s Secret to create with sealed references
18+
name: "spire-agent-sealed-creds"
19+
# KBS resource path for the certificate (e.g., default/spire-cert-qtodo/cert)
20+
certPath: "default/spire-cert-qtodo/cert"
21+
# KBS resource path for the private key (e.g., default/spire-key-qtodo/key)
22+
keyPath: "default/spire-key-qtodo/key"

values-coco-dev.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,11 @@ clusterGroup:
325325
project: hub
326326
chart: sandboxed-policies
327327
chartVersion: 0.0.*
328+
hello-coco:
329+
name: hello-coco
330+
namespace: zero-trust-workload-identity-manager
331+
project: hub
332+
path: charts/hello-coco
328333
argoCD:
329334
resourceExclusions: |
330335
- apiGroups:

0 commit comments

Comments
 (0)