Skip to content

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 up

Then call the catalog with one of the three tokens:

curl -H "Authorization: Bearer admin-secret-replace-me" \
  http://localhost:8089/api/catalog/v1/config

docker-compose.yml

Source on GitHub.

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: