Skip to content

Hardening

Check these settings before production use.

Example

Goal: a daily budget for one API key.

apiVersion: sluice.bino.bi/v1alpha1
kind: SubjectBinding
metadata:
  name: report-bot
spec:
  # rateLimit and budget apply only with claims.subjectId or issuer.
  claims:
    subjectId: "report-bot"
  apiKeys:
    - id: "sl_demo_report_bot"
      hashRef: "secret://env/SLUICE_APIKEY_REPORT_BOT_HASH"
      groups: ["agents"]
  rateLimit:
    rps: 5
    burst: 10
  budget:
    cpuSecondsPerDay: 600
    rowsPerDay: 2

In the server config:

budget:
  enabled: true
  stateDir: "./data/state"

The client sends this query twice. The second answer:

curl -s http://localhost:8080/v1/query \
  -H "X-Api-Key: sl_demo_report_bot.supersecret" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT id, name FROM shop.main.customers ORDER BY id"}'
{
  "code": "ERR_BUDGET_EXCEEDED",
  "message": "daily row budget exhausted",
  "query_id": "<query-id>"
}

What happened: the first query returned 2 rows and used up rowsPerDay. Sluice refused the second query.

Checklist

Setting Why Value
admin.token Without it the admin API accepts every request Set it in the server config or with SLUICE_ADMIN__TOKEN
admin.listen Serves the admin API and /metrics A private address
rest.tls, mcp.tls, admin.tls REST, MCP over HTTP and admin serve plain HTTP by default See Deployment
rest.tls.clientCA Adds a client certificate check. Login stays required Your CA bundle
identity.apiKeyPepper API keys do not work without the pepper A secret:// reference
Secret files Sluice refuses files that group or others can write chmod 0600
audit.chainKeyRef Without a key, a writer of the audit files can compute the hash chain again A secret:// reference to 32 bytes or more, stored away from the log
audit.file.path, audit.file.genesis Defaults: a temp directory, a genesis built from host name and build commit A fixed directory, a secret:// reference
JWT subject binding: issuer, audience, jwksUrl The audience is checked only when set Set all three
Subject binding: rateLimit, budget Both need claims.subjectId or issuer. Budgets also need budget.enabled: true See the example
resources in every allow policy Without resources, an allow policy covers every table, also the catalog views of DuckDB such as information_schema.tables. They list the tables that a deny policy covers List catalog, schema and table, the catalog without a wildcard
QueryRewritePolicy on tables: ["**"] Caps result rows and run time of every query. One aggregate row can hold a whole table See Rejects and rewrites
credentialsRef of a data source Sluice only reads A read-only database user
s3 bucket, http urls DuckDB can read the whole bucket and every listed URL Only what clients need
headersRef Every URL of the data source gets the same headers One data source per credential
Default deny A policy change must not open a table A policy test that expects deny

See also