Secure SQL gateway for DuckDB: authentication, Rego policies, row-level security and column masking
1.1K
A secure SQL gateway for DuckDB. curral puts your DuckDB databases and Iceberg lakes behind an HTTP API, with authentication, a policy that decides what each user may read or write, row-level security and column masking.
The example configuration lives in the GitHub repository (two local DuckDB
databases and three users: admin, analyst and etl):
git clone https://github.com/lucasapassos/curral && cd curral
docker run --rm -p 127.0.0.1:8080:8080 \
-v "$PWD/examples:/etc/curral:ro" -e CURRAL_DATA=/var/lib/curral \
lucasapassos/curral serve \
--catalog /etc/curral/catalog.yaml \
--users /etc/curral/users.yaml \
--policy /etc/curral/policy.rego --policy /etc/curral/roles.json
curl -u admin:admin-pw localhost:8080/v1/query \
-d '{"sql": "CREATE TABLE orders AS SELECT range AS id, range * 10 AS amount FROM range(5)"}'
curl -u analyst:analyst-pw 'localhost:8080/v1/query?format=csv' \
-d '{"sql": "SELECT * FROM orders"}'
The example passwords are public: never use examples/users.yaml outside
local tests.
| Tag | |
|---|---|
latest | the most recent release |
vX.Y.Z | a specific release (changelog) |
Images are multi-arch: linux/amd64 and linux/arm64.
gcr.io/distroless/cc (Debian 13), running as the non-root user
nonroot (UID 65532), with no shell.curral; the default command is serve. Other
subcommands: version, check, hash-password, gen-api-key,
healthcheck, query.8080 (API) and 9090 (Prometheus /metrics, only when
CURRAL_METRICS_LISTEN=:9090 is set; it has no authentication, so keep it
internal).httpfs, avro and iceberg are installed at build time
in /opt/curral/extensions, so the container starts without internet
access and with pinned extension builds.curral healthcheck), since there is no curl in
the image.| Path | Purpose |
|---|---|
/var/lib/curral | working directory; mount a volume here for DuckDB files |
/var/lib/curral/tmp | DuckDB spill directory (CURRAL_TEMP_DIR); a tmpfs works well |
/var/log/curral | a place for the audit log (CURRAL_AUDIT_LOG=/var/log/curral/audit.jsonl) |
Every flag of curral serve can also be set as an environment variable
CURRAL_<FLAG>, e.g. CURRAL_MAX_CONCURRENCY=16. Repeatable flags take a
comma-separated list: CURRAL_POLICY=/etc/curral/policy.rego,/etc/curral/roles.json.
services:
curral:
image: lucasapassos/curral:latest
ports: ["127.0.0.1:8080:8080"]
environment:
CURRAL_CATALOG: /etc/curral/catalog.yaml
CURRAL_USERS: /etc/curral/users.yaml
CURRAL_POLICY: /etc/curral/policy.rego,/etc/curral/roles.json
CURRAL_AUDIT_LOG: /var/log/curral/audit.jsonl
volumes:
- ./config:/etc/curral:ro
- audit:/var/log/curral
read_only: true
tmpfs: ["/var/lib/curral/tmp:uid=65532,gid=65532,mode=0700"]
cap_drop: [ALL]
volumes:
audit:
Send SIGHUP (docker compose kill -s HUP curral) to reload users, policy,
row filters and the TLS certificate, and to reopen the audit log.
Need more extensions? Build the image yourself:
docker build --build-arg EXTENSIONS="httpfs avro iceberg postgres" -t curral .
/v1/query, /v1/schema, formats, dry-runReport vulnerabilities privately: see the security policy.
Content type
Image
Digest
sha256:503e1ae5c…
Size
68.3 MB
Last updated
about 4 hours ago
docker pull lucasapassos/curral