Skip to content
trino-rustfs-opa

trino-rustfs-opa

What it demonstrates

trino-rustfs plus authentication and authorization. Trino authenticates to puddle as the writer role using a static bearer token; an inlined OPA policy gates each request.

Run it

cd examples/compose/trino-rustfs-opa
docker compose up

docker-compose.yml

Source on GitHub.

docker-compose.yml
# Self-contained docker-compose for running puddle with rustfs S3
# storage, Trino as a query engine, and static-token auth + OPA RBAC.
# Trino authenticates as the `writer` role.
#
# Run:
#   docker compose up
# Trino UI:    http://localhost:8080
# Catalog REST: http://localhost:8089/api/catalog
#
# To demo a different role, swap iceberg.rest-catalog.oauth2.token
# below for admin-secret-replace-me (full access) or
# reader-secret-replace-me (read-only).

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

      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

      # Static bearer token; matches the writer role in puddle's
      # static-tokens. Swap for admin-secret-replace-me or
      # reader-secret-replace-me to demo other roles.
      iceberg.rest-catalog.security=OAUTH2
      iceberg.rest-catalog.oauth2.token=writer-secret-replace-me

      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: