Skip to content

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.

brew install cloudtaser/tap/cloudtaser-cli
# 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:

kubectl get pods -n cloudtaser-system

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:

quickstart-app.yaml
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:

kubectl apply -f quickstart-app.yaml

Step 5: Verify the Secret is in Memory Only

Wait for the pod to be running:

kubectl get pods -l app=quickstart-app -w

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:

kubectl exec deploy/quickstart-app -- cat /proc/1/environ | tr '\0' '\n' | grep PGPASSWORD

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:

kubectl get pod -l app=quickstart-app -o jsonpath='{.items[0].spec.initContainers[*].name}'

Expected: cloudtaser-init


Step 6: Check Protection Score

Use the CLI to see the protection status of your cluster:

cloudtaser-cli target status

For a full validation:

cloudtaser-cli target validate \
  --secretstore-address https://vault.eu.example.com

For a compliance audit report:

cloudtaser-cli target audit \
  --secretstore-address https://vault.eu.example.com

What Just Happened

When the pod was created, the cloudtaser operator:

  1. Intercepted the pod creation via the mutating admission webhook
  2. Injected an init container (cloudtaser-init) that copied the wrapper binary to a memory-backed emptyDir at /cloudtaser/
  3. Rewrote the container entrypoint to /cloudtaser/wrapper
  4. Set environment variables (VAULT_ADDR - the standard OpenBao/HashiCorp Vault SDK address var - plus CLOUDTASER_SECRET_PATHS, CLOUDTASER_ENV_MAP, etc.) so the wrapper knows what to fetch
  5. Added CAP_IPC_LOCK so secrets can be locked in memory (no swap)

At runtime, the wrapper:

  1. Authenticated to the secret store using the pod's Kubernetes service account
  2. Fetched password and username from secret/data/quickstart/db
  3. Mapped them to PGPASSWORD and PGUSER
  4. Stored them in protected memory (memfd_secret + mlock)
  5. Fork+exec'd the original command (sh -c ...) with secrets in the child's environment via BuildChildEnv
  6. Continues running as PID 1 for lease renewal and signal forwarding. The wrapper's own /proc/1/environ was already structurally clean (secrets entered Go heap memory after exec, never the kernel's frozen envp[] for PID 1); the child's environ is protected from third-party reads by the eBPF kprobe on openat("/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