Skip to content

Commit 7c938d2

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 2e0df9b commit 7c938d2

5 files changed

Lines changed: 307 additions & 0 deletions

File tree

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.zero-trust-workload-identity-manager",
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: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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+
# TODO: Make imagePullSecrets configurable like qtodo chart pattern (values.yaml + conditional)
18+
# Currently hardcoded 'global-pull-secret' which must be manually created in the namespace
19+
# Should either: 1) use ServiceAccount.imagePullSecrets, or 2) be conditional from values
20+
imagePullSecrets:
21+
- name: global-pull-secret
22+
23+
containers:
24+
# SPIRE Agent Sidecar
25+
- name: spire-agent
26+
image: registry.redhat.io/zero-trust-workload-identity-manager/spiffe-spire-agent-rhel9@sha256:4073ef462525c2ea1326f3c44ec630e33cbab4b428e8314a85d38756c2460831
27+
command: ["/bin/sh", "-c"]
28+
args:
29+
- |
30+
echo "=== DEBUG: Checking /sealed mount ==="
31+
ls -laR /sealed || echo "/sealed does not exist"
32+
echo "=== DEBUG: Content of cert.pem (first 200 bytes) ==="
33+
head -c 200 /sealed/cert.pem 2>&1 || echo "Cannot read cert.pem"
34+
echo "=== DEBUG: Testing network connectivity to KBS (cluster-internal) ==="
35+
curl -k -I https://kbs-service.trustee-operator-system.svc.cluster.local:8080 2>&1 | head -20
36+
echo "=== DEBUG: Testing network connectivity to KBS (public route) ==="
37+
curl -k -I https://kbs.apps.bleal-vp.azure.sandboxedcontainers.com 2>&1 | head -20
38+
echo "=== DEBUG: Testing if CDH is running (HTTP on localhost:8006) ==="
39+
curl -v http://127.0.0.1:8006/cdh/resource/default/spire-cert-qtodo/cert 2>&1 | head -50
40+
echo "=== DEBUG: Starting spire-agent ==="
41+
/spire-agent run -config /opt/spire/conf/agent/agent.conf
42+
env:
43+
- name: PATH
44+
value: "/opt/spire/bin:/bin"
45+
- name: MY_NODE_NAME
46+
value: "coco-vm-node" # Virtual node name for CoCo
47+
ports:
48+
- containerPort: 9982
49+
name: healthz
50+
protocol: TCP
51+
livenessProbe:
52+
httpGet:
53+
path: /live
54+
port: healthz
55+
scheme: HTTP
56+
initialDelaySeconds: 15
57+
periodSeconds: 60
58+
readinessProbe:
59+
httpGet:
60+
path: /ready
61+
port: healthz
62+
scheme: HTTP
63+
initialDelaySeconds: 10
64+
periodSeconds: 30
65+
volumeMounts:
66+
- name: spire-config
67+
mountPath: /opt/spire/conf/agent
68+
readOnly: true
69+
- name: spire-bundle
70+
mountPath: /run/spire/bundle
71+
readOnly: true
72+
- name: spire-socket
73+
mountPath: /tmp/spire-agent/public
74+
- name: spire-persistence
75+
mountPath: /var/lib/spire
76+
- name: sealed-creds
77+
mountPath: /sealed
78+
readOnly: true
79+
securityContext:
80+
readOnlyRootFilesystem: true
81+
allowPrivilegeEscalation: false
82+
capabilities:
83+
drop:
84+
- ALL
85+
seccompProfile:
86+
type: RuntimeDefault
87+
88+
# SPIFFE Helper Sidecar
89+
- name: spiffe-helper
90+
image: ghcr.io/spiffe/spiffe-helper:0.10.1
91+
imagePullPolicy: IfNotPresent
92+
args:
93+
- "-config"
94+
- "/etc/helper.conf"
95+
volumeMounts:
96+
- name: spiffe-helper-config
97+
readOnly: true
98+
mountPath: /etc/helper.conf
99+
subPath: helper.conf
100+
- name: spire-socket
101+
readOnly: true
102+
mountPath: /tmp/spire-agent/public
103+
- name: svids
104+
mountPath: /svids
105+
securityContext:
106+
allowPrivilegeEscalation: false
107+
capabilities:
108+
drop:
109+
- ALL
110+
readOnlyRootFilesystem: false
111+
seccompProfile:
112+
type: RuntimeDefault
113+
114+
# Test Workload Container
115+
- name: test-workload
116+
image: registry.redhat.io/ubi9/ubi-minimal:latest
117+
command: ["/bin/sh", "-c"]
118+
args:
119+
- |
120+
echo "=== SPIRE Agent CoCo Test Started ==="
121+
echo "Waiting for SPIFFE certificates..."
122+
123+
# Wait for SPIFFE certificates
124+
while [ ! -f /svids/svid.pem ]; do
125+
echo "Waiting for SPIFFE certificates..."
126+
sleep 2
127+
done
128+
129+
echo "SPIFFE certificates found!"
130+
ls -la /svids/
131+
132+
echo "=== Testing SPIFFE X.509 certificates ==="
133+
echo "Certificate details:"
134+
openssl x509 -in /svids/svid.pem -text -noout | head -20
135+
136+
echo "=== Sleeping for manual inspection ==="
137+
sleep 3600
138+
volumeMounts:
139+
- name: svids
140+
mountPath: /svids
141+
readOnly: true
142+
securityContext:
143+
allowPrivilegeEscalation: false
144+
capabilities:
145+
drop:
146+
- ALL
147+
readOnlyRootFilesystem: false
148+
seccompProfile:
149+
type: RuntimeDefault
150+
151+
volumes:
152+
- name: spire-config
153+
configMap:
154+
name: spire-agent-coco
155+
- name: spiffe-helper-config
156+
configMap:
157+
name: spiffe-helper-config
158+
- name: spire-bundle
159+
configMap:
160+
name: spire-bundle
161+
- name: spire-socket
162+
emptyDir: {}
163+
- name: spire-persistence
164+
emptyDir: {}
165+
- name: sealed-creds
166+
secret:
167+
secretName: {{ .Values.sealedSecret.name }}
168+
- name: svids
169+
emptyDir: {}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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.<jws> where JWS is header.payload.signature
7+
# The payload is base64url-encoded (RFC 7515: no padding, URL-safe alphabet).
8+
# Helm's b64enc produces standard base64, so we convert to base64url.
9+
#
10+
{{- define "hello-coco.sealedRef" -}}
11+
{{- $json := printf `{"version":"0.1.0","type":"vault","name":"kbs:///%s","provider":"kbs","provider_settings":{},"annotations":{}}` . -}}
12+
sealed.fakejwsheader.{{ $json | b64enc | replace "+" "-" | replace "/" "_" | trimSuffix "=" | trimSuffix "=" }}.fakesignature
13+
{{- end }}
14+
apiVersion: v1
15+
kind: Secret
16+
metadata:
17+
name: {{ .Values.sealedSecret.name }}
18+
namespace: zero-trust-workload-identity-manager
19+
type: Opaque
20+
stringData:
21+
cert.pem: {{ include "hello-coco.sealedRef" .Values.sealedSecret.certPath | quote }}
22+
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"

0 commit comments

Comments
 (0)