trino-rustfs-opa-vending
What it demonstrates
trino-rustfs-opa with STS-vended per-table credentials. Trino picks up short-lived, table-scoped tokens from puddle’s table-load response instead of using warehouse-wide S3 keys for table I/O. The closest the local stack gets to a production AWS shape.
See Credential vending for the operator reference.
Run it
cd examples/compose/trino-rustfs-opa-vending
docker compose updocker-compose.yml
docker-compose.yml
# Self-contained docker-compose for running puddle with rustfs S3
# storage, Trino as a query engine, static-token auth + OPA RBAC,
# AND STS-vended per-table credentials. The closest the local stack
# gets to production AWS shape, with rustfs standing in for S3.
#
# Run:
# docker compose up
# Trino UI: http://localhost:8080
# Catalog REST: http://localhost:8089/api/catalog
#
# Trino authenticates as the `writer` role and picks up short-lived,
# table-scoped S3 credentials from puddle's table-load response. The
# warehouse-wide S3 keys never leave the catalog.
configs:
puddle-config:
content: |
logging:
level: info
format: text
metastore:
type: sqlite
sqlite:
path: /var/lib/puddle/puddle.db
warehouses:
default:
location: s3://puddle/warehouse
s3:
region: us-east-1
endpoint: http://rustfs:9000
path-style-access: true
access-key-id: puddle-example-key
secret-access-key: puddle-example-secret
vended-credentials:
enabled: true
# rustfs (like MinIO) ignores the role-arn value and
# gates on the inline session policy. Any well-formed
# ARN works.
role-arn: arn:aws:iam::000000000000:role/dummy
duration-seconds: 900
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
trino-iceberg:
content: |
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=http://puddle:8089/api/catalog
iceberg.rest-catalog.warehouse=default
iceberg.rest-catalog.security=OAUTH2
iceberg.rest-catalog.oauth2.token=writer-secret-replace-me
# With credential vending enabled on the catalog, Trino picks
# up short-lived, per-table S3 credentials from the table-load
# response. The static s3.aws-*-key values below are fallbacks
# for pre-flight checks (e.g. verifying a new table's location
# is empty) — vended tokens take over for actual table I/O.
iceberg.rest-catalog.vended-credentials-enabled=true
fs.native-s3.enabled=true
s3.endpoint=http://rustfs:9000
s3.region=us-east-1
s3.path-style-access=true
s3.aws-access-key=puddle-example-key
s3.aws-secret-key=puddle-example-secret
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
rustfs-init:
condition: service_completed_successfully
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
rustfs:
image: rustfs/rustfs:latest
environment:
RUSTFS_ADDRESS: 0.0.0.0:9000
RUSTFS_CONSOLE_ADDRESS: 0.0.0.0:9001
RUSTFS_CONSOLE_ENABLE: "true"
RUSTFS_VOLUMES: /data/rustfs0
RUSTFS_ACCESS_KEY: puddle-example-key
RUSTFS_SECRET_KEY: puddle-example-secret
ports: ["9000:9000", "9001:9001"]
volumes:
- rustfs-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/health"]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
restart: unless-stopped
rustfs-init:
image: amazon/aws-cli:latest
environment:
AWS_ACCESS_KEY_ID: puddle-example-key
AWS_SECRET_ACCESS_KEY: puddle-example-secret
AWS_DEFAULT_REGION: us-east-1
entrypoint: ["/bin/sh", "-c"]
command:
- |
aws --endpoint-url http://rustfs:9000 s3api create-bucket \
--bucket puddle 2>&1 | grep -v BucketAlreadyOwnedByYou || true
depends_on:
rustfs:
condition: service_healthy
restart: "no"
trino:
image: trinodb/trino:latest
ports: ["8080:8080"]
configs:
- source: trino-iceberg
target: /etc/trino/catalog/iceberg.properties
depends_on:
puddle:
condition: service_started
restart: unless-stopped
volumes:
puddle-data:
rustfs-data: