Self-Bootstrap: How cloudtaser Connects a Cluster to the EU OpenBao¶
cloudtaser uses a beacon relay to connect Kubernetes clusters to an EU-hosted secret store without direct network access. The entire process requires zero K8s Secrets, zero OpenBao tokens for day-2 operations, and only one Helm parameter to deploy.
Overview¶
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Target Cluster │ │ Beacon │ │ EU Secret Store │
│ (K8s + Operator)│────▶│ (TCP relay) │◀────│ (Vault + Bridge)│
│ │ │ port 443 │ │ │
└─────────────────┘ └──────────────┘ └─────────────────┘
outbound only stateless outbound only
no inbound ports no secrets no inbound ports
Both sides connect outbound to the beacon. Neither needs inbound ports or direct network paths to the other.
Complete Walkthrough¶
Phase 1: Get Cluster Fingerprint (Target Side)¶
$ cloudtaser-cli target fingerprint
cloudtaser Cluster Fingerprint
Cluster ID: 0ad50c05-1153-4508-9237-4cb2912e2c23
Register this cluster on your secret store:
cloudtaser-cli source register --fingerprint 0ad50c05-1153-4508-9237-4cb2912e2c23
The fingerprint is the kube-system namespace UID - unique per cluster, stable across restarts. This is the only value exchanged between the two sides. No files, no certs, no tokens.
Phase 2: Register Cluster (Secret Store Side)¶
On the OpenBao side (different network, different VPN - doesn't matter):
$ cloudtaser-cli source register \
--fingerprint 0ad50c05-1153-4508-9237-4cb2912e2c23 \
--secretstore-address https://vault.cloudtaser.io \
--secretstore-token <admin-token>
This command does five things:
1. Creates an OpenBao namespace at cloudtaser/<fingerprint[:8]> -- each cluster gets isolated storage
2. Generates a CA and two cert pairs (broker cert + bridge client cert) -- all in memory
3. Stores certs in OpenBao at cloudtaser/data/system/bridge-*
4. Registers the fingerprint at cloudtaser/data/clusters/<id>/registered
5. Computes info hashes -- one for init (from fingerprint), one for operational (from CA cert)
The bridge (running alongside OpenBao) detects the new registration and connects to the beacon with the init hash.
Phase 3: Deploy Operator (Target Side)¶
Back on the K8s cluster - just ONE parameter:
$ helm install cloudtaser cloudtaser/cloudtaser \
--set operator.broker.beacon.address=beacon.example.com:443
Or with ArgoCD:
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
source:
chart: cloudtaser
repoURL: https://charts.cloudtaser.io
helm:
values: |
operator:
beacon:
address: beacon.example.com:443
No certificates, no secrets, no tokens in the manifest. Safe to store in git.
Phase 4: Self-Bootstrap (Automatic)¶
The operator starts and self-bootstraps through the beacon relay:
┌──────────┐ ┌────────┐ ┌────────┐
│ Operator │ │ Beacon │ │ Bridge │
│ (broker) │ │ (relay)│ │ (vault)│
└────┬─────┘ └───┬────┘ └───┬────┘
│ │ │
│ 1. Connect (init hash) │ │
│──────────────────────────▶│ │
│ │◀─────────────────────────│
│ │ Bridge already waiting │
│ │ (detected registration) │
│ │ │
│ 2. Beacon matches │ │
│◀─────── relay ──────────▶│ │
│ │ │
│ 3. Send fingerprint │ relay │
│──────────────────────────────────────────────────────▶│
│ │ │
│ │ 4. Verify fingerprint │
│ │ Check vault: │
│ │ registered? ✓ │
│ │ initialized? ✗ │
│ │ │
│ 5. Receive certs │ relay │
│◀──────────────────────────────────────────────────────│
│ (CA + broker cert + key) │ │
│ │ 6. Mark initialized │
│ │ (one-time use) │
│ │ │
│ 7. Disconnect │ │
│──────── close ──────────▶│ │
│ │ │
│ 8. Reconnect │ │
│ (operational hash) │ │
│──────────────────────────▶│ │
│ │◀─────────────────────────│
│ │ Bridge in operational │
│ │ room (same CA hash) │
│ │ │
│ 9. mTLS handshake │ relay │
│◀═══════════════════════════════════════════════════▶│
│ │ │
│ OPERATIONAL │ │
│ Secrets flow via mTLS │ │
│ through beacon relay │ │
Steps explained:
- Operator reads
kube-systemUID, computesSHA256("cloudtaser-init:" + fingerprint)→ connects to beacon with this init hash - Beacon matches the operator with the bridge (both registered with the same init hash)
- Operator sends its fingerprint through the relay (protected by beacon's outer TLS)
- Bridge checks OpenBao: is this fingerprint registered? Yes. Already initialized? No.
- Bridge sends the pre-generated certificates: CA cert, broker cert, broker private key
- Bridge marks the fingerprint as "initialized" in OpenBao - one-time use, can't be replayed
- Operator disconnects from the init "room"
- Operator computes
SHA256(CA cert DER)→ reconnects to beacon with the operational hash - Both sides do a full mTLS handshake through the relay - connection is now authenticated and encrypted end-to-end
Operator pod readiness and beacon timing (operator v0.9.4+):
The operator pod's startup, liveness, and readiness probes are decoupled from beacon connectivity. The operator becomes Ready in seconds after the pod starts — beacon init runs entirely in the background. This means:
kubectl rollout status deployment -n cloudtaser-system cloudtaser-operatorcompletes in seconds, not minutes.- Beacon dial is parallel: the operator resolves all DNS A-records for the beacon address and opens TCP connections to all of them simultaneously. A P2P match typically completes in under 500ms once the bridge is also connected.
- If the bridge has not yet connected to the beacon when a pod creation is attempted, the admission webhook returns a clear error rather than stalling. The pod creation can be retried once the bridge is connected.
An operator pod that fails to become Ready (not just slow beacon init — the pod itself stuck in ContainerCreating or not passing probes) indicates a genuine problem: image pull failure, missing Helm values, or OpenBao connectivity issues (if secretBackend: vault is configured). See Troubleshooting for diagnosis steps.
Phase 5: Operational¶
Secrets now flow through the relay:
Each pod authenticates individually through OpenBao's Kubernetes auth - OpenBao RBAC policies apply per-pod, not per-bridge.
Phase 6: Migrate Existing Secrets¶
No OpenBao token needed - the bridge authenticates with its own credentials. Secrets are read from K8s, written to OpenBao through the relay, and deleted from K8s.
Security Properties¶
| Property | How |
|---|---|
| Zero K8s Secrets | All certs in operator process memory |
| Zero OpenBao tokens for day-2 | Bridge authenticates admin proxy requests |
| One-time cert exchange | Fingerprint marked as initialized after first use |
| OpenBao RBAC preserved | Each pod authenticates via K8s auth, scoped token |
| No inbound ports | Both sides connect outbound to beacon (TCP 443) |
| No certs in git/Helm | Only beacon address in Helm values |
| Beacon sees nothing | Encrypted mTLS bytes only, no key material |
Deregistering a Cluster¶
$ cloudtaser-cli source deregister \
--fingerprint 0ad50c05-1153-4508-9237-4cb2912e2c23 \
--secretstore-address https://vault.cloudtaser.io \
--secretstore-token <admin-token>
Removes the registration and initialization records. The bridge stops accepting connections from this cluster.
Automating with Terraform¶
data "kubernetes_namespace" "kube_system" {
metadata { name = "kube-system" }
depends_on = [google_container_cluster.main]
}
resource "null_resource" "register_cluster" {
provisioner "local-exec" {
command = <<-EOT
cloudtaser-cli source register \
--fingerprint ${data.kubernetes_namespace.kube_system.metadata[0].uid} \
--secretstore-address https://vault.cloudtaser.io \
--secretstore-token $VAULT_TOKEN
EOT
}
}
Every new GKE/AKS/EKS cluster gets auto-registered. ArgoCD deploys the Helm chart with one parameter. Operator self-bootstraps.