Skip to content

Plan File Format Reference

A MigrationPlan is a YAML file that describes which workloads to migrate from Kubernetes Secrets to an EU-hosted vault. It is generated by cloudtaser 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 vault.


Schema

apiVersion: cloudtaser.io/v1       # Required. Must be exactly "cloudtaser.io/v1"
kind: MigrationPlan                 # Required. Must be exactly "MigrationPlan"
metadata:
  name: <string>                    # Required. Plan name for identification
  cluster: <string>                 # Optional. Cluster API endpoint (auto-populated by discover)
  createdAt: <timestamp>            # Optional. ISO 8601 timestamp (auto-populated by discover)
  createdBy: <string>               # Optional. Username (auto-populated from OS user)
tenants:                            # Required. List of tenants (grouped by namespace)
  - name: <string>                  # Required. Tenant name (typically matches namespace)
    namespace: <string>             # Required. Kubernetes namespace
    workloads:                      # Required. List of workloads in this tenant
      - kind: <string>             # Required. Deployment, StatefulSet, or DaemonSet
        name: <string>             # Required. Workload name
        replicas: <int>            # Optional. Current replica count (informational)
        status: <string>           # Optional. pending, migrated, or skipped
        secrets:                   # Required. Secret mappings for this workload
          - source: <string>       # Required. Original secret reference
            vaultPath: <string>    # Required. Target vault KV v2 path
            fields:                # Optional. Field-to-environment-variable mapping
              <field>: <ENV_VAR>

Fields

Top-level

Field Type Required Description
apiVersion string Yes Must be cloudtaser.io/v1. 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 vault 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).
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 vault KV v2 path. Used by apply-plan for policy creation and by verify-plan for secret existence checks.
fields map[string]string No Maps vault secret fields to environment variable names. If omitted, all fields in the vault secret are injected with their original key names.

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 Successfully migrated. CloudTaser annotations applied and rolling restart triggered.
skipped Explicitly skipped during interactive migration. Will not be reprocessed.

When target protect --plan runs, it skips workloads with migrated or skipped status. This makes the migration resumable -- if interrupted, re-run the same command and only pending workloads are processed.


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 vault 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 Patches workloads with CloudTaser annotations

Vault naming conventions

The discover command generates vault 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 vault 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/v1
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

Multi-namespace enterprise deployment

apiVersion: cloudtaser.io/v1
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
      - 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
  - 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
      - kind: DaemonSet
        name: market-data-collector
        status: pending
        secrets:
          - source: k8s-secret/feed-credentials
            vaultPath: secret/data/trading/feed-credentials

Partially completed migration

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

apiVersion: cloudtaser.io/v1
kind: MigrationPlan
metadata:
  name: production-migration
tenants:
  - name: payments
    namespace: payments
    workloads:
      - kind: Deployment
        name: payment-api
        replicas: 3
        status: migrated          # Already done
        secrets:
          - source: k8s-secret/db-credentials
            vaultPath: secret/data/payments/db-credentials
            fields:
              username: DB_USER
              password: DB_PASS
      - kind: Deployment
        name: payment-worker
        replicas: 2
        status: pending           # Will be processed on next run
        secrets:
          - source: k8s-secret/db-credentials
            vaultPath: secret/data/payments/db-credentials
            fields:
              username: DB_USER
              password: DB_PASS

Workload with no field mappings

When fields is omitted, all fields in the vault 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 vault 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.