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.
Related observability¶
| 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¶
- Security Model (threat model and defense layers)
- Memory Protection
- Beacon Relay Architecture
- Zero Kubernetes Secrets
- RFC 5869 - HKDF specification
- X25519 / ECDH for ephemeral key exchange
- The
cloudtaser-operatorbroker handshake (internal/broker/broker.go) cloudtaser-wrapperunseal receiver (internal/health/health.go)- Beacon relay design (repo:
cloudtaser-beacon)