Skip to content

Plan File Format Reference

Not yet verified; do not use for production

This describes the reviewed CLI candidate, not a qualified published migration. Signed cohort compatibility and live application cutover are not yet verified. Earlier CLIs could report migrated while old unprotected Pods kept using Kubernetes Secrets. Regenerate a v2 plan and verify the replacement application's own behavior; do not trust old status markers.

A MigrationPlan is a YAML file that describes which workloads to migrate from Kubernetes Secrets to an EU-hosted OpenBao. It is generated by cloudtaser-cli target discover -o and consumed by source apply-plan, source verify-plan, and target protect --plan.

The plan file serves as a reviewable compliance artifact. Security teams can audit it before any changes are made to the cluster or OpenBao.


Schema

apiVersion: cloudtaser.io/v2
kind: MigrationPlan
metadata:
  name: <string>
  cluster: <string>
  createdAt: <timestamp>
  createdBy: <string>
tenants:
- name: <string>
  namespace: <string>
  workloads:
  - kind: <string>
    name: <string>
    replicas: <int>
    status: <string>
    secrets:
    - source: <string>
      vaultPath: <string>
      fields:
        <field>: <ENV_VAR>
    namespace: <string>
    serviceAccountName: <live-service-account-name>

Fields

Top-level

Field Type Required Description
apiVersion string Yes Must be cloudtaser.io/v2. The CLI rejects plans with a different version.
kind string Yes Must be MigrationPlan. The CLI rejects plans with a different kind.
metadata object Yes Plan-level identification and audit fields.
tenants list Yes List of tenants. May be empty ([]).

metadata

Field Type Required Description
name string Yes Human-readable plan name. Used in CLI output and reports.
cluster string No Kubernetes API endpoint of the target cluster. Auto-populated by discover from the kubeconfig.
createdAt timestamp No When the plan was generated. ISO 8601 format.
createdBy string No Who generated the plan. Auto-populated from the OS user.

tenants[]

Field Type Required Description
name string Yes Tenant name. Used to derive OpenBao policy and role names (<name>-read, <name>-role).
namespace string Yes Kubernetes namespace containing the workloads.
workloads list Yes List of workloads in this tenant.

tenants[].workloads[]

Field Type Required Description
kind string Yes Workload kind: Deployment, StatefulSet, or DaemonSet.
name string Yes Workload name (as shown by kubectl get).
namespace string Yes Must equal the tenant namespace and live workload namespace.
serviceAccountName string Yes Exact live application SA; v1 plans must be regenerated.
replicas int No Current replica count. Informational only -- used in interactive mode output.
status string No Migration status: pending (default), migrated, or skipped. Updated by target protect --plan.
secrets list Yes Secret mappings for this workload.

tenants[].workloads[].secrets[]

Field Type Required Description
source string Yes Original secret reference. Format depends on the source type (see below).
vaultPath string Yes Target OpenBao KV v2 path. Used by apply-plan for policy creation and by verify-plan for secret existence checks.
fields map[string]string Yes for cutover Explicit field-to-environment mappings; unique non-reserved POSIX destinations. Env-map groups follow secret-path order.

Source reference format

The source field uses a prefix to indicate the origin:

Prefix Example Meaning
k8s-secret/ k8s-secret/db-credentials Kubernetes Secret named db-credentials
vault-injector/ vault-injector/secret/data/app/config Existing Vault Agent Injector annotation path

Status values

The status field on each workload tracks migration progress:

Value Meaning
pending Not yet migrated. Will be processed by target protect --plan.
migrated Candidate checks passed for a complete new owned injected rollout, with readiness probes and Ready/started running affected containers with zero restarts; application behavior and source Secret retirement still need owner checks.
skipped Explicitly skipped during interactive migration. Will not be reprocessed.

The candidate command revalidates migrated entries because older versions wrote them before verification. It skips only explicit skipped entries. Failed verification stays pending; source Secret objects are retained and reported, not deleted. A failed migration can already have changed the template; it does not automatically roll back.


How each command uses the plan

Command Reads Writes Uses
target discover -o -- Creates the plan Scans cluster, generates tenant/workload/secret structure
source apply-plan Plan file -- Creates OpenBao policies and K8s auth roles per tenant
source verify-plan Plan file -- Checks each vaultPath exists and has expected fields
target protect --plan Plan file Updates status fields Atomically updates annotations/mapped inputs, waits for verified startup and reports retained source Secrets

OpenBao naming conventions

The discover command generates OpenBao paths using the pattern:

secret/data/<namespace>/<secret-name>

For Kubernetes Secret references:

  • Secret db-credentials in namespace payments becomes secret/data/payments/db-credentials

For existing Vault Agent Injector annotations:

  • The existing OpenBao path is preserved as-is

The apply-plan command uses the vaultPath values to construct policies:

  • Policy name: <tenant-name>-read
  • Policy rules: read on secret/data/<path> and read, list on secret/metadata/<path>
  • K8s auth role: <tenant-name>-role bound to the tenant's namespace

Examples

Single-namespace deployment

apiVersion: cloudtaser.io/v2
kind: MigrationPlan
metadata:
  name: staging-migration
  cluster: https://staging.k8s.example.com
  createdAt: '2026-04-01T10:00:00Z'
  createdBy: platform-team
tenants:
- name: default
  namespace: default
  workloads:
  - kind: Deployment
    name: web-app
    replicas: 2
    status: pending
    secrets:
    - source: k8s-secret/app-config
      vaultPath: secret/data/default/app-config
      fields:
        database_url: DATABASE_URL
        redis_url: REDIS_URL
    namespace: default
    serviceAccountName: <live-service-account-name>

Multi-namespace enterprise deployment

apiVersion: cloudtaser.io/v2
kind: MigrationPlan
metadata:
  name: production-migration
  cluster: https://prod.k8s.example.com
  createdAt: '2026-04-01T10:00:00Z'
  createdBy: platform-team
tenants:
- name: payments
  namespace: payments
  workloads:
  - kind: Deployment
    name: payment-api
    replicas: 3
    status: pending
    secrets:
    - source: k8s-secret/db-credentials
      vaultPath: secret/data/payments/db-credentials
      fields:
        username: DB_USER
        password: DB_PASS
    - source: k8s-secret/stripe-key
      vaultPath: secret/data/payments/stripe-key
      fields:
        secret_key: STRIPE_SECRET_KEY
    namespace: payments
    serviceAccountName: <live-service-account-name>
  - kind: Deployment
    name: payment-worker
    replicas: 2
    status: pending
    secrets:
    - source: k8s-secret/db-credentials
      vaultPath: secret/data/payments/db-credentials
      fields:
        username: DB_USER
        password: DB_PASS
    namespace: payments
    serviceAccountName: <live-service-account-name>
- name: trading
  namespace: trading
  workloads:
  - kind: StatefulSet
    name: trading-engine
    replicas: 1
    status: pending
    secrets:
    - source: k8s-secret/api-key
      vaultPath: secret/data/trading/api-key
      fields:
        key: API_KEY
    namespace: trading
    serviceAccountName: <live-service-account-name>
  - kind: DaemonSet
    name: market-data-collector
    status: pending
    secrets:
    - source: k8s-secret/feed-credentials
      vaultPath: secret/data/trading/feed-credentials
    namespace: trading
    serviceAccountName: <live-service-account-name>

Partially completed migration

After running target protect --plan interactively and approving only the first workload:

apiVersion: cloudtaser.io/v2
kind: MigrationPlan
metadata:
  name: production-migration
tenants:
- name: payments
  namespace: payments
  workloads:
  - kind: Deployment
    name: payment-api
    replicas: 3
    status: migrated
    secrets:
    - source: k8s-secret/db-credentials
      vaultPath: secret/data/payments/db-credentials
      fields:
        username: DB_USER
        password: DB_PASS
    namespace: payments
    serviceAccountName: <live-service-account-name>
  - kind: Deployment
    name: payment-worker
    replicas: 2
    status: pending
    secrets:
    - source: k8s-secret/db-credentials
      vaultPath: secret/data/payments/db-credentials
      fields:
        username: DB_USER
        password: DB_PASS
    namespace: payments
    serviceAccountName: <live-service-account-name>

Workload with no field mappings

When fields is omitted, all fields in OpenBao secret are injected using their original key names as environment variable names:

workloads:
  - kind: Deployment
    name: simple-app
    status: pending
    secrets:
      - source: k8s-secret/app-secrets
        vaultPath: secret/data/default/app-secrets
        # No fields mapping -- all vault fields injected as-is

Editing plans

Plans are standard YAML files. You can edit them with any text editor to:

  • Remove workloads that should not be migrated
  • Adjust OpenBao paths to match your naming convention
  • Fix field mappings if the auto-detected env var names are wrong
  • Change tenant names (this affects the generated policy and role names)
  • Reset status from skipped to pending to re-include a previously skipped workload

The only fields that must not be changed are apiVersion and kind -- the CLI validates these on load.