Migration Plan Workflow¶
The migration plan workflow is the recommended approach for enterprise deployments. It uses a YAML plan file as a reviewable compliance artifact that captures every workload, secret mapping, and vault path before any changes are made.
The workflow has four stages:
- Discover -- scan the target cluster and generate a plan
- Apply -- provision vault policies and Kubernetes auth roles from the plan
- Verify -- confirm that all secret values exist in vault
- Migrate -- apply CloudTaser annotations and rolling-restart workloads
The plan file travels between teams (platform, security, application owners) without requiring direct access between the vault and the Kubernetes cluster. This makes the workflow suitable for air-gapped environments and multi-cluster deployments.
Prerequisites¶
| Tool | Purpose |
|---|---|
cloudtaser CLI v0.7+ |
Generates and consumes plan files |
kubectl |
Kubernetes cluster access |
bao or vault CLI |
Populating secret values in vault |
| An EU-hosted OpenBao/Vault instance | Secret storage |
| A running Kubernetes cluster | Target for migration |
Step 1: Discover¶
Scan the target cluster for workloads that reference Kubernetes Secrets or Vault injector annotations. The --output-plan flag (-o) writes a MigrationPlan YAML file instead of SecretMapping CRDs.
This scans all non-system namespaces for Deployments, StatefulSets, and DaemonSets. Workloads already annotated with cloudtaser.io/inject: "true" are skipped. System namespaces (kube-system, kube-public, kube-node-lease) are excluded.
To scope the scan to a single namespace:
To use a specific kubeconfig (for example, when managing multiple clusters):
When --output-plan is used, the --vault-address and --vault-role flags are not required. The plan file captures workload-to-vault-path mappings without needing vault connectivity at discovery time.
What the plan contains¶
The generated plan groups workloads by namespace as tenants. Each workload lists its secret references with suggested vault paths following the pattern secret/data/<namespace>/<secret-name>.
apiVersion: cloudtaser.io/v1
kind: MigrationPlan
metadata:
name: migration-plan
cluster: https://k8s.prod.example.com
createdAt: "2026-04-01T10:00:00Z"
createdBy: platform-team
tenants:
- name: payments
namespace: payments
workloads:
- kind: Deployment
name: payment-api
replicas: 3
secrets:
- source: k8s-secret/db-credentials
vaultPath: secret/data/payments/db-credentials
fields:
username: DB_USER
password: DB_PASS
status: pending
See Plan File Format Reference for the full schema.
Review the plan¶
The plan file is a compliance artifact. Security and platform teams should review it before proceeding:
- Are the vault paths correct for your naming convention?
- Are the field-to-environment-variable mappings accurate?
- Should any workloads be excluded?
Edit the plan file directly to adjust paths, field mappings, or remove workloads that should not be migrated.
Step 2: Apply the Plan to Vault¶
The source apply-plan command reads the plan and provisions vault with the policies and Kubernetes auth roles required for each tenant. It does NOT write secret values.
cloudtaser source apply-plan plan.yaml \
--openbao-addr https://vault.eu.example.com \
--token hvs.ADMIN_TOKEN
For each tenant in the plan, this command:
- Ensures the KV v2 secrets engine is mounted (default mount:
secret) - Creates a vault policy
<tenant-name>-readgranting read access to the tenant's secret paths - Creates a Kubernetes auth role
<tenant-name>-rolebound to the tenant's namespace
Flags¶
| Flag | Description | Required | Default |
|---|---|---|---|
--openbao-addr |
Vault/OpenBao address | Yes | -- |
--token |
Vault token with admin privileges | Yes | -- |
--dry-run |
Print what would be created without making changes | No | false |
--auth-path |
Kubernetes auth mount path | No | kubernetes |
--kv-mount |
KV v2 mount path | No | secret |
Preview with dry run¶
Always preview before applying:
cloudtaser source apply-plan plan.yaml \
--openbao-addr https://vault.eu.example.com \
--token hvs.ADMIN_TOKEN \
--dry-run
Example dry-run output:
Applying plan "migration-plan" to https://vault.eu.example.com
(dry-run mode -- no changes will be made)
Would ensure KV mount:
secret (ensure mounted)
Would create policies:
payments-read
Would create roles:
payments-role
Re-run without --dry-run to execute.
Step 3: Populate Secret Values¶
After apply-plan creates the vault structure, populate the actual secret values. This step is manual -- the CLI deliberately does not move secret values to preserve the separation of concerns.
Use the bao or vault CLI:
export BAO_ADDR=https://vault.eu.example.com
export BAO_TOKEN=hvs.ADMIN_TOKEN
# Write secrets for each vault path in the plan
bao kv put secret/payments/db-credentials \
username="app_user" \
password="s3cret-from-eu-vault"
Where to get the values
- From existing Kubernetes Secrets: extract with
kubectl get secret <name> -n <namespace> -o jsonpath='{.data}'and base64-decode - From the source of truth: password manager, CI/CD variables, or cloud provider secret manager
- From
cloudtaser target import: import directly from AWS Secrets Manager, GCP Secret Manager, or Azure Key Vault
Step 4: Verify the Plan¶
Before migrating workloads, verify that every secret path in the plan exists in vault and has the expected fields populated.
cloudtaser source verify-plan plan.yaml \
--openbao-addr https://vault.eu.example.com \
--token hvs.ADMIN_TOKEN
Flags¶
| Flag | Description | Required | Default |
|---|---|---|---|
--openbao-addr |
Vault/OpenBao address | Yes | -- |
--token |
Vault token | Yes | -- |
Verification checks¶
For each secret mapping in the plan, verify-plan checks:
- The vault path exists
- The path has data (values populated, not empty)
- All fields listed in the mapping exist in the vault data
Example output¶
Plan Verification: plan.yaml
========================================
Tenant: payments (namespace: payments)
payment-api:
v secret/data/payments/db-credentials -- exists, 2 field(s) populated
Result: 1/1 secrets ready.
If any secret is missing or incomplete:
Plan Verification: plan.yaml
========================================
Tenant: payments (namespace: payments)
payment-api:
x secret/data/payments/db-credentials -- path not found
Result: 0/1 secrets ready. 1 need(s) attention.
The command exits with a non-zero status if any secrets are not ready, making it suitable for CI/CD gates.
Step 5: Migrate Workloads¶
Once all secrets are verified, apply CloudTaser protection to the workloads in the plan using target protect --plan.
Interactive mode (recommended for first migration)¶
cloudtaser target protect --plan plan.yaml \
--vault-address https://vault.eu.example.com \
--interactive
In interactive mode, each workload is presented with its secret mappings. You are prompted to confirm injection and rolling restart for each workload individually:
--- Deployment payments/payment-api (namespace: payments, replicas: 3) ---
k8s-secret/db-credentials -> secret/data/payments/db-credentials
username -> DB_USER
password -> DB_PASS
Inject CloudTaser protection? [y/N] y
-> injected
Trigger rolling restart? [y/N] y
-> restarted
-> migrated
Auto-approve mode (CI/automation)¶
For non-interactive environments:
The --yes flag auto-approves all workloads without prompting.
Flags¶
| Flag | Description | Required | Default |
|---|---|---|---|
--plan |
Path to the MigrationPlan YAML file | Yes | -- |
--vault-address |
Vault/OpenBao address | Yes | -- |
--interactive |
Prompt for each workload | No | false |
--yes |
Auto-approve all workloads (CI mode) | No | false |
--kubeconfig |
Path to kubeconfig file | No | ~/.kube/config |
-n, --namespace |
Namespace filter | No | all namespaces |
What migrate does¶
For each workload in the plan with status: pending:
- Patches the pod template with CloudTaser annotations:
cloudtaser.io/inject: "true"cloudtaser.io/vault-address: <vault-address>cloudtaser.io/secret-paths: <comma-separated vault paths>cloudtaser.io/env-map: <vault-path:field=ENV_VAR,...>
- Optionally triggers a rolling restart by adding a
cloudtaser.io/restartedAtannotation - Updates the workload's
statusin the plan file (migrated,skipped, orpending)
Resumable migration¶
The plan file is updated after each workload. If the migration is interrupted, re-run the same command -- workloads already marked as migrated or skipped are not processed again.
# Resume after interruption
cloudtaser target protect --plan plan.yaml \
--vault-address https://vault.eu.example.com \
--interactive
Post-Migration Verification¶
After migrating workloads, confirm everything is working:
# Check workload protection status
cloudtaser target status
# Validate vault connectivity and protection coverage
cloudtaser target validate \
--vault-address https://vault.eu.example.com
# Generate a compliance audit report
cloudtaser target audit \
--vault-address https://vault.eu.example.com
Check that pods are running with the CloudTaser wrapper:
# Verify init container was injected
kubectl get pod -l app=payment-api -n payments \
-o jsonpath='{.items[0].spec.initContainers[*].name}'
# Expected: cloudtaser-init
# Verify secrets are NOT in /proc/1/environ
kubectl exec deploy/payment-api -n payments -- \
cat /proc/1/environ | tr '\0' '\n' | grep DB_PASS
# Expected: no output
Complete Example¶
This section walks through the entire workflow for a cluster with two namespaces (payments and trading).
1. Discover¶
2. Review and edit the plan¶
3. Apply vault structure (dry run first)¶
cloudtaser source apply-plan plan.yaml \
--openbao-addr https://vault.eu.example.com \
--token hvs.ADMIN_TOKEN \
--dry-run
# Then apply for real
cloudtaser source apply-plan plan.yaml \
--openbao-addr https://vault.eu.example.com \
--token hvs.ADMIN_TOKEN
4. Populate secrets¶
bao kv put secret/payments/db-credentials username=app password=s3cret
bao kv put secret/trading/api-key key=ak-12345
5. Verify¶
cloudtaser source verify-plan plan.yaml \
--openbao-addr https://vault.eu.example.com \
--token hvs.ADMIN_TOKEN
6. Migrate interactively¶
cloudtaser target protect --plan plan.yaml \
--vault-address https://vault.eu.example.com \
--interactive
7. Confirm¶
Related¶
- Plan File Format Reference -- full YAML schema documentation
- Enterprise Deployment Architecture -- CLI-as-orchestrator model and multi-cluster topology
- Migration Guide -- legacy
--frombased migration (ESO, Sealed Secrets, SOPS) - CLI Reference -- full command documentation