Skip to main content

Restrict a Data Source

A constraint applies a template to one or more data sources, with the template's parameters. This page uses the two templates Kamiwaza ships, so you don't need to write any Rego. To write your own template, see Write a Template; the steps for its constraints are the same as on this page.

StepWhat
1Choose a template and its parameters
2Try the constraint on sample records
3Publish it, and check from Kaizen

Before you start​

You need:

  • Policy turned on: see Connect Your OPA.

  • A Kamiwaza administrator account, and kapi set up as in Run Your Own Connector: Before you start.

  • The data source's URN, in DATA_SOURCE. It's the value the data source was created with in Run Your Own Connector. To find it again by name:

    kapi GET /catalog/containers/ | jq -r '.[] | "\(.urn) \(.name)"'
    DATA_SOURCE='<the urn:li:container:... of your data source>'

Step 1: Choose a template and its parameters​

Kamiwaza ships two templates:

TemplateWhat it does
content_filterDrops records that contain a term, or whose marking is above a ceiling
field_needs_attributeRedacts a field unless the user has one of a set of attribute values

Both work on records, the JSON objects Kamiwaza builds from what the connector returns. Use the field names from your connector's results:

CallOne record is
Browse, searchOne item, with the fields the connector returned
QueryOne row, keyed by column name, for example {"id": "inc-4", "title": "Disk alert", "owner": "Lee"}. A redacted cell comes back as null.
FetchThe file's id, filename and mime_type, plus content for a text file. Dropping it, or redacting any field, withholds the whole file.

content_filter​

ParameterTypeMeaning
deny_termslist of stringsDrop any record that contains one of these terms, in any field name or value. Not case-sensitive.
marking_fieldstringThe record field that holds its marking, for example classification.
levelslist of stringsThe markings, lowest first, for example ["PUBLIC", "INTERNAL", "RESTRICTED"].
maxstringThe highest marking allowed through. Must be one of levels.

Set deny_terms, or the three marking parameters together, or both.

With the marking parameters set, a record whose marking is missing, or isn't in levels, is dropped. It can't be compared with the ceiling, so it isn't let through.

field_needs_attribute​

ParameterTypeMeaning
fieldstringThe record field to redact, for example owner_phone.
attributestringThe user attribute to check, for example team.
valueslist of stringsThe values that may see the field, for example ["ops-leads"].

The field is redacted whenever it's present in the record, whatever its value, unless the user's attribute has one of values. For a list attribute, one matching value is enough.

The attribute must be declared in Kamiwaza, and users must carry it. Declare it once:

kapi PUT /cluster/attribute-schema/team -d '{"type": "string[]"}'

The types are string, int, bool and string[]. Declaring the attribute adds it to Keycloak's user profile.

Then set each user's value in Keycloak. Kamiwaza's address doesn't serve the Keycloak admin console, so use Keycloak's admin CLI inside its pod. Sign it in first; the administrator password goes from the keycloak-admin Secret through standard input, never on a command line:

NS=kamiwaza
POD=$(kubectl -n "$NS" get pod -l app.kubernetes.io/name=keycloak \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n "$NS" get secret keycloak-admin -o jsonpath='{.data.password}' \
| base64 -d \
| kubectl -n "$NS" exec -i "$POD" -c keycloak -- sh -c \
'IFS= read -r p; KC_CLI_PASSWORD="$p" /opt/keycloak/bin/kcadm.sh config credentials \
--config /tmp/kcadm.config --server http://localhost:8080 --realm master --user admin'
kcadm() { kubectl -n "$NS" exec "$POD" -c keycloak -- /opt/keycloak/bin/kcadm.sh "$@" --config /tmp/kcadm.config; }

Then, for each user:

USERNAME=pat # the user's Kamiwaza username
USER_ID=$(kcadm get users -r kamiwaza -q username="$USERNAME" -q exact=true --fields id | jq -r '.[0].id')
kcadm update "users/$USER_ID" -r kamiwaza -s 'attributes.team=["ops-leads"]'
kcadm get "users/$USER_ID" -r kamiwaza | jq -c '{username, attributes}'

Check: the last command shows "team":["ops-leads"]. The value reaches Kamiwaza in the user's token at their next sign-in.

When you're done, remove the CLI's sign-in:

kubectl -n "$NS" exec "$POD" -c keycloak -- rm -f /tmp/kcadm.config

If users sign in through another identity provider, map the attribute from it instead, in that provider's Keycloak mappers.

To see a template's Rego and its full parameter schema:

kapi GET /authz/policies/templates/content_filter | jq -r .rego
kapi GET /authz/policies/templates/content_filter | jq .parameters_schema

Step 2: Try it on sample records​

Start the constraint as a draft. A draft isn't enforced; you can run it against records you supply and see what it would do. The constraint below drops any record from the data source that mentions project-x:

NAME=incident-db-terms
jq -n --arg ds "$DATA_SOURCE" '{
template: "content_filter",
parameters: {deny_terms: ["project-x"]},
match: {resources: [$ds]},
enforcement_action: "deny"
}' | kapi POST "/authz/policies/constraints/$NAME/drafts" -d @- > draft.json
DRAFT=$(jq -r '.draft_id // empty' draft.json)
[ -n "$DRAFT" ] && echo "draft $DRAFT" || cat draft.json

Constraint names use lowercase letters, digits, - and _.

The OPA picks the draft up within a few seconds. Wait for compiled:

for i in $(seq 1 12); do
STATUS=$(kapi GET "/authz/policies/drafts/$DRAFT" | jq -r .status)
echo "$STATUS"; [ "$STATUS" = pending ] || break; sleep 5
done

Then run it on sample records, as a user with the attributes you give:

kapi POST "/authz/policies/drafts/$DRAFT/dry-run" -d '{
"records": [
{"id": 1, "title": "Router reboot"},
{"id": 2, "title": "Project-X outage"}
],
"subject_attributes": {"team": ["support"]}
}' | jq .decision

Check: drop lists record index 1 (the second record, counting from 0), with reason deny_term. Records not listed are kept.

For field_needs_attribute, the decision lists fields under redact. Run it once with attributes that should see the field and once with attributes that shouldn't.

To change the parameters, start another draft with the new ones. Drafts you don't publish are removed after a day.

Step 3: Publish it​

Promote the draft. This publishes the constraint and applies it to the data sources in its match:

kapi POST "/authz/policies/drafts/$DRAFT/promote" -d '{"change_note": "Hide Project X incidents"}' \
| jq '{revision, constraint: .constraint.constraint | {name, version, match}}'

Until your OPA has loaded the change, usually within seconds, calls to that data source return an error that starts "A policy update for this data source isn't active on the policy engine yet". After that, its results are filtered.

Check: in Kaizen, ask about the data source as a user the constraint applies to. Records with the term don't appear.

You can also skip the draft and publish directly with POST /authz/policies/constraints, using the same body plus "name".

Change a constraint​

Each change writes a new version of the constraint. Earlier versions are kept.

ChangeHow
New parametersStart a draft with the new parameters (step 2), and promote it. Or replace the constraint directly: PUT /authz/policies/constraints/$NAME with parameters, match and enforcement_action, all three.
Apply it to another data sourcePOST /authz/policies/constraints/$NAME/attach with {"resource": "<urn>"}
Remove it from a data sourcePOST /authz/policies/constraints/$NAME/detach with {"resource": "<urn>"}
Roll backStart a draft with {"from_version": <n>} instead of parameters, and promote it. That writes a new version with the old content.
Stop using itkapi POST "/authz/policies/constraints/$NAME/retire" -d '{"change_note": "<why>"}'

Every change holds back that data source's results for a few seconds, until the OPA has loaded it, as when you published.

To see what is in force:

kapi GET "/authz/policies/constraints?resource=$(jq -rn --arg ds "$DATA_SOURCE" '$ds|@uri')" \
| jq '.[] | {name, template, version, parameters, enforcement_action}'
kapi GET "/authz/policies/constraints/$NAME/versions" | jq '.[] | {version, created_at, created_by, change_note}'

Several constraints can apply to one data source. Their drops and redactions all apply.

enforcement_action is deny, which applies the constraint. dryrun applies nothing and only records what the constraint would have done.