Migration Plan Workflow¶
Not yet verified; do not use for production
The checks below describe candidate CLI source and focused test evidence;
a signed matched release and live end-to-end migration are not yet verified.
An earlier live CLI reported migrated while admission refused the new
Pods and old unprotected Pods kept using Kubernetes Secrets. A successful
patch, Available Deployment, source verification or old status marker is
insufficient. Regenerate a v2 plan, verify the replacement application
through its Service and review source Secret retirement separately.
This workflow uses a YAML plan file to review workloads, secret mappings and OpenBao paths before changes. This draft describes the separate direct-store migration candidate; it is not a way to qualify or repair the US-cluster/EU-Port customer procedure. Do not run its legacy source-auth setup against the managed authority procedure.
The workflow has four stages:
- Discover -- scan the target cluster and generate a plan
- Apply -- provision OpenBao policies and Kubernetes auth roles from the plan
- Verify -- confirm that all secret values exist in OpenBao
- Migrate -- apply cloudtaser annotations and rolling-restart workloads
The plan file can be reviewed by platform, security and application owners. It does not prove runtime connectivity: direct workload login and external Kubernetes TokenReview still need their configured network paths and CA trust.
Prerequisites¶
| Tool | Purpose |
|---|---|
| Reviewed CLI migration candidate | Generates and consumes v2 plans; release qualification is not yet verified |
kubectl |
Kubernetes cluster access |
bao or vault CLI |
Populating secret values in OpenBao |
| An EU-hosted OpenBao instance | Secret storage |
| A running Kubernetes cluster | Target for migration |
Step 1: Discover¶
Scan the target cluster for workloads that reference Kubernetes Secrets or OpenBao 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 secretstore.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 --secretstore-address and --secretstore-role flags are not required. The plan file captures workload-to-OpenBao-path mappings without needing OpenBao 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 OpenBao paths following the pattern secret/data/<namespace>/<secret-name>.
apiVersion: cloudtaser.io/v2
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
namespace: payments
serviceAccountName: <live-service-account-name>
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 OpenBao 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 OpenBao¶
The source apply-plan command reads the plan and provisions OpenBao with the policies and Kubernetes auth roles required for each tenant. It does NOT write secret values.
cloudtaser-cli source apply-plan plan.yaml \
--secretstore-address https://vault.eu.example.com \
--ca-cert ./source-ca.pem \
--secretstore-token-file ./private-bootstrap.token
For each tenant in the plan, this command:
- Ensures the KV v2 secrets engine is mounted (default mount:
secret) - Creates an OpenBao 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
In the candidate documented here, the role is also bound to the exact workload
ServiceAccounts recorded in the v2 plan. Confirm that the final apply-plan
writer and protect consumer use the same role convention before cutover;
integration with other role-writing changes is not yet verified. An older
wildcard role left on the source can defeat narrow tenant roles. Inventory and
reconcile existing grants; creating a new role alone does not prove isolation.
Flags¶
| Flag | Description | Required | Default |
|---|---|---|---|
--secretstore-address |
OpenBao address | Yes | -- |
--secretstore-token-file |
Private admin token file or - for stdin |
Yes | -- |
--ca-cert, --secretstore-ca-file |
Public source CA PEM | No | -- |
--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-cli source apply-plan plan.yaml \
--secretstore-address https://vault.eu.example.com \
--ca-cert ./source-ca.pem \
--secretstore-token-file ./private-bootstrap.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.
Dry-run renders intended changes without contacting the store. It is not a CA, authentication, connectivity or secret-existence receipt.
Step 3: Populate Secret Values¶
After apply-plan creates OpenBao 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-cli 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 OpenBao and has the expected fields populated.
cloudtaser-cli source verify-plan plan.yaml \
--secretstore-address https://vault.eu.example.com \
--ca-cert ./source-ca.pem \
--secretstore-token-file ./private-bootstrap.token
Flags¶
| Flag | Description | Required | Default |
|---|---|---|---|
--secretstore-address |
OpenBao address | Yes | -- |
--secretstore-token-file |
Private admin token file or - for stdin |
Yes | -- |
--ca-cert, --secretstore-ca-file |
Public source CA PEM; otherwise configured/system trust | No | -- |
Verification checks¶
For each secret mapping in the plan, verify-plan checks:
- OpenBao path exists
- The path has data (values populated, not empty)
- All fields listed in the mapping exist in OpenBao 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-cli target protect --plan plan.yaml \
--secretstore-address https://vault.eu.example.com \
--interactive
In interactive mode, each workload is presented with its secret mappings. One approval permits the atomic template change and its resulting rollout; there is no second restart patch:
--- Deployment payments/payment-api ---
Inject CloudTaser protection? [y/N] y
-> migrated (new injected rollout Ready; mapped Secret references removed)
Source Secret retained: payments/db-credentials; retirement requires separate consumer/rollback review.
Auto-approve mode (CI/automation)¶
For non-interactive environments:
cloudtaser-cli target protect --plan plan.yaml \
--secretstore-address https://vault.eu.example.com \
--yes
The --yes flag auto-approves all workloads without prompting.
Flags¶
| Flag | Description | Required | Default |
|---|---|---|---|
--plan |
Path to the MigrationPlan YAML file | Yes | -- |
--secretstore-address |
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 |
Source-based scanning only; plan mode requires exact namespaces in the reviewed file | No | -- |
What migrate does¶
The candidate source and focused tests implement these checks for each approved workload; successful live cutover is not yet verified:
- Check the live workload namespace/SA against the reviewed v2 plan, then make
one resourceVersion-guarded template update. It sets canonical
secretstore.cloudtaser.io/inject: "true", the store address,cloudtaser.io/secretstore-role: <tenant>-role, secret paths and env-map. - Encode env-map as
password=DB_PASS,username=DB_USER;key=API_KEY: commas separate fields, semicolons separate groups in secret-path order. No path prefix belongs before a field. Explicit field mappings are required; ambiguous destinations or unsupported source volumes/envFrom refuse. - Remove only exact mapped Kubernetes
secretKeyRefinputs from the template. The template update itself starts the rollout. A paused/zero Deployment or OnDelete/partitioned update requires a separate cutover plan. - Wait for the whole current rollout, with exact controller ownership, current
generation, webhook-redacted annotation digests, wrapper runtime config and
Ready/started running containers. Each affected container must have a
readiness probe and zero restarts. The CLI checks probe presence; you must
make the probe sensitive to actual application delivery. A probe-less Pod
that reports Ready or an old Available replica is insufficient. Any refusal, crash,
incomplete rollout, identity drift or deadline leaves
pendingand exits nonzero; only verified startup is counted asmigrated. - Retain and explicitly report each source Secret for later reviewed retirement. This does not claim that its plaintext or historical copies have left etcd.
Failure does not automatically roll back a template update. Inspect the current
template, owned Pods, rollout conditions and application Service before retry
or an owner-reviewed rollback. A retained source Secret alone does not restore
its removed Pod references. Keep the original reviewed workload manifest and
rollback procedure; a pending result is not a guarantee of unchanged workloads.
Private source-CA trust must also be configured in the operator/workload profile;
apply-plan --ca-cert configures the CLI's source connection only. If the audience
fence is enabled, approve any alternate workload endpoint before cutover.
The startup checks establish that the new wrapper fetched/mapped secrets and started the replacement app without the mapped Secret fallback. They do not inspect app memory or prove that its runtime API consumed the value. Qualify the image/delivery mode, and verify a digest or a real application operation through its own Service before retiring the source Secret.
| Additional plan flag | Meaning |
|---|---|
--rollout-timeout 5m |
Per-workload maximum verification wait; caller cancellation can shorten it |
--dry-run |
Validate/render intent without workload or plan-status writes; not a connectivity receipt |
Resumable migration¶
The plan is saved after the run, including per-workload pending/error results. Re-running revalidates historical migrated entries instead of trusting an older CLI's marker; explicitly skipped entries remain skipped. A status-save failure is an error. Keep and review the plan, not just the summary count.
# Resume after interruption
cloudtaser-cli target protect --plan plan.yaml \
--secretstore-address https://vault.eu.example.com \
--interactive
Post-Migration Verification¶
After migrating workloads, confirm everything is working:
# Check workload protection status
cloudtaser-cli target status
# Validate vault connectivity and protection coverage
cloudtaser-cli target validate \
--secretstore-address https://vault.eu.example.com
# Generate a compliance audit report
cloudtaser-cli target audit \
--secretstore-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
# Inspect replacement readiness and Service membership without protected exec.
kubectl get pods -n payments
kubectl get endpointslices -n payments
Source Secret retirement¶
After the application's own value/behavior check, inventory other Pod and controller references, rollback needs, ESO/GitOps producers, backups and etcd retention. Stop or update any producer that would recreate the Secret. Only an authorized owner may then delete the exact obsolete source Secret. Never bulk- delete a namespace's Secrets from the plan alone. The migration command does not do that destructive action or assert erasure of old data/backups.
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