Control data
Most shipped controls read their settings from a data row named pbac.operator.controls.<control>. You change what such a control does by writing its row. You write no Rego and restart nothing. Controls and segments explains the model. This page is the how-to.
The examples use these two values.
export POLICYARC_URL="https://your-environment.example"
export POLICYARC_ADMIN_KEY="paste the Admin API key"
List the controls and their settings
curl -fsS "$POLICYARC_URL/admin/api/controls" -H "X-Admin-API-Key: $POLICYARC_ADMIN_KEY" |
jq -r '.[] | "\(.id)\t\(.settings | join(" "))"'
Each line is a control id and its settings, written as <row key>#<setting>. Our instance listed 22 controls. The denylist control's line reads as follows.
denylist pbac.operator.controls.denylist#client_ids pbac.operator.controls.denylist#subjects pbac.operator.controls.denylist#domains pbac.operator.controls.denylist#software_ids pbac.operator.controls.denylist#issuers
GET /admin/api/controls/<id> returns the control's whole manifest, with its shape, its reason codes and the request fields it reads.
Read a row
curl -fsS "$POLICYARC_URL/admin/api/policy-data" -H "X-Admin-API-Key: $POLICYARC_ADMIN_KEY" |
jq '.[] | select(.dataKey == "pbac.operator.controls.resource_registry") | .payload | fromjson'
The list returns every row. Each row's payload is a JSON string, so fromjson turns it back into an object. A row that has never been written is not in the list, and the control then uses its built-in behaviour.
Write a row
PUT /admin/api/policy-data/by-key creates the row or replaces all of it. The body names the key again and carries the row as payload.
curl -fsS -X PUT "$POLICYARC_URL/admin/api/policy-data/by-key?dataKey=pbac.operator.controls.denylist" \
-H "X-Admin-API-Key: $POLICYARC_ADMIN_KEY" \
-H 'Content-Type: application/json' \
-d '{"dataKey": "pbac.operator.controls.denylist", "payload": {"client_ids": ["agent-under-review"]}}'
PATCH on the same path sets one value and leaves the rest of the row alone. Name the value with a dotted path and send the new value as the body. It returns 404 when the row does not exist yet. The path splits on dots, so it cannot reach a key that contains a dot. Use PUT for those.
curl -fsS -X PATCH "$POLICYARC_URL/admin/api/policy-data/by-key?dataKey=pbac.operator.controls.resource_registry&path=resource.types.urn:example:reports" \
-H "X-Admin-API-Key: $POLICYARC_ADMIN_KEY" \
-H 'Content-Type: application/json' \
-d '{"scopes": ["read", "write"]}'
A control row that has a schema is checked on every PUT and PATCH. A row that fails returns HTTP 422 and is not saved. We sent {"client_id": "x"} to the denylist row, which spells the setting client_ids.
{
"dataKey": "pbac.operator.controls.denylist",
"error": "Policy data failed schema validation",
"failures": ["$: property 'client_id' is not defined in the schema and the schema does not allow additional properties"]
}
When a change takes effect
A data write returns as soon as the row is saved. The policy engine loads it a few seconds later. When we asked for a token straight after entitling a client, the first request was refused with grant_type_not_allowed and a retry a few seconds later worked. Wait a few seconds before you check a change.
Changes we made and what they did
We ran each of these on a test instance.
| Control and row | Write | Effect we saw |
|---|---|---|
Resource registry, pbac.operator.controls.resource_registry | PATCH resource.types.urn:example:reports to {"scopes": ["read", "write"]} | The type could be requested with resource_types=urn:example:reports. |
Client entitlements, pbac.operator.controls.client_entitlements | PATCH client_id.<client id> with grant_types and granted_resources | A client registered for authorization_code only could get client_credentials tokens for the type. |
Denylist, pbac.operator.controls.denylist | PUT {"client_ids": [<client id>]} | Its token requests were refused. The audit row's reason was client_denied. |
Trusted issuers, pbac.operator.controls.trust | PUT {"trusted_issuers": [<issuer>]} | Clients could register with software statements from that issuer. |
Token lifetime, through a binding in pbac.operator.segments | A binding on the dcr_clients segment with {"ttl_override": 120} | Tokens for clients registered without a statement came back with "expires_in":120. A client registered with a statement still got its usual lifetime. |
The last row shows a binding at work. A binding scopes a control's settings to a segment. Only some controls read bindings. Controls and segments explains which.
Rows of your own
A custom control can read a row you create, under any key you choose. The policy patterns use keys that start with custom., such as custom.reports_allowlist. A row with no schema is saved as you send it. Send a JSON object as the payload. Read it in Rego by its literal key, as in data["custom.reports_allowlist"].
The oauth_config row
Older guides put every setting under one row, oauth_config. Each control now has a row of its own. The shipped token, authorize and register policies do not read oauth_config, and a new instance ships it empty.
Keep secrets out
Every row is sent to the policy engine in the policy bundle. Do not put API keys, passwords or tokens in a row. Store them with /admin/api/secrets.
Send feedback about this page