Getting started with Open Policy Agent

Introduction

Open Policy Agent is a general-purpose policy engine that includes a high-level declarative language that you can use to define policies.

Getting Started

The easiest way to get started with Open Policy Agent (OPA) is to use Docker Compose.

Docker Compose

I created a Docker Compose configuration file, as follows:

  opa:
    image: openpolicyagent/opa:1.6.0-static
    command:
      - "run"
      - "--server"
      - "--addr=0.0.0.0:8181"
      - "--log-level=info"
      - "--log-format=json-pretty"
    restart: unless-stopped
    ports:
      - 8181:8181

Policy Definition

OPA uses a declarative language called Rego to define policies.

For example:

package organization.read

methods := "GET HEAD"

default allow := false

allow if contains(methods, input.request.method)

You can use Rego to specify rules for authorisation and other security aspects of your application.

Loading Policies and Data

The simplest way to load policies and data into OPA is to provide them via the file system (i.e., Docker volumes) and command line arguments.

Let's update our Docker Compose configuration file, as follows:

  opa:
    image: openpolicyagent/opa:1.6.0-static
    command:
      - "run"
      - "--server"
      - "/policies/organization-read.rego"
      - "/policies/organization-write.rego"
      - "--addr=0.0.0.0:8181"
      - "--log-level=info"
      - "--log-format=json-pretty"
    restart: unless-stopped
    ports:
      - 8181:8181
    volumes:
      - '${PWD}/services/opa/conf/policies:/policies'

Policy Evaluation

OPA can act as a policy decision point in combination with an API Gateway (e.g., APISIX) acting as a policy enforcement point.

When an application requests access to a resource (using standard HTTP methods like GET, HEAD, POST, PUT, PATCH and DELETE), the request is intercepted by the API Gateway and routed to OPA.

For example:

  - name: provider-directory-api-organization-read
    uri: /fhir/Organization*
    methods: [ "GET", "HEAD" ]
    upstream_id: 1
    plugins:
      opa:
        host: "https://opa:8282"
        policy: "organization/read"

  - name: provider-directory-api-organization-write
    uri: /fhir/Organization*
    methods: [ "POST", "PUT", "PATCH", "DELETE" ]
    upstream_id: 1
    plugins:
      opa:
        host: "https://opa:8282"
        policy: "organization/write"

OPA evaluates the (access control) policy and decides if the request should be 'allowed' or 'denied'.

You can obtain additional information about OPA decisions by setting your log level to 'debug' and including the decision logs:

  opa:
    image: openpolicyagent/opa:1.6.0-static
    command:
    
      ...
      
      - "--log-level=debug"
      - "--set=decision_logs.console=true"

For example:

docker container logs opa

You should see something like:

{
  "current_version": "1.6.0",
  "level": "debug",
  "msg": "OPA is up to date.",
  "time": "2025-07-18T23:00:45Z"
}
{
  "client_addr": "172.18.0.2:34608",
  "level": "info",
  "msg": "Received request.",
  "req_body": "{\"input\":{\"request\":{\"port\":9443,\"query\":{\"_id\":\"adv-hearing-care\"},\"scheme\":\"https\",\"headers\":{\"authorization\":\"Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMOFRkd19mLTRlUDJOVmZzMUVDbDJka0ZwNmFGbi1QR2xZZWlxb0FvSkowIn0.eyJleHAiOjE3NTI4ODAwNzMsImlhdCI6MTc1Mjg3OTc3MywianRpIjoidHJydGNjOmQ5OTk3MzEzLWQwNzMtZTkxYi1jMDc0LTgxNjQ1NGFlNWQ2YyIsImlzcyI6Imh0dHBzOi8va2V5Y2xvYWsuYXUubG9jYWxob3N0Ojg0NDMvcmVhbG1zL2hhcGktZmhpci1kZXYiLCJhdWQiOlsib2F1dGgyLXByb3h5IiwiYWNjb3VudCJdLCJzdWIiOiJkYTRmMDkxMi0xY2Q5LTQzMmQtOWM5ZC1hNTVkYjExMDkzZGUiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvYXV0aDItcHJveHkiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtaGFwaS1maGlyLWRldiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJvYXV0aDItcHJveHkiOnsicm9sZXMiOlsidW1hX3Byb3RlY3Rpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBzeXN0ZW0vT3JnYW5pemF0aW9uLnJlYWQgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudEhvc3QiOiIxNzIuMTguMC4xIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LW9hdXRoMi1wcm94eSIsImNsaWVudEFkZHJlc3MiOiIxNzIuMTguMC4xIiwiY2xpZW50X2lkIjoib2F1dGgyLXByb3h5In0.XAZXKtzk6lGnYYS0LcWc66MDalhWJ_p7E3-KYNse_LM9MKBgTrNqtizBdNgCeS8jgJ-366szDNv9ZvOMVgt3y-Hd2PhMMD-z93GuwcrOefcAH2Ji8ZgtgSt5VG4R7J5F_3ty1VP-Jhhq6EAMtsm1H8t-5AQjoDTaZjqZLpnVX8P-8nhkEfNZWZEILGyXWN0lcko-1lhLdIbbRYMl5cOxKM7s7FuK23G5y1JJQZRC5HWVF6Xa9zqQo-yioTpVlr2OIVE-eNA7FE17d_lDzAHQm-InNHkGEpiMcA3Wfgc-CS9slBfmIQxuU2N6Qnc1z7ZPHznAhzxcgPetCIGFjwSaTw\",\"user-agent\":\"curl/8.7.1\",\"host\":\"provider-directory.au.localhost\",\"accept\":\"*/*\",\"content-type\":\"application/fhir+json\"},\"method\":\"GET\",\"path\":\"/fhir/Organization\",\"host\":\"provider-directory.au.localhost\"},\"type\":\"http\",\"var\":{\"server_port\":\"9443\",\"remote_addr\":\"172.18.0.1\",\"timestamp\":1752879785,\"remote_port\":\"56920\",\"server_addr\":\"172.18.0.2\"}}}",
  "req_id": 2,
  "req_method": "POST",
  "req_params": {},
  "req_path": "/v1/data/organization/read",
  "time": "2025-07-18T23:03:05Z"
}
{
  "decision_id": "6ce6af4a-9ad2-4a23-ad8d-83428683ff37",
  "input": {
    "request": {
      "headers": {
        "accept": "*/*",
        "authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMOFRkd19mLTRlUDJOVmZzMUVDbDJka0ZwNmFGbi1QR2xZZWlxb0FvSkowIn0.eyJleHAiOjE3NTI4ODAwNzMsImlhdCI6MTc1Mjg3OTc3MywianRpIjoidHJydGNjOmQ5OTk3MzEzLWQwNzMtZTkxYi1jMDc0LTgxNjQ1NGFlNWQ2YyIsImlzcyI6Imh0dHBzOi8va2V5Y2xvYWsuYXUubG9jYWxob3N0Ojg0NDMvcmVhbG1zL2hhcGktZmhpci1kZXYiLCJhdWQiOlsib2F1dGgyLXByb3h5IiwiYWNjb3VudCJdLCJzdWIiOiJkYTRmMDkxMi0xY2Q5LTQzMmQtOWM5ZC1hNTVkYjExMDkzZGUiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvYXV0aDItcHJveHkiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtaGFwaS1maGlyLWRldiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJvYXV0aDItcHJveHkiOnsicm9sZXMiOlsidW1hX3Byb3RlY3Rpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBzeXN0ZW0vT3JnYW5pemF0aW9uLnJlYWQgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudEhvc3QiOiIxNzIuMTguMC4xIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LW9hdXRoMi1wcm94eSIsImNsaWVudEFkZHJlc3MiOiIxNzIuMTguMC4xIiwiY2xpZW50X2lkIjoib2F1dGgyLXByb3h5In0.XAZXKtzk6lGnYYS0LcWc66MDalhWJ_p7E3-KYNse_LM9MKBgTrNqtizBdNgCeS8jgJ-366szDNv9ZvOMVgt3y-Hd2PhMMD-z93GuwcrOefcAH2Ji8ZgtgSt5VG4R7J5F_3ty1VP-Jhhq6EAMtsm1H8t-5AQjoDTaZjqZLpnVX8P-8nhkEfNZWZEILGyXWN0lcko-1lhLdIbbRYMl5cOxKM7s7FuK23G5y1JJQZRC5HWVF6Xa9zqQo-yioTpVlr2OIVE-eNA7FE17d_lDzAHQm-InNHkGEpiMcA3Wfgc-CS9slBfmIQxuU2N6Qnc1z7ZPHznAhzxcgPetCIGFjwSaTw",
        "content-type": "application/fhir+json",
        "host": "provider-directory.au.localhost",
        "user-agent": "curl/8.7.1"
      },
      "host": "provider-directory.au.localhost",
      "method": "GET",
      "path": "/fhir/Organization",
      "port": 9443,
      "query": {
        "_id": "adv-hearing-care"
      },
      "scheme": "https"
    },
    "type": "http",
    "var": {
      "remote_addr": "172.18.0.1",
      "remote_port": "56920",
      "server_addr": "172.18.0.2",
      "server_port": "9443",
      "timestamp": 1752879785
    }
  },
  "labels": {
    "id": "5db6ed80-30b9-42f1-a151-b8b63ca504e5",
    "version": "1.6.0"
  },
  "level": "info",
  "metrics": {
    "counter_server_query_cache_hit": 0,
    "timer_rego_external_resolve_ns": 459,
    "timer_rego_input_parse_ns": 1074000,
    "timer_rego_query_compile_ns": 337042,
    "timer_rego_query_eval_ns": 1389209,
    "timer_server_handler_ns": 3634125
  },
  "msg": "Decision Log",
  "path": "organization/read",
  "req_id": 2,
  "requested_by": "172.18.0.2:34608",
  "result": {
    "allow": true,
    "bearer_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMOFRkd19mLTRlUDJOVmZzMUVDbDJka0ZwNmFGbi1QR2xZZWlxb0FvSkowIn0.eyJleHAiOjE3NTI4ODAwNzMsImlhdCI6MTc1Mjg3OTc3MywianRpIjoidHJydGNjOmQ5OTk3MzEzLWQwNzMtZTkxYi1jMDc0LTgxNjQ1NGFlNWQ2YyIsImlzcyI6Imh0dHBzOi8va2V5Y2xvYWsuYXUubG9jYWxob3N0Ojg0NDMvcmVhbG1zL2hhcGktZmhpci1kZXYiLCJhdWQiOlsib2F1dGgyLXByb3h5IiwiYWNjb3VudCJdLCJzdWIiOiJkYTRmMDkxMi0xY2Q5LTQzMmQtOWM5ZC1hNTVkYjExMDkzZGUiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvYXV0aDItcHJveHkiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtaGFwaS1maGlyLWRldiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJvYXV0aDItcHJveHkiOnsicm9sZXMiOlsidW1hX3Byb3RlY3Rpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBzeXN0ZW0vT3JnYW5pemF0aW9uLnJlYWQgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudEhvc3QiOiIxNzIuMTguMC4xIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LW9hdXRoMi1wcm94eSIsImNsaWVudEFkZHJlc3MiOiIxNzIuMTguMC4xIiwiY2xpZW50X2lkIjoib2F1dGgyLXByb3h5In0.XAZXKtzk6lGnYYS0LcWc66MDalhWJ_p7E3-KYNse_LM9MKBgTrNqtizBdNgCeS8jgJ-366szDNv9ZvOMVgt3y-Hd2PhMMD-z93GuwcrOefcAH2Ji8ZgtgSt5VG4R7J5F_3ty1VP-Jhhq6EAMtsm1H8t-5AQjoDTaZjqZLpnVX8P-8nhkEfNZWZEILGyXWN0lcko-1lhLdIbbRYMl5cOxKM7s7FuK23G5y1JJQZRC5HWVF6Xa9zqQo-yioTpVlr2OIVE-eNA7FE17d_lDzAHQm-InNHkGEpiMcA3Wfgc-CS9slBfmIQxuU2N6Qnc1z7ZPHznAhzxcgPetCIGFjwSaTw",
    "is_client": true,
    "is_method": true,
    "is_request_path": true,
    "is_scope": true,
    "methods": "GET HEAD",
    "path": "/fhir/Organization",
    "scope": "system/Organization.read",
    "token": {
      "acr": "1",
      "allowed-origins": [
        "*"
      ],
      "aud": [
        "oauth2-proxy",
        "account"
      ],
      "azp": "oauth2-proxy",
      "clientAddress": "172.18.0.1",
      "clientHost": "172.18.0.1",
      "client_id": "oauth2-proxy",
      "email_verified": false,
      "exp": 1752880073,
      "iat": 1752879773,
      "iss": "https://keycloak.au.localhost:8443/realms/hapi-fhir-dev",
      "jti": "trrtcc:d9997313-d073-e91b-c074-816454ae5d6c",
      "preferred_username": "service-account-oauth2-proxy",
      "realm_access": {
        "roles": [
          "default-roles-hapi-fhir-dev",
          "offline_access",
          "uma_authorization"
        ]
      },
      "resource_access": {
        "account": {
          "roles": [
            "manage-account",
            "manage-account-links",
            "view-profile"
          ]
        },
        "oauth2-proxy": {
          "roles": [
            "uma_protection"
          ]
        }
      },
      "scope": "profile system/Organization.read email",
      "sub": "da4f0912-1cd9-432d-9c9d-a55db11093de",
      "typ": "Bearer"
    }
  },
  "time": "2025-07-18T23:03:05Z",
  "timestamp": "2025-07-18T23:03:05.314506801Z",
  "type": "openpolicyagent.org/decision_logs"
}
{
  "client_addr": "172.18.0.2:34608",
  "level": "info",
  "msg": "Sent response.",
  "req_id": 2,
  "req_method": "POST",
  "req_path": "/v1/data/organization/read",
  "resp_body": "{\"decision_id\":\"6ce6af4a-9ad2-4a23-ad8d-83428683ff37\",\"result\":{\"allow\":true,\"bearer_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMOFRkd19mLTRlUDJOVmZzMUVDbDJka0ZwNmFGbi1QR2xZZWlxb0FvSkowIn0.eyJleHAiOjE3NTI4ODAwNzMsImlhdCI6MTc1Mjg3OTc3MywianRpIjoidHJydGNjOmQ5OTk3MzEzLWQwNzMtZTkxYi1jMDc0LTgxNjQ1NGFlNWQ2YyIsImlzcyI6Imh0dHBzOi8va2V5Y2xvYWsuYXUubG9jYWxob3N0Ojg0NDMvcmVhbG1zL2hhcGktZmhpci1kZXYiLCJhdWQiOlsib2F1dGgyLXByb3h5IiwiYWNjb3VudCJdLCJzdWIiOiJkYTRmMDkxMi0xY2Q5LTQzMmQtOWM5ZC1hNTVkYjExMDkzZGUiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvYXV0aDItcHJveHkiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtaGFwaS1maGlyLWRldiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJvYXV0aDItcHJveHkiOnsicm9sZXMiOlsidW1hX3Byb3RlY3Rpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBzeXN0ZW0vT3JnYW5pemF0aW9uLnJlYWQgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudEhvc3QiOiIxNzIuMTguMC4xIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LW9hdXRoMi1wcm94eSIsImNsaWVudEFkZHJlc3MiOiIxNzIuMTguMC4xIiwiY2xpZW50X2lkIjoib2F1dGgyLXByb3h5In0.XAZXKtzk6lGnYYS0LcWc66MDalhWJ_p7E3-KYNse_LM9MKBgTrNqtizBdNgCeS8jgJ-366szDNv9ZvOMVgt3y-Hd2PhMMD-z93GuwcrOefcAH2Ji8ZgtgSt5VG4R7J5F_3ty1VP-Jhhq6EAMtsm1H8t-5AQjoDTaZjqZLpnVX8P-8nhkEfNZWZEILGyXWN0lcko-1lhLdIbbRYMl5cOxKM7s7FuK23G5y1JJQZRC5HWVF6Xa9zqQo-yioTpVlr2OIVE-eNA7FE17d_lDzAHQm-InNHkGEpiMcA3Wfgc-CS9slBfmIQxuU2N6Qnc1z7ZPHznAhzxcgPetCIGFjwSaTw\",\"is_client\":true,\"is_method\":true,\"is_request_path\":true,\"is_scope\":true,\"methods\":\"GET HEAD\",\"path\":\"/fhir/Organization\",\"scope\":\"system/Organization.read\",\"token\":{\"acr\":\"1\",\"allowed-origins\":[\"*\"],\"aud\":[\"oauth2-proxy\",\"account\"],\"azp\":\"oauth2-proxy\",\"clientAddress\":\"172.18.0.1\",\"clientHost\":\"172.18.0.1\",\"client_id\":\"oauth2-proxy\",\"email_verified\":false,\"exp\":1752880073,\"iat\":1752879773,\"iss\":\"https://keycloak.au.localhost:8443/realms/hapi-fhir-dev\",\"jti\":\"trrtcc:d9997313-d073-e91b-c074-816454ae5d6c\",\"preferred_username\":\"service-account-oauth2-proxy\",\"realm_access\":{\"roles\":[\"default-roles-hapi-fhir-dev\",\"offline_access\",\"uma_authorization\"]},\"resource_access\":{\"account\":{\"roles\":[\"manage-account\",\"manage-account-links\",\"view-profile\"]},\"oauth2-proxy\":{\"roles\":[\"uma_protection\"]}},\"scope\":\"profile system/Organization.read email\",\"sub\":\"da4f0912-1cd9-432d-9c9d-a55db11093de\",\"typ\":\"Bearer\"}}}\n",
  "resp_bytes": 2445,
  "resp_duration": 8.023,
  "resp_status": 200,
  "time": "2025-07-18T23:03:05Z"
}
Source Code
References
Open Policy Agent