Migration Guide¶
The cloudtaser migrate command scans your cluster for workloads using existing secret management tools and generates a migration script that transitions them to CloudTaser. It supports External Secrets Operator, Sealed Secrets, and SOPS-encrypted Kubernetes Secrets.
Overview¶
Migration follows a consistent pattern regardless of the source tool:
- Scan --
cloudtaser migratediscovers workloads using the source tool - Generate -- it produces a shell script with annotate, delete, and restart commands
- Review -- you review the script and ensure vault has the secrets at the expected paths
- Execute -- run the script to migrate workloads
Vault must have the secrets first
The migration script does not copy secrets into vault. It assumes your EU-hosted vault already has the secrets at paths matching the CloudTaser annotations. For tools where this is not automatic (Sealed Secrets, SOPS), you must import secrets into vault before running the migration.
Migrate from External Secrets Operator¶
External Secrets Operator (ESO) synchronizes secrets from an external store (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) into Kubernetes Secrets. CloudTaser replaces both ESO and the Kubernetes Secrets it creates.
Scan¶
cloudtaser migrate --from=eso \
--vault-address https://vault.eu.example.com \
--vault-role cloudtaser
This scans all namespaces for ExternalSecret CRDs and their associated Kubernetes Secrets. For each, it:
- Reads the
ExternalSecretspec to identify the remote secret store, key, and property mappings - Identifies the Deployments, StatefulSets, or DaemonSets that reference the generated Kubernetes Secret via
secretKeyReforenvFrom - Maps the remote keys to vault paths and generates
cloudtaser.io/env-mapannotations
Output¶
The command generates a migration script:
#!/bin/bash
# Generated by: cloudtaser migrate --from=eso
# Namespace: production
# Date: 2026-03-21
# --- myapp ---
# ExternalSecret: myapp-secrets (source: aws-secretsmanager/prod/myapp)
# Vault path: secret/data/prod/myapp
kubectl annotate deployment myapp -n production \
cloudtaser.io/inject=true \
cloudtaser.io/vault-address=https://vault.eu.example.com \
cloudtaser.io/vault-role=cloudtaser \
cloudtaser.io/secret-paths=secret/data/prod/myapp \
"cloudtaser.io/env-map=db_password=PGPASSWORD,api_key=API_KEY"
kubectl delete externalsecret myapp-secrets -n production
kubectl delete secret myapp-secrets -n production
kubectl rollout restart deployment/myapp -n production
Prerequisites¶
- CloudTaser operator and eBPF agent must be deployed in the cluster
- Vault must be configured with Kubernetes auth (
cloudtaser connect) - Vault must contain the secrets at the mapped paths
ESO already syncs from an external store
If your ESO SecretStore points to AWS Secrets Manager, GCP Secret Manager, or Azure Key Vault, use cloudtaser import to copy those secrets into your EU vault first:
Migrate from Sealed Secrets¶
Sealed Secrets encrypts secret values in Git using the cluster's sealing key. The Sealed Secrets controller decrypts them in-cluster and creates standard Kubernetes Secrets.
Scan¶
cloudtaser migrate --from=sealed-secrets \
--vault-address https://vault.eu.example.com \
--vault-role cloudtaser
This scans all namespaces for SealedSecret CRDs and their corresponding Kubernetes Secrets. For each, it:
- Reads the SealedSecret metadata to identify the target Secret name and namespace
- Reads the corresponding Kubernetes Secret to identify data keys
- Identifies workloads that reference the Secret
- Generates CloudTaser annotations with vault path mappings
Output¶
#!/bin/bash
# Generated by: cloudtaser migrate --from=sealed-secrets
# Namespace: production
# --- myapp ---
# SealedSecret: myapp-sealed (generates Secret: myapp-secrets)
# Vault path: secret/data/production/myapp-secrets
kubectl annotate deployment myapp -n production \
cloudtaser.io/inject=true \
cloudtaser.io/vault-address=https://vault.eu.example.com \
cloudtaser.io/vault-role=cloudtaser \
cloudtaser.io/secret-paths=secret/data/production/myapp-secrets \
"cloudtaser.io/env-map=db_password=PGPASSWORD,api_key=API_KEY"
kubectl delete sealedsecret myapp-sealed -n production
kubectl delete secret myapp-secrets -n production
kubectl rollout restart deployment/myapp -n production
Prerequisites¶
Action required: import secrets into vault
Sealed Secrets stores encrypted values in Git. The actual plaintext secrets exist only inside the cluster as Kubernetes Secrets. Before migrating, you must extract the current secret values and write them to vault.
Option 1: Extract from existing Kubernetes Secrets
# Read the current values from the K8s Secret
kubectl get secret myapp-secrets -n production -o json | \
jq -r '.data | to_entries[] | "\(.key)=\(.value | @base64d)"'
# Write to vault
vault kv put secret/production/myapp-secrets \
db_password="extracted_value" \
api_key="extracted_value"
Option 2: Re-encrypt from source of truth
If you have the original plaintext values elsewhere (password manager, deployment scripts), write them directly to vault.
Migrate from SOPS¶
SOPS encrypts Kubernetes Secret manifests (or values files) using KMS, PGP, or age keys. The decrypted Secret is applied to the cluster at deploy time (via ArgoCD SOPS plugin, Flux Kustomize, or CI/CD pipeline).
Scan¶
cloudtaser migrate --from=sops \
--vault-address https://vault.eu.example.com \
--vault-role cloudtaser
This scans all namespaces for Kubernetes Secrets and identifies those that are likely SOPS-managed by checking for:
- SOPS metadata annotations on the Secret
- Corresponding SOPS-encrypted files in Git (if
--git-repois specified) - Secrets referenced by workloads that do not have an ExternalSecret or SealedSecret source
For each identified Secret, the command generates CloudTaser annotations and cleanup commands.
Output¶
#!/bin/bash
# Generated by: cloudtaser migrate --from=sops
# Namespace: production
# --- myapp ---
# Secret: myapp-secrets (SOPS-managed)
# Vault path: secret/data/production/myapp-secrets
kubectl annotate deployment myapp -n production \
cloudtaser.io/inject=true \
cloudtaser.io/vault-address=https://vault.eu.example.com \
cloudtaser.io/vault-role=cloudtaser \
cloudtaser.io/secret-paths=secret/data/production/myapp-secrets \
"cloudtaser.io/env-map=db_password=PGPASSWORD,api_key=API_KEY"
kubectl delete secret myapp-secrets -n production
kubectl rollout restart deployment/myapp -n production
Prerequisites¶
Action required: import secrets into vault
SOPS-encrypted secrets exist as encrypted files in Git and as plaintext Kubernetes Secrets in the cluster. Before migrating, extract the current values and write them to vault.
Decrypt and import:
# Decrypt the SOPS file
sops -d secrets.enc.yaml | yq '.data | to_entries[] | .key + "=" + (.value | @base64d)' -r
# Write to vault
vault kv put secret/production/myapp-secrets \
db_password="decrypted_value" \
api_key="decrypted_value"
After migration, remove the SOPS-encrypted files from Git. They are no longer needed.
What the Migration Script Does¶
Every generated migration script follows the same three-step pattern per workload:
| Step | Command | Purpose |
|---|---|---|
| 1. Annotate | kubectl annotate deployment ... |
Adds CloudTaser annotations to the pod template, triggering injection on next rollout |
| 2. Delete | kubectl delete externalsecret/sealedsecret/secret ... |
Removes the old secret resources from the cluster |
| 3. Restart | kubectl rollout restart deployment ... |
Triggers a new rollout so pods pick up the CloudTaser annotations |
The script is idempotent
Running the script multiple times is safe. kubectl annotate with --overwrite replaces existing annotations. kubectl delete on a missing resource returns a warning, not an error. kubectl rollout restart is always safe.
Vault Path Mapping¶
The migration command maps source secrets to vault paths using the following convention:
| Source | Generated Vault Path |
|---|---|
ESO: ExternalSecret with remote key prod/myapp |
secret/data/prod/myapp |
Sealed Secrets: SealedSecret generating Secret myapp-secrets in namespace production |
secret/data/production/myapp-secrets |
SOPS: Secret myapp-secrets in namespace production |
secret/data/production/myapp-secrets |
Override the path prefix with --vault-path-prefix:
cloudtaser migrate --from=eso \
--vault-address https://vault.eu.example.com \
--vault-role cloudtaser \
--vault-path-prefix "secret/data/eu"
Post-Migration Verification¶
After running the migration script:
# Verify workloads are running with CloudTaser injection
cloudtaser status -n production
# Run an audit to confirm no orphaned Kubernetes Secrets remain
cloudtaser audit \
--vault-address https://vault.eu.example.com \
-n production
# Check that pods are receiving secrets
kubectl logs <pod-name> -c <container-name> | head -20
Migrate one namespace at a time
Start with a staging or development namespace. Verify everything works before migrating production workloads.