How zen-lock Works
A technical deep dive into zen-lock's encrypt/decrypt lifecycle within Zen Mesh.
Architecture
zen-lock runs as a Kubernetes controller with a mutating admission webhook in your cluster:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ zen-lock │ │ K8s API │ │ Your Pods │
│ Controller │────▶│ Server │────▶│ (egress, │
│ + Webhook │ │ (etcd) │ │ agent) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
│ Only ciphertext │ Only ciphertext │ Plaintext
│ stored here │ stored here │ exists here
The Encrypt Flow
When the control plane generates an enrollment bundle:
# Simplified: what happens under the hood
zen-lock encrypt \
--public-key age1y... \
--input enrollment-credentials.json \
--output ZenLock CRD (ciphertext only)
- The control plane has an age public key
- Sensitive fields are encrypted with age
- The resulting ciphertext is packaged into a Kubernetes Secret
- The Secret is applied to your cluster
Your cluster's etcd never sees plaintext. The API server stores and serves only ciphertext.
The Decrypt Flow (Mutating Webhook)
When a pod starts that needs access to secrets (e.g., zen-egress needs the mTLS private key):
- Pod creation request hits the Kubernetes API server
- zen-lock webhook intercepts the request
- Webhook checks: does this pod have a
zen-lockannotation or volume reference? - If yes: Webhook reads the ZenLock CRD, decrypts the ciphertext
- Webhook injects a temporary K8s Secret into the pod's namespace
- Pod mounts the ephemeral Secret as a volume
- Pod starts with access to plaintext secrets
Pod spec with zen-lock reference
│
▼
┌─────────────────────┐
│ Mutating Webhook │
│ 1. Read ZenLock CRD │
│ 2. Decrypt with age │
│ 3. Create ephemeral │
│ K8s Secret │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Pod starts with │
│ ephemeral Secret │
│ mounted as volume │
└─────────────────────┘
Ephemeral Lifecycle
The decrypted K8s Secret is not permanent:
- Created by the webhook just before the pod starts
- Mounted into the pod as a volume
- Deleted automatically when the pod terminates (orphan TTL)
- Never committed to Git, never visible in
kubectl get secretsfor more than the pod's lifetime
This means:
- A compromised etcd dump reveals only ciphertext
- A compromised backup reveals only ciphertext
- Plaintext exists only in running pod memory
Age Encryption
zen-lock uses age, a modern file encryption tool:
- Simple: One public key, no PKI infrastructure needed
- Small: Minimal dependency, no heavy crypto libraries
- Auditable: Go implementation, easy to verify
- Standard: age is becoming a standard for K8s secret encryption (used by SOPS, Flux, etc.)
What You'll See in Your Cluster
After enrollment, you'll see zen-lock resources:
# The controller
kubectl get pods -n zen-mesh -l app=zen-lock
# Encrypted CRDs (ciphertext only)
kubectl get zenlocks -n zen-mesh
# Ephemeral secrets (temporary, created by webhook)
kubectl get secrets -n zen-mesh | grep ephemeral
The zenlocks contain only ciphertext. The ephemeral secrets exist only while pods are running.