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
- Open Policy Agent docs: Introduction
- Styra docs: Rego Style Guide
- GitHub: Awesome OPA - A curated list of awesome OPA-related tools, frameworks and articles
- Open Policy Agent: Playground
- Styra Academy courses: OPA Policy Authoring
- Open Policy Agent: Ecosystem