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.
| Step | What |
|---|---|
| 1 | Choose a template and its parameters |
| 2 | Try the constraint on sample records |
| 3 | Publish it, and check from Kaizen |
Before you start
You need:
-
Policy turned on: see Connect Your OPA.
-
A Kamiwaza administrator account, and
kapiset 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:
| Template | What it does |
|---|---|
content_filter | Drops records that contain a term, or whose marking is above a ceiling |
field_needs_attribute | Redacts 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:
| Call | One record is |
|---|---|
| Browse, search | One item, with the fields the connector returned |
| Query | One row, keyed by column name, for example {"id": "inc-4", "title": "Disk alert", "owner": "Lee"}. A redacted cell comes back as null. |
| Fetch | The 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
| Parameter | Type | Meaning |
|---|---|---|
deny_terms | list of strings | Drop any record that contains one of these terms, in any field name or value. Not case-sensitive. |
marking_field | string | The record field that holds its marking, for example classification. |
levels | list of strings | The markings, lowest first, for example ["PUBLIC", "INTERNAL", "RESTRICTED"]. |
max | string | The 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
| Parameter | Type | Meaning |
|---|---|---|
field | string | The record field to redact, for example owner_phone. |
attribute | string | The user attribute to check, for example team. |
values | list of strings | The 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.
| Change | How |
|---|---|
| New parameters | Start 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 source | POST /authz/policies/constraints/$NAME/attach with {"resource": "<urn>"} |
| Remove it from a data source | POST /authz/policies/constraints/$NAME/detach with {"resource": "<urn>"} |
| Roll back | Start a draft with {"from_version": <n>} instead of parameters, and promote it. That writes a new version with the old content. |
| Stop using it | kapi 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.