Skip to main content

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 rowWriteEffect we saw
Resource registry, pbac.operator.controls.resource_registryPATCH 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_entitlementsPATCH client_id.<client id> with grant_types and granted_resourcesA client registered for authorization_code only could get client_credentials tokens for the type.
Denylist, pbac.operator.controls.denylistPUT {"client_ids": [<client id>]}Its token requests were refused. The audit row's reason was client_denied.
Trusted issuers, pbac.operator.controls.trustPUT {"trusted_issuers": [<issuer>]}Clients could register with software statements from that issuer.
Token lifetime, through a binding in pbac.operator.segmentsA 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.