Break-glass access¶
Break-glass access lets chosen operators read a masked column unmasked. Analysts read email masked. A member of the operator group sre reads the real value, and the audit log shows who did.
Example¶
Start the server with examples/break-glass/serve.sh. The mask covers the analytics group. Its exclude takes the sre group out.
apiVersion: sluice.bino.bi/v1alpha1
kind: ColumnMaskPolicy
metadata:
name: mask-email
priority: 50
spec:
match:
any:
- subjects:
groups: ["analytics"]
resources:
catalogs: ["shop"]
schemas: ["main"]
tables: ["customers"]
columns: ["email"]
# Subjects in the sre group are not masked.
exclude:
any:
- subjects:
groups: ["sre"]
mask:
type: "null"
The SRE key is in both groups.
apiVersion: sluice.bino.bi/v1alpha1
kind: SubjectBinding
metadata:
name: break-glass-sre
spec:
claims:
subjectId: "on-call-sre"
apiKeys:
- id: "sl_demo_sre"
hashRef: "secret://env/SLUICE_APIKEY_SRE_HASH"
# The sre group lifts the email mask.
groups: ["analytics", "sre"]
The analyst key reads email.
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: sl_demo_analyst.supersecret" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT id, email FROM shop.main.customers ORDER BY id"}'
{
"query_id": "<query-id>",
"columns": ["id", "email"],
"rows": [
[1, null],
[2, null],
[3, null]
],
"row_count": 3,
"truncated": false
}
The SRE key sends the same SQL.
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: sl_demo_sre.rotatedtoken" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT id, email FROM shop.main.customers ORDER BY id"}'
{
"query_id": "<query-id>",
"columns": ["id", "email"],
"rows": [
[1, "alice@acme.example"],
[2, "bob@acme.example"],
[3, "carol@acme.example"]
],
"row_count": 3,
"truncated": false
}
Find the SRE query in the audit log.
{
"catalogs": ["shop"],
"decision": "allow",
"event_type": "query",
"origin": "rest",
"policies_applied": [
{
"kind": "SqlAccessPolicy",
"name": "allow-analytics",
"priority": 100
},
{
"kind": "ColumnMaskPolicy",
"name": "mask-email",
"priority": 50
}
],
"prior_hash": "<hash>",
"query_id": "<query-id>",
"remote_ip": "<remote-ip>",
"rewritten_fingerprint": "f90a934da548078a",
"sluice_version": "<version>",
"sql_fingerprint": "f90a934da548078a",
"sql_sample": "SELECT id, email FROM shop.main.customers ORDER BY id",
"subject": {
"groups": ["analytics", "sre"],
"id": "on-call-sre",
"issuer": "break-glass-sre",
"method": "api_key",
"remote_ip": "<remote-ip>"
},
"tables": ["shop.main.customers"],
"timestamp": "<time>",
"hash": "<hash>"
}
What happened: The allow matches both keys through the analytics group. The exclude drops the mask for every subject in sre. The audit record names the subject and its groups.
More examples¶
The sre group alone gives no access. This suite case proves it.
- name: the sre group alone gives no access
identity: { subject: on-call-sre, groups: [sre] }
sql: "SELECT id, email FROM shop.main.customers"
expect:
outcome: deny
Limits¶
- The audit record does not say that the mask was lifted.
policies_appliedstill listsmask-email, and Sluice writes no separate record. - The only sign is that
rewritten_fingerprintequalssql_fingerprint: Sluice ran the SQL unchanged. - To find such queries, search the
queryrecords for the group. - The
excludeis always on. For access that a person accepts each time, use an approval.
See also¶
- Matching: how
excludeworks - Column masks
- Audit log