← Control PlaneCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Control Plane
Snapshot Sep 30, 2026 · 23:00 UTC · version 1.0.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "setup-secret",
"description": "Secret access wiring and manifest authoring. Use when a workload needs to read a secret, the user asks to create a secret or generate secret YAML, configure a pull secret, or fix a deployment paused on a secret reference.",
"included_files": [],
"skill_md_contents": "---\nname: setup-secret\ndescription: Secret access wiring and manifest authoring. Use when a workload needs to read a secret, the user asks to create a secret or generate secret YAML, configure a pull secret, or fix a deployment paused on a secret reference.\n---\n\n# Secret Access Setup\n\n> **Secrets are read-only through this app.** `list_resources` / `get_resource` (kind=\"secret\") show existence and metadata, never values. No tool creates, edits, deletes, or reveals a secret: secret data and lifecycle are managed by the user (Console, CLI, Terraform, Pulumi, or the API); you draft manifests with placeholders, the user fills the values and applies. `grant_workload_secret_access` grants a workload access — it never returns values.\n\nSecret access is the #1 thing users get wrong: a workload reads a secret only when **three** things are all in place. Miss any one and the value is silently absent at runtime — or the deployment pauses on an unresolved reference.\n\n## The mandatory chain\n\n| Step | What must be true | Without it |\n|---|---|---|\n| **1. Identity** | an identity exists and is linked to the workload (`spec.identityLink`) | workload has no API credential — reads nothing |\n| **2. Policy** | a policy grants that identity `reveal` on the secret | reference resolves to empty |\n| **3. Reference** | the secret is injected as `cpln://secret/NAME` (env or volume) | nothing to read |\n\n`reveal`, **not** `view` — `view` exposes only metadata. This is the single most common mistake.\n\n## Pull secrets are different — no identity/policy\n\nTo pull images from a private registry, don't build the chain. Add the registry secret to the **GVC's** `pullSecretLinks` and every workload in that GVC can pull. Pull secrets are registry credentials — `docker`, `ecr`, or `gcp` types.\n\n```yaml\nkind: gvc\nspec:\n pullSecretLinks:\n - //secret/my-registry\n```\n\n## Authoring a secret manifest — the user applies it\n\nDrafting the manifest is an expected part of the job — users ask for a scaffold, fill in the real values themselves, and apply it. Generate the YAML with UPPERCASE placeholders, then always hand back the next steps:\n\n1. **Fill in the placeholders locally** — the value never enters the chat.\n2. **Apply it**: `cpln apply -f secret.yaml --org ORG`, the Console's **cpln apply** button (paste the YAML), or the per-type CLI command that reads the value from a file (`cpln secret create-docker --name NAME --file config.json`).\n3. **Treat the filled file as a live credential** — keep it out of git and delete it after applying.\n4. **Say when it's done** — verify with `get_resource` (kind=\"secret\") and continue with the access chain below.\n\nNever ask for the real value in chat, and never apply the manifest yourself.\n\n`data` has a fixed shape per `type`, validated by the backend on create. The trap: **for `docker`, `gcp`, and `azure-sdk`, `data` is a single JSON string** (a `>-` block scalar in YAML), never a YAML mapping — an object is rejected.\n\n```yaml\nkind: secret\nname: my-registry\ntype: docker\ndata: >-\n {\"auths\":{\"REGISTRY_HOST\":{\"username\":\"USERNAME\",\"password\":\"PASSWORD\"}}}\n```\n\n| `type` | `data` | Backend validation |\n|---|---|---|\n| `opaque` | object `{payload, encoding?}` | `payload` valid base64 when `encoding: base64` (default `plain`) |\n| `dictionary` | object of string values | keys match `[-._a-zA-Z0-9]+` |\n| `userpass` | object `{username, password, encoding?}` | — |\n| `tls` | object `{cert, key?, chain?}` | `cert` and `key` must be valid PEM |\n| `keypair` | object `{secretKey, publicKey?, passphrase?}` | `secretKey` a valid PEM private key |\n| `aws` | object `{accessKey, secretKey, roleArn?, externalId?}` | `accessKey` starts `AKIA`, `roleArn` starts `arn:` |\n| `ecr` | aws fields + `repos` (1–20) | each `ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com[/REPO]` |\n| `azure-connector` | object `{url, code}` | `url` must be https |\n| `nats-account` | object `{accountId, privateKey}` | `accountId` a public nkey (`A…`), `privateKey` a seed (`SA…`) |\n| `docker` | **JSON string** | must parse with an `auths` object keyed by registry host, at least one entry |\n| `gcp` | **JSON string** | full service-account key: `type`, `project_id`, `private_key_id`, `private_key`, `client_email`, `client_id`, `auth_uri`, `token_uri`, `auth_provider_x509_cert_url`, `client_x509_cert_url` |\n| `azure-sdk` | **JSON string** | `subscriptionId` / `tenantId` / `clientId` (UUIDs) plus `clientSecret` |\n\n`get_resource_schema` (kind=\"secret\") returns the apply schema and REST endpoints.\n\n## Workflow\n\n### 1 — Identify the secret\n\nThe secret must already exist — the user creates and rotates it through any Control Plane surface: Console, CLI (value-in-a-file, never an inline flag), Terraform, Pulumi, or the API. Confirm it exists with `list_resources` or `get_resource` (kind=\"secret\") before wiring anything; never ask for the value in chat and never invent a placeholder. If it does not exist yet, author the manifest (section above) and wait until the user has applied it.\n\n### 2 — Grant the workload access\n\n**Preferred — one call.** `grant_workload_secret_access` (`gvc`, `workloadName`, `secretName`) creates the identity if missing (default `{gvc}-{workloadName}`), links it to the workload, and creates/updates a `reveal` policy (default `{gvc}-{workloadName}-secrets-policy`). It never returns secret values, and it does **not** inject the reference — step 3 still applies.\n\n**Manual alternative** (granular control): `create_identity` → `update_workload` to set `spec.identityLink` → `create_policy` (targetKind `secret`, a `reveal` binding naming the identity). Policy shape lives in **access-control**.\n\n**Ordering matters.** The workload must already exist. For a new workload that references a secret: `create_workload` first (its deployment pauses on the unresolved reference), then grant — the deployment resumes.\n\nIdentities are **GVC-scoped**: one per workload, shareable across workloads in the same GVC, never across GVCs.\n\n### 3 — Inject the reference\n\n`update_workload` (read current state with `get_resource` first) to add `cpln://secret/NAME` — the whole secret — or `cpln://secret/NAME.KEY` for one property:\n\n| Type | Keys | Example |\n|---|---|---|\n| opaque | `payload` | `cpln://secret/api-key.payload` |\n| userpass | `username`, `password` | `cpln://secret/creds.password` |\n| tls | `key`, `cert`, `chain` | `cpln://secret/web-tls.cert` |\n| dictionary | user-defined | `cpln://secret/cfg.DB_HOST` |\n| aws / ecr | `accessKey`, `secretKey`, `roleArn` | `cpln://secret/aws.accessKey` |\n\nInject as an **env var** or a **volume mount** (`{ uri: \"cpln://secret/NAME\", path: \"/secrets/x\" }`). Mounts are read-only (except Azure Files), max **15** per container, and these knative-reserved paths are rejected: `/dev`, `/dev/log`, `/tmp`, `/var`, `/var/log`.\n\n### 4 — Verify and redeploy\n\n- `get_resource` (kind=\"workload\") → `spec.identityLink` is set and the env/volume reference reads `cpln://secret/…`.\n- `get_resource` (kind=\"policy\") → the binding grants `reveal` to that identity.\n- Updating a workload spec redeploys automatically; via CLI use `cpln apply --ready` to block until healthy. **A rotated secret value needs a redeploy** — running replicas keep the old value until then.\n\n## Quick reference — MCP tools\n\n| Tool | Purpose |\n|---|---|\n| `grant_workload_secret_access` | Composite — identity + `reveal` policy + link, in one call |\n| `create_identity` / `create_policy` | Build the access chain manually (granular control) |\n| `update_workload` | Set `identityLink`; inject the env / volume reference |\n| `list_resources` / `get_resource` (kind=\"secret\") | Confirm a secret exists / read its metadata |\n\n## Common mistakes\n\n- **Object `data` on a docker / gcp / azure-sdk secret** — those three types take one JSON string; a YAML mapping fails validation.\n- **No identity** — a workload with no `identityLink` reads no secrets.\n- **`view` instead of `reveal`** — metadata only, no value.\n- **Bad reference** — must be `cpln://secret/NAME`, not the bare name.\n- **Granting before the workload exists** — the workload comes first.\n- **Sharing an identity across GVCs** — they are GVC-scoped.\n- **Over-engineering pull secrets** — registries need only `pullSecretLinks`, no identity/policy.\n- **Skipping the redeploy after rotation** — running replicas keep the old value.\n\n## Related skills\n\n| Need | Skill |\n|---|---|\n| Policy shape, permissions, principals | `access-control` |\n| Workload identities, cloud / private-network access | `native-networking` |\n| Workload spec, deploy, env vars | `workload` |\n\n## Documentation\n\n- [Secret Reference](https://docs.controlplane.com/reference/secret.md)\n"
}SHA-256: 02d7d68dd9b0a51109467e13f997ba3ef6bf9352901ae9fd38df94d48945e38f