Skip to content

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
sluice policy test policies.d --tests tests/any.yaml
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 a catalogs, schemas or tables pattern.
  • An unknown op or actions value loads, but it never matches.
  • In SQL, a table without catalog matches only * and ** in catalogs and schemas.
  • A claim name that holds a dot cannot be checked.
  • An exclude with all on a SqlAccessPolicy works per query, not per table.

See also