opa-local
What it demonstrates
Static-token auth + OPA RBAC on top of basic.
Three principals — admin, alice (writer), bob (reader) — gated
by an inlined OPA policy.
Run it
cd examples/compose/opa-local
docker compose upThen call the catalog with one of the three tokens:
curl -H "Authorization: Bearer admin-secret-replace-me" \
http://localhost:8089/api/catalog/v1/configdocker-compose.yml
docker-compose.yml
# Self-contained docker-compose for running puddle with static-token
# auth + OPA RBAC. Three principals:
# admin (admin-secret-replace-me) — full access
# alice (writer-secret-replace-me) — read + mutate
# bob (reader-secret-replace-me) — read-only
#
# Run:
# docker compose up
# curl -H "Authorization: Bearer admin-secret-replace-me" \
# http://localhost:8089/api/catalog/v1/config
#
# Tokens are committed deliberately so this works out of the box.
# Replace before exposing to anything that isn't your laptop.
configs:
puddle-config:
content: |
logging:
level: info
format: text
metastore:
type: sqlite
sqlite:
path: /var/lib/puddle/puddle.db
warehouses:
default:
location: file:///var/lib/puddle/warehouse
authn:
static-tokens:
- token: admin-secret-replace-me
principal: admin
attrs:
groups: [admin]
- token: writer-secret-replace-me
principal: alice
attrs:
groups: [writer]
- token: reader-secret-replace-me
principal: bob
attrs:
groups: [reader]
authz:
mode: opa
opa:
url: http://opa:8181/v1/data/puddle/rbac/allow
opa-policy:
content: |
# Starter RBAC policy: three roles keyed off groups[].
# admin — every action on every resource
# writer — read + mutate (table.create, table.commit, etc.)
# reader — read-only (config.read, *.list, *.load, *.exists)
package puddle.rbac
import rego.v1
default allow := false
allow if {
count(input.resources) > 0
every r in input.resources {
permitted(input.action, r)
}
}
permitted(_, _) if {
"admin" in user_groups
}
permitted(action, _) if {
action in read_actions
some role in {"reader", "writer"}
role in user_groups
}
permitted(action, _) if {
action in write_actions
"writer" in user_groups
}
read_actions := {
"config.read",
"namespace.list", "namespace.load", "namespace.exists",
"table.list", "table.load", "table.exists",
"view.list", "view.load", "view.exists",
"metrics.report",
}
write_actions := {
"namespace.create", "namespace.drop", "namespace.update-properties",
"table.create", "table.commit", "table.drop", "table.rename", "table.register",
"view.create", "view.replace", "view.drop", "view.rename",
"transaction.commit",
}
user_groups contains g if {
some g in input.principal.attrs.groups
}
services:
puddle:
image: ghcr.io/ragnard/puddle:latest
ports: ["8089:8089"]
volumes:
- puddle-data:/var/lib/puddle
configs:
- source: puddle-config
target: /etc/puddle/puddle.yaml
command: ["/puddle", "-c", "/etc/puddle/puddle.yaml"]
depends_on:
opa:
condition: service_started
restart: unless-stopped
opa:
image: openpolicyagent/opa:latest
command: [run, --server, --addr=:8181, --log-level=info, /policy/rbac.rego]
configs:
- source: opa-policy
target: /policy/rbac.rego
restart: unless-stopped
volumes:
puddle-data: