Skip to content
Deployment
Skill

/access-control

Primary skill for access control, policies, and RBAC on Control Plane. Use when the user asks about permissions, policies, service accounts, user access, group membership, bindings, who can do what, least-privilege, or IAM.

From plugin
ai-plugin
1030 skills2 agents2 commands1 MCP
Install
$ npx -y skills add controlplane-com/ai-plugin --skill access-control --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/access-control

Context preview

The summary Claude sees to decide when to auto-load this skill.

Primary skill for access control, policies, and RBAC on Control Plane. Use when the user asks about permissions, policies, service accounts, user access, group membership, bindings, who can do what, least-privilege, or IAM.

SKILL.md

access-control.SKILL.md
name: access-control
description: "Primary skill for access control, policies, and RBAC on Control Plane. Use when the user asks about permissions, policies, service accounts, user access, group membership, bindings, who can do what, least-privilege, or IAM."

Access Control & Policies — Primary Skill

> **Tool availability:** some MCP tools named here live in the `full` toolset profile — if one is not advertised on this connection, tell the user to reconnect the MCP server with `?toolsets=full` (or use the `cpln` CLI fallback). Reads work on every profile via the generic `list_resources` / `get_resource` tools; `delete_resource` is on every profile except `readonly`.

A **policy** targets one resource kind and binds **permissions** to **principals** (users, groups, service accounts, workload identities). The common failure is a policy that **exists but grants nothing** — a wrong `targetKind`, permission name, or principal link fails with no error — so read the policy back after writing.

The model

| Layer | Scope | Controls | |---|---|---| | **Billing-account roles** | account-wide (set on the billing account, separate from org policies) | `billing_admin`, `billing_viewer`, `org_creator` — none grant org-resource access | | **Org policies** | per resource kind | all day-to-day access |

A caller is allowed an action when some policy on the resource's kind has a binding that lists that action (or a permission implying it) **and** names the caller — or a group the caller is in.

Policy anatomy

A policy = one **`targetKind`** + a **target scope** + **bindings**.

kind: policy
name: app-secret-access
targetKind: secret           # exactly one resource kind
targetLinks:                 # OR `target: all` OR `targetQuery:` (pick one)
  - //secret/database-url
bindings:                    # ≤ 50
  - permissions: [reveal, use]
    principalLinks:          # 1–200
      - //gvc/production/identity/app-identity

**Limits:** `bindings` ≤ 50; `principalLinks` 1–200 per binding; `targetLinks` ≤ 200. `origin` is read-only — `default` (yours) or `builtin` (locked; see Built-ins).

**Target scope (pick one):** `target: all` (org-wide roles) · `targetLinks` (specific resources) · `targetQuery` (tag query — see **query-spec**).

**Valid target kinds:** the `targetKind` enum accepts all **33** resource kinds, but only the **20 kinds with permission schemas** (the ones `get_permissions` accepts) are meaningful targets: `workload, secret, gvc, identity, image, org, policy, group, serviceaccount, user, volumeset, domain, location, ipset, mk8s, cloudaccount, agent, auditctx, quota, task`.

**Create vs update:**

  • `create_policy` builds **one** binding from `addPermissions` × (`addUsers`/`addGroups`/`addServiceAccounts`/`addIdentities`) — providing only one side is an **error** (a binding needs both). It also requires **exactly one** target scope (`targetAll` / `targetLinks` / `targetQuery`). For several distinct bindings, use `update_policy` `addBindings`.
  • `update_policy` **merges** bindings (matched by exact permission set — you can't extend a set) but **replaces** targets (`targetLinks` wholesale, `removeTargetLinks` incremental, `targetAll` — mutually exclusive).

Permissions

**`get_permissions` (`kind`) is the source of truth** — it returns the kind's exact permission list and its implication map. Confirm names with it before writing a policy; never hand-write them, and don't assume a kind only has `create` / `delete` / `edit` / `view` / `manage`.

Two traps that don't need a lookup:

  • **`manage` implies every permission** for a kind — grant it only to true admins.
  • **Secret values need `reveal`, not `read`** — the most common mistake.

Many kinds add non-obvious permissions beyond CRUD — e.g. secret `reveal`/`use`, image `pull`, workload `connect`/`exec.*`, serviceaccount `addKey`, user `invite`/`impersonate`, mk8s `clusterAdmin` — so pull the real set with `get_permissions`.

Principals

| Type | Link | |---|---| | User | `//user/EMAIL` | | Group (preferred) | `//group/NAME` | | Service account | `//serviceaccount/NAME` | | Workload identity | `//gvc/GVC/identity/NAME` (**GVC-scoped** — never `//identity/NAME`) |

An **identity never belongs to a group**, so authorize it only with a binding that names its exact link. At runtime the attached identity is the workload's API credential: calls from inside the workload with the injected `CPLN_TOKEN` against `CPLN_ENDPOINT` carry exactly the permissions policies grant that identity — nothing more.

Groups

Members are **users and service accounts only** (≤ 200). Bind policies to groups, not individuals.

  • **Create / edit:** `create_group` (`name`, `memberLinks`, `memberQuery`, `identityMatcher`); `edit_group` (`addMemberLinks` / `removeMemberLinks` — read first with `get_resource` (kind="group")).
  • **Dynamic:** `memberQuery` matches users by tag query; `identityMatcher` matches identities by a `jmespath`/`javascript` expression.

Service accounts (non-human / CI/CD)

  • **Key:** `add_key_to_service_account` (`serviceAccountName`, **`keyDescription` required**, optional `groupName`) — **auto-creates the SA if missing** and returns the key **once** (save it; lost = revoke + remint). `create_service_account` makes one with no key.
  • **Revoke:** `update_service_account` `removeKeys: [NAME]` (immediate). `delete_resource` (kind="service_account") revokes all keys.
  • **CI/CD auth:** store the key as the `CPLN_TOKEN` secret/env var — the CLI uses it ahead of any profile (and works without one); don't pass `--token` on the command line (it leaks into logs). Full setup: **gitops-cicd**.

Users (IDP-backed)

  • **Invite:** `invite_user_to_org` (`email`, optional `groupName`).
  • **Read / remove:** `get_resource` (kind="user") / `delete_resource` (kind="user") take **`identifier`** (id or email); `list_resources` (kind="user") has an `email` filter. No `create_user`/`update_user`.

Built-ins (seeded per org)

| Resource |

Read more
Ships withai-plugin

Run containerized workloads across AWS, GCP, Azure, OCI, and your own hardware under one API.

Get the whole plugin

Other skills on ai-plugin.