Policy bindings#
A policy says what is allowed. A binding says who it applies to.
Create a binding#
curl -fsS -X POST \
-H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
-H "Content-Type: application/json" \
https://agents.example.com/v1/projects/$PROJECT_ID/policy-bindings \
-d '{
"name": "platform-team",
"policyIds": ["123e4567-...", "223e4567-..."],
"subjects": [{"kind": "user", "userId": "alex@example.com"}]
}'
In the production project, bind the agent-base and github-readonly
policies to alex@example.com. Call the binding platform-team.
nexusctl policy-binding create --project production \
--name platform-team \
--policy 123e4567-e89b-12d3-a456-426614174000 \
--policy 223e4567-e89b-12d3-a456-426614174000 \
--subject user=alex@example.com
--policy takes a policy UUID and is repeatable; merge order is left to right. --subject is repeatable too.
Bindings exist at project scope (--project) and org scope (--org).
Subjects#
| Kind | Form | Applies to |
|---|---|---|
everyone |
--subject everyone |
Every principal in scope. |
user |
--subject user=<sub> |
One person. |
api-token |
--subject api-token=<uuid> |
One API token. |
The API also accepts an all_sandboxes subject kind for policies that should reach every sandbox in scope.
Sandboxes need no binding
A sandbox attaches policies directly with --policy at creation. It is its own principal, so there is nothing to bind it to. Bindings exist for users and API tokens.
Merge order#
Several bindings can reach the same principal. position decides the order they merge in: lower merges first, later wins on conflict.
nexusctl policy-binding create --project production \
--name baseline --position 0 --policy $BASE --subject everyone
nexusctl policy-binding create --project production \
--name oncall-elevated --position 10 --policy $ELEVATED --subject user=sam@example.com
Everyone gets the baseline. The on-call engineer's elevated binding merges afterwards and wins where the two disagree.
Give the baseline a low position and exceptions higher ones, spaced apart, so a new tier can be inserted later without renumbering.
The organization ceiling#
Org-scoped policies are a ceiling, not a default. A project binding that grants more than the org policy permits is clipped to the org policy when the effective policy is resolved.
Nothing fails loudly when that happens, so check for it:
curl -fsS -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
https://agents.example.com/v1/orgs/$ORG_ID/policy-bindings/drift
Check the acme org for policy binding drift. For each entry, tell
me what the project tried to grant and what the org ceiling cut.
nexusctl policy-binding list-drift --org acme
Drift entries are where a project team believes it granted something the organization does not allow. Each one is either a project binding to correct or an org policy to widen deliberately.
Make this a periodic review. It is the cheapest governance check the platform offers.
Inspect#
curl -fsS -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
https://agents.example.com/v1/projects/$PROJECT_ID/policy-bindings
List the policy bindings in the production project and tell me who
each one applies to.
nexusctl policy-binding list --project production
nexusctl policy-binding describe <binding-id> --project production
describe resolves policy UUIDs to names and renders subjects for humans, which turns a binding from a list of identifiers into something reviewable.
Update and delete#
nexusctl policy-binding update <binding-id> --project production --position 20
nexusctl policy-binding delete <binding-id> --project production
Unspecified fields are left alone. --clear-description blanks the description.
Related#
- Policies — the documents bindings attach
- Identity — the principals bindings target
- API tokens — binding policies to non-interactive identities
- Sandboxes — attaching policies without a binding