Skip to content

Approval workflow

An ApprovalPolicy holds a query on sensitive columns until a person accepts it.

Example

Start the server with examples/approval-workflow/serve.sh. It also starts webhook.py, which stands in for the approver's inbox.

The policy holds a query that names ssn or a salary* column, or that filters on country = 'de'.

apiVersion: sluice.bino.bi/v1alpha1
kind: ApprovalPolicy
metadata: { name: approve-pii-reads, priority: 100 }
spec:
  match:
    any:
      - resources: { tables: [people] }
  when:
    columnsAccessed: ["ssn", "salary*"]
    predicates:
      - { column: "country", op: "=", value: "de" }
  reason: "PII columns on people need the approval of a data steward"

The server config needs the approval block. Sluice does not start without publicBaseUrl.

approval:
  # The accept and reject links in the webhook start with this URL.
  publicBaseUrl: "http://localhost:8080"
  # A held query waits this long for a decision. Then it answers ERR_APPROVAL_PENDING.
  syncWait: 1s
  # An approval without a decision expires after this time.
  requestTtl: 15m
  # After the accept, the query must run within this time.
  grantTtl: 5m
  webhooks:
    - url: "http://localhost:8090/"

Alice asks for ssn. Sluice holds the query.

curl -s http://localhost:8080/v1/query \
  -H "X-Api-Key: sl_demo_alice.world" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT name, ssn FROM shop.main.people ORDER BY id"}'
{
  "code": "ERR_APPROVAL_PENDING",
  "message": "human approval required; a decision has been requested. Poll or await the approval, then re-run the IDENTICAL query (execute_sql or sample_table call) within the grant window to execute.",
  "query_id": "<query-id>",
  "details": {
    "approval_id": "<approval-id>",
    "expires_at": "<time>"
  }
}

webhook.py stored the webhook that Sluice sent.

tail -n 1 data/webhooks.jsonl
{
  "approval_id": "<approval-id>",
  "subject": {
    "id": "alice",
    "issuer": "analysts",
    "groups": ["analysts"]
  },
  "sql": "SELECT name, ssn FROM shop.main.people ORDER BY id",
  "reasons": ["policy approve-pii-reads: PII columns on people need the approval of a data steward"],
  "policies": ["approve-pii-reads"],
  "accept_url": "<accept-url>",
  "reject_url": "<reject-url>",
  "requested_at": "<time>",
  "expires_at": "<time>"
}

The approver opens the accept link and confirms on the page. The step sends the POST of that page.

curl -s -X POST "$(cat data/accept_url)"
{
  "approval_id": "<approval-id>",
  "note": "recorded",
  "state": "approved"
}

Alice sends the same query again. Now it runs.

curl -s http://localhost:8080/v1/query \
  -H "X-Api-Key: sl_demo_alice.world" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT name, ssn FROM shop.main.people ORDER BY id"}'
{
  "query_id": "<query-id>",
  "columns": ["name", "ssn"],
  "rows": [
    ["Anna", "111-22-3333"],
    ["Bernd", "222-33-4444"],
    ["Chloe", "333-44-5555"]
  ],
  "row_count": 3,
  "truncated": false
}

She sends it a third time. Sluice holds it again.

curl -s http://localhost:8080/v1/query \
  -H "X-Api-Key: sl_demo_alice.world" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT name, ssn FROM shop.main.people ORDER BY id"}'
{
  "code": "ERR_APPROVAL_PENDING",
  "message": "human approval required; a decision has been requested. Poll or await the approval, then re-run the IDENTICAL query (execute_sql or sample_table call) within the grant window to execute.",
  "query_id": "<query-id>",
  "details": {
    "approval_id": "<approval-id>",
    "expires_at": "<time>"
  }
}

What happened: The accept created a grant for one run of the identical query. The second run used it up.

More examples

The approver opens the reject link of the new approval and confirms.

curl -s -X POST "$(cat data/reject_url)"
{
  "approval_id": "<approval-id>",
  "note": "recorded",
  "state": "rejected"
}

Alice reads the state of her approval with her own key.

curl -s "http://localhost:8080/v1/approvals/$(cat data/approval_id)" \
  -H "X-Api-Key: sl_demo_alice.world"
{
  "approval_id": "<approval-id>",
  "expires_at": "<time>",
  "state": "rejected"
}

Limits

  • SELECT * on the table needs approval too. Sluice cannot tell which columns a star reads.
  • The token in the link is the only check. Anyone who has the link can accept.

See also