Skip to content

Security Model - Trust Chain and Per-Pod Isolation

Authorised application boundary

Authorised application code is trusted. An authorised app can send out a secret it has copied or encoded. Current content checks do not provide a general guarantee against this disclosure or a malicious authorised app. Memory-read tests and a Ready agent do not prove copied/encoded-secret exfiltration prevention. Enforced NetworkPolicy can restrict destinations, but allowed traffic remains a channel. See Security Model.

Identity and store-login boundary

Direct Kubernetes-auth store login authenticates a ServiceAccount and namespace, not the injected wrapper or Deployment. A pod creator using that identity, or node root stealing its JWT, can log in separately and read the role's allowed records. Use one workload per identity, exact-record policies and restricted workload creation. Exec/memory controls do not block that raw login. This limit remains unresolved; see the Security Model. One workload per identity applies on both direct and Port paths. Denial of a second same-identity workload on Port has not been established.

The broker token path scopes individual deliveries to declared paths. This is not a universal per-pod isolation guarantee: direct store logins use the shared ServiceAccount identity, and authorized application code can disclose delivered values. This page explains the token cascade and the four per-pod isolation layers.


Token cascade - where secrets actually come from

When a protected pod uses cloudtaser.io/secretstore-auth-method: token (the flagship, beacon-relayed path):

┌──────────────────────────────────────────────────────────────────┐
│ Vault (EU-hosted, on-prem / self-hosted)                         │
│   Root policies, root token held in OpenBao                      │
└────────────┬─────────────────────────────────────────────────────┘
             │ mTLS through the beacon P2P relay
             ▼
┌──────────────────────────────────────────────────────────────────┐
│ Operator's own token (cluster-wide)                              │
│   Policy: mint child tokens + read cluster-meta                  │
│   Obtained via /v1/port/init - delivered into operator         │
│   process memory, NEVER on disk / K8s Secret / etcd              │
│   TTL: hours, renewable                                          │
└────────────┬─────────────────────────────────────────────────────┘
             │ calls `auth/token/create` with pod-specific policy
             ▼
┌──────────────────────────────────────────────────────────────────┐
│ Per-pod child token (one per protected pod)                      │
│   Policy: ONLY paths from this pod's                             │
│     secretstore.cloudtaser.io/secret-paths annotation                        │
│   TTL: minutes (typical 5-15), not renewable beyond pod life     │
└────────────┬─────────────────────────────────────────────────────┘
             │ delivered to wrapper via HKDF-v2 encrypted envelope
             ▼
┌──────────────────────────────────────────────────────────────────┐
│ Wrapper (inside THIS pod)                                        │
│   Uses child token to fetch only THIS pod's secrets              │
│   Delivers secret to app via memfd (kernel-only inheritance)     │
│   eBPF enforcement blocks /proc/environ, /proc/mem, ptrace       │
│   from the outside                                               │
└──────────────────────────────────────────────────────────────────┘

Per-pod isolation layers

Four layers fire independently at unseal time. None share state between pods:

Layer What's per-pod Consequence
ECDH keypair Wrapper generates a fresh X25519 keypair on startup (in memory, never persisted). Different shared secret per pod.
HKDF-derived AES key Derived from (wrapper pubkey) + (broker ephemeral pubkey). Ciphertext intercepted on the wire is useless for any other pod.
Child vault token Minted with pod-specific policy, short TTL. Compromised pod → only its declared paths leak, token auto-expires.
memfd + eBPF Anonymous memfd inherited across fork+exec; eBPF blocks external readers. Covered external reads require active enforcement; node-root and application copies retain the security-model limits.

What each component sees - blast-radius table

Component compromise What the attacker gets
Protected pod Only that pod's declared secret-paths, scoped by child token. Token expires in minutes.
Operator Ability to mint new child tokens. Still has to mint + wait for unseal; every mint is auditable in vault. Does NOT get direct pod memory access.
Beacon relay Encrypted TCP frames. Cannot decrypt - does not hold HKDF keys. Acts as a dumb forwarder.
CloudTaser Port Proxy to vault. Holds operator's port cert (in memory only). Does NOT hold per-pod tokens.
Kubernetes API / etcd Default operator runtime private material stays in memory. Legacy beacon-key and Kubernetes Port bootstrap paths can create Secrets and place input in Helm state; review the installation path.
Cloud provider (AWS/GCP/Azure) Relay traffic is encrypted. Provider access through node root, credential theft or application code is outside this transport guarantee.

Two auth methods - when each applies

cloudtaser supports two authentication methods for delivering secrets. Their trust models differ; direct mode does not establish per-pod isolation.

secretstore-auth-method: token - flagship path (beacon-relayed)

Used for data sovereignty workloads. The wrapper never holds a long-lived credential. The operator mints a short-lived child token in vault and delivers it via the broker's HKDF-v2 encrypted envelope. Traffic between operator and vault flows through the P2P beacon relay and CloudTaser Port - the cloud provider never sees plaintext.

Annotations required:

secretstore.cloudtaser.io/inject: "true"
cloudtaser.io/secretstore-auth-method: "token"
cloudtaser.io/secretstore-address: "https://your-vault.example.eu:8200"
secretstore.cloudtaser.io/secret-paths: "secret/data/prod/db-creds"
secretstore.cloudtaser.io/env-map: "password=DB_PASSWORD,username=DB_USER"

secretstore-auth-method: kubernetes - direct fallback

Used where beacon-relayed operator is not deployed (for example, traditional single-region vault + vault-agent migrations). The wrapper authenticates directly to vault using its own ServiceAccount JWT, scoped by vault's Kubernetes-auth role bindings.

The store role bounds allowed records for the ServiceAccount/namespace identity; another holder of that identity can obtain its own token. No wrapper or Deployment binding is established. In this mode, the P2P beacon relay, HKDF-v2 envelope, and ECDH per-pod crypto are skipped. This path exists for migration compatibility - production deployments should prefer token mode.


Metric / signal What it proves
cloudtaser_broker_unseal_protocol_version_total{version="v2"} HKDF-v2 unseal envelope was used on this unseal. Non-zero = flagship path active.
cloudtaser_ebpf_enforcement_mode{mode=...} (roadmap) Which of LSM / kprobe-override / reactive-kill is active per node.

Further reading