Appearance
plexctl rebac
Synopsis
plexctl rebac is the operator surface for the ReBAC authorization model. It wraps the typed /v1/authz operations (PostAuthzCheck, ListRelationTuples, CreateRelationTuple, PatchRelationTuple, DeleteRelationTuple) so an operator can answer a single authorization question or manage relation tuples without hand-rolling curl calls.
The expand verb is intentionally not provided: there is no /v1/authz/expand operation in the public v1 contract, so wiring it would require a contract addition first.
Invocation
text
plexctl rebac check [flags]
plexctl rebac tuple <add|list|update|delete> [flags]
plexctl rebac lookup-resources [flags]
plexctl rebac lookup-subjects [flags]Every subcommand is a private cobra constructor; flags are validated at parse time so an invalid UUID or a missing required flag surfaces as exit 2 instead of a wire round-trip.
plexctl rebac check
Answers a single subject→relation→resource question via POST /v1/authz/check. --subject, --relation, and --resource are required. A successful query whose decision is allowed renders the typed RebacCheckResponse and exits 0. A successful query whose decision is denied is not exit 0: it maps to the dedicated ReBAC-denial exit code (77) — the same code an enforced 403 rebac_denied response yields — so a runbook branches on one "you lack the relation" signal regardless of which path produced it. The reason and correlation id are written to stderr on a denial.
plexctl rebac tuple add
Creates a relation tuple via POST /v1/authz/relation-tuples. The project_id query parameter is required by the wire operation (a non-pointer UUID), so it surfaces as a required --project flag rather than a server round-trip. --subject, --relation, and --resource are required; --caveat-name and --caveat-context are optional. The handler renders the 201 RelationTuple.
--relation takes a relation, not a permission. Relations are stored — they are what a tuple is — while permissions are computed from them by the schema, so there is nothing to write a permission into. Naming one (act, manage, observe, …) is refused with 400 and code: invalid_triple; grant a relation the permission derives from instead. For a Resource, act derives from owner, maintainer, and operator, so --relation operator is the smallest grant that confers it. The ReBAC schema reference lists the relations and permissions of every object type.
plexctl rebac tuple list
Lists relation tuples via GET /v1/authz/relation-tuples. The --project flag is required (the wire project_id query parameter is mandatory). Supports keyset pagination via --cursor and an opt-in --all flag that walks every page until next_cursor is empty. The --all accumulator is capped at 100 000 items as defence-in-depth against a runaway server.
plexctl rebac tuple update
Updates a relation tuple via PATCH /v1/authz/relation-tuples/{id}. The wire RelationTuplePatchRequest marks subject, relation, and resource as required, so the PATCH is a full re-statement of the triple — not a sparse update. All three flags are therefore required; --caveat-name and --caveat-context are optional.
plexctl rebac tuple delete
Deletes a relation tuple via DELETE /v1/authz/relation-tuples/{id}. The destructive operation is gated behind the root persistent --yes flag — mirroring domain delete, group delete, and audit erase-identity — so a single typo of the tuple id cannot wipe a grant without explicit confirmation. The server returns 204 No Content on success.
plexctl rebac lookup-resources
Enumerates every resource of --resource-type the --subject can reach via --relation, calling POST /v1/authz/lookup-resources. The result is materialised data — an empty list is a normal answer and exits 0, never an error. There is no --all/cursor: the server drains SpiceDB's pagination internally and returns the full set.
plexctl rebac lookup-subjects
The dual of lookup-resources: enumerates every subject of --subject-type that can reach --resource via --relation, calling POST /v1/authz/lookup-subjects. Same materialised-data posture.
Flags
plexctl rebac check
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--subject | string | yes | — | Subject reference, e.g. user:<uuid>. |
--relation | string | yes | — | Relation name to test, e.g. viewer. |
--resource | string | yes | — | Resource reference, e.g. project:<uuid>. |
--caveat-context | string | no | — | Optional caveat context as a JSON object. A malformed value is a local exit 2 failure, not a round-trip. |
plexctl rebac tuple add
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--project | UUID | yes | — | Project the tuple is scoped to. Maps to the required project_id query parameter. |
--subject | string | yes | — | Subject reference, e.g. user:<uuid>. |
--relation | string | yes | — | Relation name, e.g. viewer. |
--resource | string | yes | — | Resource reference, e.g. project:<uuid>. |
--caveat-name | string | no | — | Optional caveat name to attach to the tuple. |
--caveat-context | string | no | — | Optional caveat context as a JSON object. |
plexctl rebac tuple list
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--project | UUID | yes | — | Project to list tuples for. Maps to the required project_id query parameter. |
--limit | int | no | server default | Maximum items per page. 0 lets the server pick. |
--cursor | string | no | — | Continuation token from a previous call's next_cursor. Mutually exclusive with --all. |
--all | bool | no | false | Follow next_cursor until exhausted. Capped at 100 000 items. |
plexctl rebac tuple update
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
<id> (positional) | UUID | yes | — | Relation-tuple UUID. |
--subject | string | yes | — | Subject reference (full re-statement). |
--relation | string | yes | — | Relation name (full re-statement). |
--resource | string | yes | — | Resource reference (full re-statement). |
--caveat-name | string | no | — | Optional caveat name to attach. |
--caveat-context | string | no | — | Optional caveat context as a JSON object. |
plexctl rebac tuple delete
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
<id> (positional) | UUID | yes | — | Relation-tuple UUID. |
--yes (persistent) | bool | yes | false | Required confirmation for the destructive operation. The CLI refuses to call DeleteRelationTuple without --yes. |
plexctl rebac lookup-resources
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--subject | string | yes | — | Subject reference whose reachable resources are listed, e.g. user:<uuid>. |
--relation | string | yes | — | Relation name to evaluate, e.g. read. |
--resource-type | string | yes | — | Object type to enumerate, e.g. project. Returned items are <resource-type>:<id>. |
--caveat-context | string | no | — | Optional caveat context as a JSON object. |
plexctl rebac lookup-subjects
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--subject-type | string | yes | — | Object type to enumerate, e.g. user. Returned items are <subject-type>:<id>. |
--relation | string | yes | — | Relation name to evaluate, e.g. read. |
--resource | string | yes | — | Resource reference whose authorised subjects are listed, e.g. project:<uuid>. |
--caveat-context | string | no | — | Optional caveat context as a JSON object. |
Persistent flags inherited from root
--server, --profile, --token-file, --output, --yes, --reveal-secrets. The --yes flag is consumed by tuple delete; the others apply unchanged. See ../plexctl.md for the canonical list.
Output JSON schema reference
JSON and YAML output mirror the typed wire shapes verbatim:
checkrendersRebacCheckResponseon analloweddecision — see the schema definition in../../../api/openapi/plexsphere-v1.yaml.tuple add/tuple updaterenderRelationTuple.tuple listrendersRelationTupleList, withnext_cursorpreserved verbatim so a caller can resume pagination from the response shape.lookup-resources/lookup-subjectsrenderRebacLookupResponse(items+correlation_id). There is no cursor —itemsis the full materialised set.
The text output mode emits a tabwriter grid: DECISION, REASON, CORRELATION_ID for check, ID, SUBJECT, RELATION, RESOURCE, CREATED_AT for the tuple operations, and a single OBJECT column (one object reference per row) for the lookup operations. Reach for --output json when the caveat context or the full relation path matters.
Exit codes
plexctl collapses every failure into the taxonomy below; the single source of truth is exitCodeFor in ../../../cmd/plexctl/app.go.
| Code | Reachable | Meaning |
|---|---|---|
0 | yes | The API returned the expected status (200 for an allowed check / list / update, 201 for add, 204 for delete). |
1 | yes | Runtime / API error: transport failure, unexpected status code, malformed response body, the --all safety cap fired, or tuple delete invoked without --yes. |
2 | yes | Flag-parse / misconfiguration: missing required flag, malformed UUID, mutually exclusive --all / --cursor, or a malformed --caveat-context JSON object. |
3 | yes | Missing or insecure credentials, or 401 Unauthorized from the API. |
4 | yes | Permission denied or resource not addressable: 403 Forbidden (non-ReBAC denial path) or 404 Not Found from the API. |
64 | no | Not reachable — rebac is fully wired and never returns *NotImplementedError. |
77 | yes | ReBAC denial. Either a successful rebac check whose decision is denied, or a 403 Forbidden whose typed *output.APIError reports IsRebacDenied() == true. Both map here so callers branch on one "you lack the relation" code. |
Examples
Check whether a subject has a relation
shell
export PLEXSPHERE_URL="${PLEXSPHERE_URL:-https://localhost:8080}"
plexctl rebac check \
--server "${PLEXSPHERE_URL}" \
--subject user:0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--relation viewer \
--resource project:0190a8b8-a0c0-7a0a-8a0a-b0b0b0b0b0b0 \
--output json
echo $? # 0 when allowedA denied decision writes the reason to stderr and exits 77:
shell
plexctl rebac check \
--server "${PLEXSPHERE_URL}" \
--subject user:0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--relation editor \
--resource project:0190a8b8-a0c0-7a0a-8a0a-b0b0b0b0b0b0
# stderr: plexctl: rebac check: denied (reason=insufficient_relation, correlation_id=...)
echo $? # 77Create a relation tuple
shell
plexctl rebac tuple add \
--server "${PLEXSPHERE_URL}" \
--project 0190a8b8-a0c0-7a0a-8a0a-c0c0c0c0c0c0 \
--subject user:0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--relation owner \
--resource project:0190a8b8-a0c0-7a0a-8a0a-b0b0b0b0b0b0Page through every tuple with --all
shell
plexctl rebac tuple list \
--server "${PLEXSPHERE_URL}" \
--project 0190a8b8-a0c0-7a0a-8a0a-c0c0c0c0c0c0 \
--all \
--output jsonThe walk follows next_cursor until empty and aborts with exit 1 if the accumulator passes 100 000 items.
Re-state a relation tuple
shell
plexctl rebac tuple update 0190a8b8-a0c0-7a0a-8a0a-d0d0d0d0d0d0 \
--server "${PLEXSPHERE_URL}" \
--subject user:0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--relation viewer \
--resource project:0190a8b8-a0c0-7a0a-8a0a-b0b0b0b0b0b0All three of --subject, --relation, --resource are required — the PATCH body re-states the full triple because the wire schema marks them required.
Delete with --yes
shell
plexctl rebac tuple delete 0190a8b8-a0c0-7a0a-8a0a-d0d0d0d0d0d0 \
--server "${PLEXSPHERE_URL}" \
--yes
echo $? # 0The server returns 204 No Content on success; the empty body is treated as a no-op and the dispatcher reports exit 0.
Enumerate reachable resources / authorised subjects
shell
plexctl rebac lookup-resources \
--server "${PLEXSPHERE_URL}" \
--subject user:0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--relation read \
--resource-type project \
--output json
plexctl rebac lookup-subjects \
--server "${PLEXSPHERE_URL}" \
--subject-type user \
--relation read \
--resource project:0190a8b8-a0c0-7a0a-8a0a-b0b0b0b0b0b0An empty items array is a normal answer and still exits 0 — the lookup succeeded, the set is just empty.
Cross-references
../../../api/openapi/plexsphere-v1.yaml—PostAuthzCheck,ListRelationTuples,CreateRelationTuple,PatchRelationTuple,DeleteRelationTupleoperation definitions and theRebacCheckResponse,RelationTuple,RelationTupleListschemas.../../../cmd/plexctl/commands/rebac.go— source of truth for the cobra command, flag wiring, the--allcap, the denied-check exit mapping, and the table projection.