Conditions¶
spec.conditions holds CEL expressions. A policy applies only when its selector matches and every condition is true.
Example¶
Only subjects with the claim tier: gold read reports.
apiVersion: sluice.bino.bi/v1alpha1
kind: SqlAccessPolicy
metadata:
name: allow-reports-gold
priority: 100
spec:
effect: allow
match:
any:
- resources:
catalogs: ["shop"]
schemas: ["main"]
tables: ["reports"]
conditions:
# The "in" test guards the lookup: a missing claim gives false, not an error.
- name: gold-tier
expression: "'tier' in subject.claims && subject.claims.tier == 'gold'"
The suite:
cases:
- name: the gold tier reads reports
identity: { subject: gina, claims: { tier: gold } }
sql: "SELECT id FROM shop.main.reports"
expect:
outcome: allow
- name: another tier is denied
identity: { subject: sam, claims: { tier: silver } }
sql: "SELECT id FROM shop.main.reports"
expect:
outcome: deny
- name: a subject without the claim is denied, with no error
identity: { subject: nora }
sql: "SELECT id FROM shop.main.reports"
expect:
outcome: deny
PASS the gold tier reads reports
PASS another tier is denied
PASS a subject without the claim is denied, with no error
3 passed, 0 failed, 3 total
What happened: only the first subject passes the condition. No policy allows the other two subjects, so Sluice denies them.
Fields¶
| Field | Type | Default | Meaning |
|---|---|---|---|
conditions[].name | string | none | Shown in error messages. |
conditions[].expression | string | required | CEL expression that returns a boolean. |
Sluice checks every expression at load. A value alone, such as query.has_cte, is a load error. Write query.has_cte == true.
Variables¶
| Variable | Type | Value |
|---|---|---|
subject.id, subject.email, subject.issuer | string | Id, email and issuer of the subject. |
subject.groups | list | Groups of the subject. |
subject.claims | map | Claims of the subject. |
subject.auth_method | string | jwt or api_key. |
request.remote_ip, request.user_agent | string | IP address and user agent of the client. Empty over stdio. |
request.headers | map | Request headers, names in lower case: request.headers["x-region"]. Credential headers are left out. |
query.tables | list | Tables as catalog.schema.table, lower case. A missing part stays empty: ..orders. |
query.catalogs | list | Catalogs that the query reads. |
query.joins | int | Count of JOIN keywords and of commas between FROM items. |
query.has_limit, query.limit | boolean, int | The query has a LIMIT, and its value, or 0 when that is not a plain number, such as 500 + 1 or '500'. |
query.has_where, query.where_columns | boolean, list | The query has a WHERE, and its columns. |
query.is_aggregate, query.group_by_columns | boolean, list | The query aggregates, and its GROUP BY columns. |
query.has_select_star, query.has_order_by, query.has_union | boolean | The query has *, ORDER BY, or a UNION, INTERSECT or EXCEPT. |
query.has_cte, query.is_recursive_cte | boolean | The query has WITH or WITH RECURSIVE. |
The rules of a QueryRejectPolicy use the same variables. That page says more about the query values.
More examples¶
Check the query¶
This allow needs a LIMIT and a query.limit of at most 100.
apiVersion: sluice.bino.bi/v1alpha1
kind: SqlAccessPolicy
metadata:
name: allow-orders-small
priority: 100
spec:
effect: allow
match:
any:
- subjects:
groups: ["interns"]
resources:
catalogs: ["shop"]
schemas: ["main"]
tables: ["orders"]
conditions:
- name: small-result
expression: "query.has_limit == true && query.limit <= 100"
- name: a query with a small limit is allowed
identity: { subject: ines, groups: [interns] }
sql: "SELECT id FROM shop.main.orders LIMIT 50"
expect:
outcome: allow
- name: without a limit the policy does not apply
identity: { subject: ines, groups: [interns] }
sql: "SELECT id FROM shop.main.orders"
expect:
outcome: deny
- name: a limit that is not a constant counts as 0
identity: { subject: ines, groups: [interns] }
sql: "SELECT id FROM shop.main.orders LIMIT 500 + 1"
expect:
outcome: allow
- name: a limit inside one SELECT of a union does not count
identity: { subject: ines, groups: [interns] }
sql: "(SELECT id FROM shop.main.orders LIMIT 50) UNION ALL SELECT id FROM shop.main.orders"
expect:
outcome: deny
A LIMIT that is not a plain number still passes, so this is not a row limit. For that, use a QueryRewritePolicy.
A condition that fails¶
A lookup of a missing claim is an error, not false. Sluice then denies the whole query with ACL_DENIED, also when another policy allows the table.
apiVersion: sluice.bino.bi/v1alpha1
kind: ColumnMaskPolicy
metadata:
name: mask-card-number
priority: 50
spec:
match:
any:
- resources:
catalogs: ["shop"]
schemas: ["main"]
tables: ["payments"]
columns: ["card_number"]
conditions:
# No guard: this lookup fails for a subject without the claim.
- name: no-high-clearance
expression: "subject.claims.clearance != 'high'"
mask:
type: "null"
- name: with the claim the condition decides
identity: { subject: carl, groups: [analysts], claims: { clearance: low } }
sql: "SELECT id, card_number FROM shop.main.payments"
expect:
outcome: allow
masks: ["shop.main.payments.card_number=null"]
- name: without the claim the condition fails and the query is denied
identity: { subject: bob, groups: [analysts] }
sql: "SELECT id, card_number FROM shop.main.payments"
expect:
outcome: deny
errorCode: ACL_DENIED
sluice policy explain shows the reason:
sluice policy explain --policies-dir policies.d \
--user bob --groups analysts \
--sql "SELECT id, card_number FROM shop.main.payments"
subject : bob
resource: shop.main.payments
decision: deny
Kind Name Priority Effect
---- ---- -------- ------
SqlAccessPolicy - rejected: policy condition evaluation failed: condition "no-high-clearance": no such key: clearance
Limits¶
- No variable holds the current time.
- Most
query.*values do not read subqueries. See Rejects and rewrites. - The client sets its headers and its user agent. Trust them only when a proxy that you control sets them.
- Only queries carry
request.*values. The MCP tools that list or describe tables,explain_accessand the admin API see empty values. So a condition onrequest.*can hide a table from the lists that a query may read. - A row filter has its own CEL form. See Row filters.