Matching¶
spec.match says which subjects and tables a policy covers. spec.exclude takes some of them out again.
Example¶
Analysts read orders. Support agents read tickets.
apiVersion: sluice.bino.bi/v1alpha1
kind: SqlAccessPolicy
metadata:
name: allow-reporting
priority: 100
spec:
effect: allow
match:
any:
- subjects:
groups: ["analysts"]
resources:
catalogs: ["shop"]
schemas: ["main"]
tables: ["orders"]
- subjects:
groups: ["support"]
resources:
catalogs: ["shop"]
schemas: ["main"]
tables: ["tickets"]
The suite:
cases:
- name: an analyst matches the first clause
identity: { subject: alice, groups: [analysts] }
sql: "SELECT id FROM shop.main.orders"
expect:
outcome: allow
- name: a support agent matches the second clause
identity: { subject: sam, groups: [support] }
sql: "SELECT id FROM shop.main.tickets"
expect:
outcome: allow
- name: a support agent matches no clause for orders
identity: { subject: sam, groups: [support] }
sql: "SELECT id FROM shop.main.orders"
expect:
outcome: deny
PASS an analyst matches the first clause
PASS a support agent matches the second clause
PASS a support agent matches no clause for orders
3 passed, 0 failed, 3 total
What happened: under any, one matching clause is enough. In a clause, subjects and resources must both match.
Fields¶
| Field | Type | Default | Meaning |
|---|---|---|---|
any | list | none | Clauses. One must match. |
all | list | none | Clauses. All must match. |
subjects | object | every subject | The subjects of a clause. |
resources | object | every table | The tables of a clause. |
With both any and all, one any clause or all all clauses must match.
A selector works per table or per query:
| Kind | match | exclude |
|---|---|---|
SqlAccessPolicy, RowFilterPolicy, ColumnMaskPolicy | Covers each table that matches. | Removes single tables. |
QueryRejectPolicy, QueryRewritePolicy, ApprovalPolicy | One matching table is enough. | One excluded table switches the policy off. |
Subjects¶
Every field that you set must match.
| Field | Matches when |
|---|---|
groups | The subject is in one of the groups. |
roles | The roles claim holds one of the roles. Without the claim, the groups count. |
apiKeys | The subject used an API key and its subject id is listed. |
jwtClaims | Every claim check passes. |
ipRanges | The address of the client is in one of the CIDR ranges, such as 10.20.0.0/16. Behind a proxy, this is the address of the proxy. |
A check has claim, op and a value. A dot in claim steps into a nested claim.
op | Passes when |
|---|---|
Equals | The claim equals value. |
NotEquals | The claim is missing or differs from value. |
In | The claim equals one of values. |
NotIn | The claim is missing or equals none of values. |
Exists | The claim is present. |
Matches | The claim is text that matches the regular expression pattern. |
Types must agree: the number 3 does not equal the text "3".
Resources¶
| Field | Matches |
|---|---|
catalogs, schemas, tables | One part each of catalog.schema.table. Case does not matter. |
columns | Column names. Only column masks use it. |
tags | Tables and columns with a tag. |
actions | The statement type. See Access control. |
A pattern must match the whole name.
| Pattern | Meaning |
|---|---|
* | Any text without a dot. |
** | Any text. |
\*, \. | A literal * or dot. |
More examples¶
All clauses must match¶
A subject needs both groups.
apiVersion: sluice.bino.bi/v1alpha1
kind: SqlAccessPolicy
metadata:
name: allow-invoices
priority: 100
spec:
effect: allow
match:
all:
- subjects:
groups: ["finance"]
- subjects:
groups: ["managers"]
- resources:
catalogs: ["shop"]
schemas: ["main"]
tables: ["invoices"]
- name: a finance manager matches every clause
identity: { subject: fiona, groups: [finance, managers] }
sql: "SELECT id FROM shop.main.invoices"
expect:
outcome: allow
- name: finance alone is not enough
identity: { subject: frank, groups: [finance] }
sql: "SELECT id FROM shop.main.invoices"
expect:
outcome: deny
Roles and claims¶
Auditors from Germany or Austria read audit_notes.
apiVersion: sluice.bino.bi/v1alpha1
kind: SqlAccessPolicy
metadata:
name: allow-auditors
priority: 100
spec:
effect: allow
match:
any:
- subjects:
roles: ["auditor"]
jwtClaims:
- claim: org.country
op: In
values: ["DE", "AT"]
resources:
catalogs: ["shop"]
schemas: ["main"]
tables: ["audit_notes"]
- name: role and claim both match
identity:
subject: anna
claims: { roles: [auditor], org: { country: DE } }
sql: "SELECT id FROM shop.main.audit_notes"
expect:
outcome: allow
- name: without a roles claim the groups count as roles
identity:
subject: ben
groups: [auditor]
claims: { org: { country: AT } }
sql: "SELECT id FROM shop.main.audit_notes"
expect:
outcome: allow
- name: the claim check fails for another country
identity:
subject: carl
claims: { roles: [auditor], org: { country: US } }
sql: "SELECT id FROM shop.main.audit_notes"
expect:
outcome: deny
Wildcards¶
Analysts read some lake tables.
apiVersion: sluice.bino.bi/v1alpha1
kind: SqlAccessPolicy
metadata:
name: allow-lake
priority: 100
spec:
effect: allow
match:
any:
- subjects:
groups: ["analysts"]
resources:
catalogs: ["lake"]
schemas: ["*"]
tables: ["events_*", "export**", 'sales\.2024']
- name: the star matches the rest of the table name
identity: { subject: alice, groups: [analysts] }
sql: "SELECT id FROM lake.raw.events_2026"
expect:
outcome: allow
- name: another table name does not match
identity: { subject: alice, groups: [analysts] }
sql: "SELECT id FROM lake.raw.sessions"
expect:
outcome: deny
- name: names match without regard to case
identity: { subject: alice, groups: [analysts] }
sql: "SELECT id FROM LAKE.Raw.Events_2026"
expect:
outcome: allow
- name: a table without catalog and schema does not match
identity: { subject: alice, groups: [analysts] }
sql: "SELECT id FROM events_2026"
expect:
outcome: deny
Exclude a group¶
The sre group reads email without the mask.
apiVersion: sluice.bino.bi/v1alpha1
kind: ColumnMaskPolicy
metadata:
name: mask-email
priority: 50
spec:
match:
any:
- resources:
catalogs: ["shop"]
schemas: ["crm"]
columns: ["email"]
exclude:
any:
- subjects:
groups: ["sre"]
mask:
type: hash
- name: an analyst reads hashed email addresses
identity: { subject: alice, groups: [analysts] }
sql: "SELECT email FROM shop.crm.leads"
expect:
outcome: allow
masks: ["shop.crm.leads.email=hash"]
- name: the sre group is excluded from the mask
identity: { subject: sara, groups: [sre] }
sql: "SELECT email FROM shop.crm.leads"
expect:
outcome: allow
rewrittenSql: "SELECT email FROM shop.crm.leads"
Which policy wins¶
One mask applies per column: the one with the highest priority. At equal priority, the mask with more names without a wildcard wins. Other kinds have no such rule. This mask has the priority of mask-email:
apiVersion: sluice.bino.bi/v1alpha1
kind: ColumnMaskPolicy
metadata:
name: mask-email-customers
priority: 50
spec:
match:
any:
- resources:
catalogs: ["shop"]
schemas: ["crm"]
tables: ["customers"]
columns: ["email"]
mask:
type: "null"
- name: at equal priority the more exact selector wins
identity: { subject: alice, groups: [analysts] }
sql: "SELECT email FROM shop.crm.customers"
expect:
outcome: allow
masks: ["shop.crm.customers.email=null"]
- name: other tables get the wider mask
identity: { subject: alice, groups: [analysts] }
sql: "SELECT email FROM shop.crm.leads"
expect:
outcome: allow
masks: ["shop.crm.leads.email=hash"]
Limits¶
- These are load errors: an empty
spec.match, an unknown tag, a dot in acatalogs,schemasortablespattern. - An unknown
oporactionsvalue loads, but it never matches. - In SQL, a table without catalog matches only
*and**incatalogsandschemas. - A claim name that holds a dot cannot be checked.
- An
excludewithallon aSqlAccessPolicyworks per query, not per table.