Quickstart: Protect a Workload in 5 Minutes¶
This guide takes you from zero to a running cloudtaser-protected workload. You will install the operator, store a secret in OpenBao, annotate a deployment, and verify that the secret exists only in process memory.
What you will have at the end: a pod where PGPASSWORD is injected directly from an EU-hosted OpenBao into process memory. It never touches etcd, Kubernetes Secrets, or disk.
Before production -- pick the right substrate
The quickstart uses whatever cluster and OpenBao you already have. Before deploying in production, read the Sovereign Deployment Decision Guide -- it walks through the two substrate choices (OpenBao host, target compute SKU) that determine whether the sovereignty claim actually holds.
Prerequisites¶
| Tool | Purpose |
|---|---|
kubectl |
Kubernetes cluster access (1.28+) |
helm v3 |
Operator deployment |
| A running OpenBao instance (also compatible with HashiCorp Vault) | Secret storage (must be reachable from the cluster) |
bao or vault CLI |
Creating test secrets |
You also need the cloudtaser CLI (cloudtaser-cli) installed.
# macOS (Apple Silicon)
curl -sL https://releases.cloudtaser.io/cli/v0.17.30/cloudtaser-cli-darwin-arm64 \
-o /usr/local/bin/cloudtaser-cli && chmod +x /usr/local/bin/cloudtaser-cli
# Linux (x86_64)
curl -sL https://releases.cloudtaser.io/cli/v0.17.30/cloudtaser-cli-linux-amd64 \
-o /usr/local/bin/cloudtaser-cli && chmod +x /usr/local/bin/cloudtaser-cli
Available platforms: darwin-amd64, darwin-arm64, linux-amd64, linux-arm64
Step 1: Install cloudtaser¶
Install the operator, mutating webhook, and eBPF agent from the OCI Helm chart:
helm repo add cloudtaser https://charts.cloudtaser.io
helm install cloudtaser cloudtaser/cloudtaser \
--namespace cloudtaser-system \
--create-namespace \
--set operator.secretstore.address=https://vault.eu.example.com
Replace https://vault.eu.example.com with your actual secret store address (OpenBao or HashiCorp Vault).
Secret-store-backed operational state
By default, the operator stores its own operational secrets (webhook TLS certificates, broker tokens) in the configured secret store rather than Kubernetes Secrets. This means the operator needs secret-store access at startup. If the secret store is not available yet, add --set operator.secretBackend=inmemory to fall back to in-memory secrets. See Zero Kubernetes Secrets Architecture for details.
Legacy operator.vault.* values
operator.vault.address continues to work as an alias during the deprecation window. Phase 3 removes it; new install guides should use operator.secretstore.* exclusively.
Verify the pods are running:
Expected output:
NAME READY STATUS RESTARTS AGE
cloudtaser-operator-6f8b9c7d4-x2k9m 1/1 Running 0 30s
cloudtaser-ebpf-xxxxx 1/1 Running 0 30s
Step 2: Connect the Cluster to the Secret Store¶
Use the CLI to register your cluster and configure secret-store connectivity. By default, this uses beacon relay mode -- both the secret store and the cluster connect outbound to a stateless beacon relay on TCP 443. No direct network path between the secret store and cluster is needed.
# Get the cluster fingerprint
cloudtaser-cli target fingerprint
# Register on the secret-store side (creates namespace cloudtaser/<fingerprint[:8]>)
cloudtaser-cli source register \
--fingerprint <cluster-fingerprint> \
--secretstore-address https://vault.eu.example.com \
--secretstore-token hvs.YOUR_ADMIN_TOKEN
Or use the legacy direct connect if the secret store is reachable from the cluster:
cloudtaser-cli target connect \
--secretstore-address https://vault.eu.example.com \
--secretstore-token hvs.YOUR_ADMIN_TOKEN
Legacy --vault-* flag names
The CLI still accepts --vault-address / --vault-token / --vault-role (and every other --vault-* flag) as aliases. They are hidden from --help output; new install guides should use --secretstore-* exclusively. Phase 3 of the rename removes the aliases.
Preview first
Add --dry-run to see what will be created without making changes.
Step 3: Create a Test Secret¶
Store a secret in your OpenBao (or HashiCorp Vault) instance using the bao or vault CLI:
export BAO_ADDR=https://vault.eu.example.com
export BAO_TOKEN=hvs.YOUR_ADMIN_TOKEN
bao kv put secret/quickstart/db \
password="s3cret-from-eu-secret-store" \
username="app_user"
Step 4: Deploy a Protected Workload¶
Create a deployment with cloudtaser annotations that tell the operator where to find the secret and how to map it to environment variables:
apiVersion: apps/v1
kind: Deployment
metadata:
name: quickstart-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: quickstart-app
template:
metadata:
labels:
app: quickstart-app
annotations:
cloudtaser.io/inject: "true"
cloudtaser.io/secretstore-role: "cloudtaser"
cloudtaser.io/secret-paths: "secret/data/quickstart/db"
cloudtaser.io/env-map: "password=PGPASSWORD,username=PGUSER"
# No secretstore-address needed in beacon mode -- the operator broker
# routes requests through the beacon relay automatically
spec:
containers:
- name: app
image: busybox:latest
command: ["sh", "-c", "echo 'PGPASSWORD is set (value hidden)' && sleep 3600"]
Apply it:
Step 5: Verify the Secret is in Memory Only¶
Wait for the pod to be running:
Once READY 1/1, confirm the wrapper injected the secret:
# Check wrapper logs -- should show "secrets loaded, starting child process"
kubectl logs -l app=quickstart-app -c app | head -20
Verify the secret is NOT in the wrapper's environ (PID 1) -- it is structurally clean because secrets are fetched from vault after the wrapper has already exec'd, and Linux freezes /proc/PID/environ at exec time:
This should return nothing. The secret lives in the wrapper's Go heap memory and in the child process's environ snapshot; cross-process reads of /proc/<child>/environ are blocked by the cloudtaser eBPF kprobe.
Verify the init container and volume injection happened:
Expected: cloudtaser-init
Step 6: Check Protection Score¶
Use the CLI to see the protection status of your cluster:
For a full validation:
For a compliance audit report:
What Just Happened¶
When the pod was created, the cloudtaser operator:
- Intercepted the pod creation via the mutating admission webhook
- Injected an init container (
cloudtaser-init) that copied the wrapper binary to a memory-backed emptyDir at/cloudtaser/ - Rewrote the container entrypoint to
/cloudtaser/wrapper - Set environment variables (
VAULT_ADDR- the standard OpenBao/HashiCorp Vault SDK address var - plusCLOUDTASER_SECRET_PATHS,CLOUDTASER_ENV_MAP, etc.) so the wrapper knows what to fetch - Added
CAP_IPC_LOCKso secrets can be locked in memory (no swap)
At runtime, the wrapper:
- Authenticated to the secret store using the pod's Kubernetes service account
- Fetched
passwordandusernamefromsecret/data/quickstart/db - Mapped them to
PGPASSWORDandPGUSER - Stored them in protected memory (memfd_secret + mlock)
- Fork+exec'd the original command (
sh -c ...) with secrets in the child's environment viaBuildChildEnv - Continues running as PID 1 for lease renewal and signal forwarding. The wrapper's own
/proc/1/environwas already structurally clean (secrets entered Go heap memory after exec, never the kernel's frozenenvp[]for PID 1); the child's environ is protected from third-party reads by the eBPF kprobe onopenat("/proc/<pid>/environ")
Result: secrets travel directly from the EU secret store to process memory. They never pass through etcd, Kubernetes Secrets, or any US-controlled storage layer.
Next Steps¶
- Migration Plan Workflow -- the recommended approach for migrating existing workloads using a reviewable plan file (discover, apply, verify, migrate)
- Getting Started (full guide) -- detailed walkthrough including OpenBao deployment
- Annotations Reference -- all available pod annotations
- CLI Reference -- full command documentation
- Security Model -- trust boundaries and threat model
- Installation Guide -- production Helm configuration