Skip to content

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.

--project names the Project whose manage permission authorizes the write, and that permission reaches only the objects the Project governs. --resource must therefore be one of:

  • the Project itself, project:<project-id>;
  • a resource:<id> or policy:<id> whose parent is that Project;
  • a labeldefinition:<id> registered at that Project or at the Domain the Project belongs to — the assigner grant the Label lesson writes.

Anything else is refused with 403 and reason: out_of_scope before it is written: platform:plexsphere, every domain:<id> (the Project's own Domain included), Projects, Resources, Policies and Label definitions of other Projects or Domains, platform Label definitions, and every other object type. That refusal exits 4, not 77: no relation the caller could be granted on the Project makes the write valid. Grant access to an object outside the Project through the surface that governs it, or address the Project that does.

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.

The list shows the tuples tuple add recorded under the Project, in id order — the relation-tuple ledger. Relationships that other paths write (the event sync, the seeds) are not in it; rebac lookup-subjects and rebac check read those.

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.

The caller needs manage on the Project the tuple was added under, and that Project must govern the existing and the replacement --resource — the rule tuple add applies. A replacement it does not govern (platform:*, domain:*, an object of another Project) is refused with 403 and reason: out_of_scope (exit 4); a missing manage is the ReBAC denial (exit 77). A replacement triple another tuple already holds is refused with 409 and code: tuple_conflict (exit 1). Every refusal leaves the existing tuple in place, and the tuple keeps its id.

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, and 404 with code: tuple_not_found (exit 4) for an id it does not hold, including a second delete of the same id. The caller needs manage on the Project the tuple was added under, and that Project must govern the tuple's resource; otherwise the delete is refused with 403 (exit 77 for the missing manage, exit 4 for out_of_scope).

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 ​

FlagTypeRequiredDefaultDescription
--subjectstringyes—Subject reference, e.g. user:<uuid>.
--relationstringyes—Relation name to test, e.g. viewer.
--resourcestringyes—Resource reference, e.g. project:<uuid>.
--caveat-contextstringno—Optional caveat context as a JSON object. A malformed value is a local exit 2 failure, not a round-trip.

plexctl rebac tuple add ​

FlagTypeRequiredDefaultDescription
--projectUUIDyes—Project whose manage permission authorizes the write. Maps to the required project_id query parameter.
--subjectstringyes—Subject reference, e.g. user:<uuid>.
--relationstringyes—Relation name, e.g. viewer.
--resourcestringyes—Resource reference, e.g. project:<uuid>. Must be an object the --project Project governs (see above).
--caveat-namestringno—Optional caveat name to attach to the tuple.
--caveat-contextstringno—Optional caveat context as a JSON object.

plexctl rebac tuple list ​

FlagTypeRequiredDefaultDescription
--projectUUIDyes—Project to list tuples for. Maps to the required project_id query parameter.
--limitintnoserver defaultMaximum items per page. 0 lets the server pick.
--cursorstringno—Continuation token from a previous call's next_cursor. Mutually exclusive with --all.
--allboolnofalseFollow next_cursor until exhausted. Capped at 100 000 items.

plexctl rebac tuple update ​

FlagTypeRequiredDefaultDescription
<id> (positional)UUIDyes—Relation-tuple UUID.
--subjectstringyes—Subject reference (full re-statement).
--relationstringyes—Relation name (full re-statement).
--resourcestringyes—Resource reference (full re-statement).
--caveat-namestringno—Optional caveat name to attach.
--caveat-contextstringno—Optional caveat context as a JSON object.

plexctl rebac tuple delete ​

FlagTypeRequiredDefaultDescription
<id> (positional)UUIDyes—Relation-tuple UUID.
--yes (persistent)boolyesfalseRequired confirmation for the destructive operation. The CLI refuses to call DeleteRelationTuple without --yes.

plexctl rebac lookup-resources ​

FlagTypeRequiredDefaultDescription
--subjectstringyes—Subject reference whose reachable resources are listed, e.g. user:<uuid>.
--relationstringyes—Relation name to evaluate, e.g. read.
--resource-typestringyes—Object type to enumerate, e.g. project. Returned items are <resource-type>:<id>.
--caveat-contextstringno—Optional caveat context as a JSON object.

plexctl rebac lookup-subjects ​

FlagTypeRequiredDefaultDescription
--subject-typestringyes—Object type to enumerate, e.g. user. Returned items are <subject-type>:<id>.
--relationstringyes—Relation name to evaluate, e.g. read.
--resourcestringyes—Resource reference whose authorised subjects are listed, e.g. project:<uuid>.
--caveat-contextstringno—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:

  • check renders RebacCheckResponse on an allowed decision — see the schema definition in ../../../api/openapi/plexsphere-v1.yaml.
  • tuple add / tuple update render RelationTuple.
  • tuple list renders RelationTupleList, with next_cursor preserved verbatim so a caller can resume pagination from the response shape.
  • lookup-resources / lookup-subjects render RebacLookupResponse (items + correlation_id). There is no cursor — items is 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.

CodeReachableMeaning
0yesThe API returned the expected status (200 for an allowed check / list / update, 201 for add, 204 for delete).
1yesRuntime / API error: transport failure, unexpected status code (including the 409 tuple_conflict of a tuple update), malformed response body, the --all safety cap fired, or tuple delete invoked without --yes.
2yesFlag-parse / misconfiguration: missing required flag, malformed UUID, mutually exclusive --all / --cursor, or a malformed --caveat-context JSON object.
3yesMissing or insecure credentials, or 401 Unauthorized from the API.
4yesPermission denied or resource not addressable: 403 Forbidden (non-ReBAC denial path, including the out_of_scope refusal of a tuple add / tuple update / tuple delete resource the Project does not govern) or 404 Not Found from the API.
64noNot reachable — rebac is fully wired and never returns *NotImplementedError.
77yesReBAC 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 allowed

A 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 $?  # 77

Create 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 maintainer \
  --resource project:0190a8b8-a0c0-7a0a-8a0a-c0c0c0c0c0c0

The same call with a resource the Project does not govern is refused before anything is written:

shell
plexctl rebac tuple add \
  --server   "${PLEXSPHERE_URL}" \
  --project  0190a8b8-a0c0-7a0a-8a0a-c0c0c0c0c0c0 \
  --subject  user:0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
  --relation admin \
  --resource platform:plexsphere
echo $?  # 4 — 403, reason out_of_scope

Page through every tuple with --all ​

shell
plexctl rebac tuple list \
  --server  "${PLEXSPHERE_URL}" \
  --project 0190a8b8-a0c0-7a0a-8a0a-c0c0c0c0c0c0 \
  --all \
  --output  json

The 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-b0b0b0b0b0b0

All 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 $?  # 0

The 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-b0b0b0b0b0b0

An 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, DeleteRelationTuple operation definitions and the RebacCheckResponse, RelationTuple, RelationTupleList schemas.
  • ../../../cmd/plexctl/commands/rebac.go — source of truth for the cobra command, flag wiring, the --all cap, the denied-check exit mapping, and the table projection.