OPA exposes a set of APIs that enable unified, logically centralized policy management. Read this page if you are interested in how to build a control plane around OPA that enables policy distribution and collection of important telemetry data like decision logs.

OPA enables low-latency, highly-available policy enforcement by providing a lightweight engine for distributed architectures. By default, all of the policy and data that OPA uses to make decisions is kept in-memory:

Host-local Architecture

OPA is designed to enable distributed policy enforcement. You can run OPA next to each and every service that needs to offload policy decision-making. By colocating OPA with the services that require decision-making, you ensure that policy decisions are rendered as fast as possible and in a highly-available manner.

Management APIs - 图2

Distributed Enforcement

To control and observe a set of OPAs, each OPA can be configured to connect to management APIs that enable:

  • Policy distribution (Bundles)
  • Decision telemetry (Decision Logs)
  • Agent telemetry (Status)
  • Dynamic agent configuration (Discovery)

By configuring and implementing these management APIs you can unify control and visiblity over OPAs in your environments. OPA does not provide a control plane service out-of-the-box today.

Control Plane

OPA can periodically download bundles of policy and data from remote HTTP servers. The policies and data are loaded on the fly without requiring a restart of OPA. Once the policies and data have been loaded, they are enforced immediately. Policies and data loaded from bundles are accessible via the standard OPA .

Bundles provide an alternative to pushing policies into OPA via the REST APIs. By configuring OPA to download bundles from a remote HTTP server, you can ensure that OPA has an up-to-date copy of policies and data required for enforcement at all times.

By default, the OPA REST APIs will prevent you from modifying policy and data loaded via bundles. If you need to load policy and data from multiple sources, see the section below.

See the Configuration Reference for configuration details.

OPA expects the service to expose an API endpoint that serves bundles. The bundle API should allow clients to download bundles at an arbitrary URL. In combination with a service’s path.

If the bundle exists, the server should respond with an HTTP 200 OK status followed by a gzipped tarball in the message body.

  1. HTTP/1.1 200 OK
  2. Content-Type: application/gzip

Enable bundle downloading via configuration. For example:

  1. services:
  2. - name: acmecorp
  3. url: https://example.com/service/v1
  4. credentials:
  5. bearer:
  6. token: "bGFza2RqZmxha3NkamZsa2Fqc2Rsa2ZqYWtsc2RqZmtramRmYWxkc2tm"
  7. bundles:
  8. authz:
  9. service: acmecorp
  10. resource: somedir/bundle.tar.gz
  11. polling:
  12. min_delay_seconds: 10
  13. max_delay_seconds: 20

Using this configuration, OPA will fetch bundles from https://example.com/service/v1/somedir/bundle.tar.gz.

The URL is constructed as follows:

  1. https://example.com/service/v1/somedir/bundle.tar.gz
  2. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  3. services[0].url resource

If the bundles[_].resource field is not defined, the value defaults to bundles/<name> where the name is the key value in the configuration. For the example above this is authz and would default to bundles/authz.

Bundle names can have any valid YAML characters in them, including /. This can be useful when relying on default resource behavior with a name like authz/bundle.tar.gz which results in a resource of bundles/authz/bundle.tar.gz.

See the following section for details on the bundle file format.

Caching

Services implementing the Bundle Service API should set the HTTP Etag header in bundle responses to identify the revision of the bundle. OPA will include the Etag value in the If-None-Match header of bundle requests. Services can check the If-None-Match header and reply with HTTP 304 Not Modified if the bundle has not changed since the last update.

Bundle File Format

Bundle files are gzipped tarballs that contain policies and data. The data files in the bundle must be organized hierarchically into directories inside the tarball.

The hierarchical organization indicates to OPA where to load the data files into the .

You can list the content of a bundle with tar.

  1. $ tar tzf bundle.tar.gz
  2. .manifest
  3. roles
  4. roles/bindings
  5. roles/bindings/data.json
  6. roles/permissions
  7. roles/permissions/data.json
  8. http
  9. http/example
  10. http/example/authz
  11. http/example/authz/authz.rego

In this example, the bundle contains one policy file (authz.rego) and two data files (roles/bindings/data.json and roles/permissions/data.json).

Bundle files may contain an optional .manifest file that stores bundle metadata. The file should contain a JSON serialized object, with the following fields:

  • If the bundle service is capable of serving different revisions of the same bundle, the service should include a top-level revision field containing a string value that identifies the bundle revision.

  • If you expect to load additional data into OPA from outside the bundle (e.g., via OPA’s HTTP API) you should include a top-level roots field containing of path prefixes that declare the scope of the bundle. See the section below on managing data from multiple sources. If the roots field is not included in the manifest it defaults to [""] which means that ALL data and policy must come from the bundle.

  • OPA will only load data files named data.json or data.yaml (which contain JSON or YAML respectively). Other JSON and YAML files will be ignored.

  • The *.rego policy files must be valid Modules

For example, this manifest specifies a revision (which happens to be a Git commit hash) and a set of roots for the bundle contents. In this case, the manifest declares that it owns the roots data.roles and data.http.example.authz.

  1. {
  2. "revision" : "7864d60dd78d748dbce54b569e939f5b0dc07486",
  3. "roots": ["roles", "http/example/authz"]
  4. }

Multiple Sources of Policy and Data

By default, when OPA is configured to download policy and data from a bundle service, the entire content of OPA’s policy and data cache is defined by the bundle. However, if you need to load OPA with policy and data from multiple sources, you can implement your bundle service to generate bundles that are scoped to a subset of OPA’s policy and data cache.

We recommend that whenever possible, you implement policy and data aggregation centrally, however, in some cases that’s not possible (e.g., due to latency requirements.)

To scope bundles to a subset of OPA’s policy and data cache, include a top-level roots key in the bundle that defines the roots of the data namespace that are owned by the bundle.

For example, the following manifest would declare two roots (acmecorp/policy and acmecorp/oncall):

  1. {
  2. "roots": ["acmecorp/policy", "acmecorp/oncall"]
  3. }

If OPA was loaded with a bundle containing this manifest it would only erase and overwrite policy and data under these roots. Policy and data loaded under other roots is left intact.

When OPA loads scoped bundles, it validates that:

  • The roots are not overlapping (e.g., a/b/c and a/b are overlapped and will result in an error.) Note: This is not enforced across multiple bundles. Only within the same bundle manifest.

  • The policies in the bundle are contained under the roots. This is determined by inspecting the package statement in each of the policy files. For example, given the manifest above, it would be an error to include a policy file containing package acmecorp.other because acmecorp.other is not contained in either of the roots.

  • The data in the bundle is contained under the roots.

If bundle validation fails, OPA will report the validation error via the Status API.

When you run OPA, you can provide bundle files over the command line. This allows you to manually check that your bundles include all of the files that you intended and that they are structured correctly. For example:

  1. opa run bundle.tar.gz

OPA can periodically report decision logs to remote HTTP servers. The decision logs contain events that describe policy queries. Each event includes the policy that was queried, the input to the query, bundle metadata, and other information that enables auditing and offline debugging of policy decisions.

When decision logging is enabled the OPA server will include a decision_id field in API calls that return policy decisions.

See the Configuration Reference for configuration details.

Decision Log Service API

OPA expects the service to expose an API endpoint that will receive decision logs.

  1. POST /logs[/<partition_name>] HTTP/1.1
  2. Content-Encoding: gzip
  3. Content-Type: application/json

The partition name is an optional path segment that can be used to route logs to different backends. If the partition name is not configured on the agent, updates will be sent to /logs.

The message body contains a gzip compressed JSON array. Each array element (event) represents a policy decision returned by OPA.

  1. [
  2. {
  3. "labels": {
  4. "app": "my-example-app",
  5. "id": "1780d507-aea2-45cc-ae50-fa153c8e4a5a",
  6. "version": "v0.14.2"
  7. },
  8. "decision_id": "4ca636c1-55e4-417a-b1d8-4aceb67960d1",
  9. "bundles": {
  10. "authz": {
  11. "revision": "W3sibCI6InN5cy9jYXRhbG9nIiwicyI6NDA3MX1d"
  12. }
  13. },
  14. "path": "http/example/authz/allow",
  15. "input": {
  16. "method": "GET",
  17. "path": "/salary/bob"
  18. },
  19. "result": "true",
  20. "requested_by": "[::1]:59943",
  21. "timestamp": "2018-01-01T00:00:00.000000Z"
  22. }
  23. ]

Decision log updates contain the following fields:

Local Decision Logs

Local console logging of decisions can be enabled via the console config option. This does not require any remote server. Example of minimal config to enable:

  1. decision_logs:
  2. console: true

This will dump all decision through the OPA logging system at the info level. See for more details.

Policy queries may contain sensitive information in the input document that must be removed before decision logs are uploaded to the remote API (e.g., usernames, passwords, etc.) Similarly, parts of the policy decision itself may be considered sensitive.

By default, OPA queries the data.system.log.mask path prior to encoding and uploading decision logs or calling custom decision log plugins.

OPA provides the decision log event as input to the policy query and expects the query to return a set of JSON Pointers that refer to fields in the decision log event to erase.

For example, assume OPA is queried with the following input document:

To remove the password field from decision log events related to “user” resources, supply the following policy to OPA:

  1. package system.log
  2. mask["/input/password"] {
  3. # OPA provides the entire decision log event as input to the masking policy.
  4. # Refer to the original input document under input.input.
  5. input.input.resource == "user"
  6. }
  7. # To mask certain fields unconditionally, omit the rule body.
  8. mask["/input/ssn"]

When the masking policy generates one or more JSON Pointers, they will be erased from the decision log event. The erased paths are recorded on the event itself:

  1. {
  2. "decision_id": "b4638167-7fcb-4bc7-9e80-31f5f87cb738",
  3. "erased": [
  4. "/input/password",
  5. "/input/ssn"
  6. ],
  7. "input": {
  8. "name": "bob",
  9. },
  10. ------------------------- 8< -------------------------
  11. "path": "system/main",
  12. "requested_by": "127.0.0.1:36412",
  13. "result": true,
  14. "timestamp": "2019-06-03T20:07:16.939402185Z"
  15. }

There are a few restrictions on the JSON Pointers that OPA will erase:

  • Pointers must be prefixed with /input or /result.
  • Pointers may be undefined. For example /input/name/first in the example above would be undefined. Undefined pointers are ignored.

OPA can periodically report status updates to remote HTTP servers. The updates contain status information for OPA itself as well as the Bundles that have been downloaded and activated.

OPA sends status reports whenever bundles are downloaded and activated. If the bundle download or activation fails for any reason, the status update will include error information describing the failure.

The status updates will include a set of labels that uniquely identify the OPA instance. OPA automatically includes an id value in the label set that provides a globally unique identifier or the running OPA instance and a version value that provides the version of OPA.

See the for configuration details.

Status Service API

OPA expects the service to expose an API endpoint that will receive status updates.

  1. POST /status[/<partition_name>] HTTP/1.1
  2. Content-Type: application/json
  1. {
  2. "labels": {
  3. "app": "my-example-app",
  4. "id": "1780d507-aea2-45cc-ae50-fa153c8e4a5a",
  5. "version": "v0.14.2"
  6. },
  7. "bundles": {
  8. "http/example/authz": {
  9. "active_revision": "TODO",
  10. "last_successful_download": "2018-01-01T00:00:00.000Z",
  11. "last_successful_activation": "2018-01-01T00:00:00.000Z"
  12. }
  13. },
  14. "metrics": {
  15. "prometheus": {
  16. "go_gc_duration_seconds": {
  17. "help": "A summary of the GC invocation durations.",
  18. "metric": [
  19. {
  20. "summary": {
  21. "quantile": [
  22. {
  23. "quantile": 0,
  24. "value": 0.000011799
  25. },
  26. {
  27. "quantile": 0.25,
  28. "value": 0.000011905
  29. },
  30. {
  31. "quantile": 0.5,
  32. "value": 0.000040002
  33. },
  34. {
  35. "quantile": 0.75,
  36. "value": 0.000065238
  37. },
  38. {
  39. "quantile": 1,
  40. "value": 0.000104897
  41. }
  42. ],
  43. "sample_count": 7,
  44. "sample_sum": 0.000309117
  45. }
  46. }
  47. ],
  48. "name": "go_gc_duration_seconds",
  49. "type": 2
  50. },
  51. ------------------------------8< SNIP 8<------------------------------
  52. "http_request_duration_seconds": {
  53. "help": "A histogram of duration for requests.",
  54. "metric": [
  55. {
  56. "histogram": {
  57. "bucket": [
  58. {
  59. "cumulative_count": 1,
  60. "upper_bound": 0.005
  61. },
  62. {
  63. "cumulative_count": 1,
  64. "upper_bound": 0.01
  65. },
  66. {
  67. "cumulative_count": 1,
  68. "upper_bound": 0.025
  69. },
  70. {
  71. "cumulative_count": 1,
  72. "upper_bound": 0.05
  73. },
  74. {
  75. "cumulative_count": 1,
  76. "upper_bound": 0.1
  77. },
  78. {
  79. "cumulative_count": 1,
  80. "upper_bound": 0.25
  81. },
  82. {
  83. "cumulative_count": 1,
  84. "upper_bound": 0.5
  85. },
  86. {
  87. "cumulative_count": 1,
  88. "upper_bound": 1
  89. },
  90. {
  91. "cumulative_count": 1,
  92. "upper_bound": 2.5
  93. },
  94. {
  95. "cumulative_count": 1,
  96. "upper_bound": 5
  97. },
  98. {
  99. "cumulative_count": 1,
  100. "upper_bound": 10
  101. }
  102. ],
  103. "sample_count": 1,
  104. "sample_sum": 0.003157399
  105. },
  106. "label": [
  107. {
  108. "name": "code",
  109. "value": "200"
  110. },
  111. {
  112. "name": "handler",
  113. "value": "v1/data"
  114. },
  115. {
  116. "name": "method",
  117. "value": "get"
  118. }
  119. ]
  120. ],
  121. "name": "http_request_duration_seconds",
  122. "type": 4
  123. }
  124. }
  125. }
  126. }

Status updates contain the following fields:

If the bundle download or activation failed, the status update will contain the following additional fields.

If the bundle download or activation failed, the status update will contain the following additional fields.

Services should reply with HTTP status 200 OK if the status update is processed successfully.

OPA can be configured to download bundles of policy and data, report status, and upload decision logs to remote endpoints. The discovery feature helps you centrally manage the OPA configuration for these features. You should use the discovery feature if you want to avoid managing OPA configuration updates in number of different locations.

When the discovery feature is enabled, OPA will periodically download a discovery bundle. Like regular bundles, the discovery bundle may contain JSON and Rego files. OPA will evaluate the data and policies contained in the discovery bundle to generate the rest of the configuration. There are two main ways to structure the discovery bundle:

  1. Include static JSON configuration files that define the OPA configuration.
  2. Include Rego files that can be evaluated to produce the OPA configuration.

If you need OPA to select which policy to download dynamically (e.g., based on environment variables like the region where OPA is running), use the second option.

If discovery is enabled, other features like bundle downloading and status reporting cannot be configured manually. Similarly, discovered configuration cannot override the original discovery settings in the configuration file that OPA was booted with.

See the for configuration details.

Discovery Service API

OPA expects the service to expose an API endpoint that serves bundles.

  1. GET /<service_url>/<discovery.prefix>/<discovery.name> HTTP/1.1

If the bundle exists, the server should respond with an HTTP 200 OK status followed by a gzipped tarball in the message body.

  1. HTTP/1.1 200 OK
  2. Content-Type: application/gzip

You can enable discovery with an OPA configuration file similar to the example below. In some places in the documentation, the initial configuration provided to OPA is referred to as the “boot configuration”.

  1. services:
  2. - name: acmecorp
  3. url: https://example.com/control-plane-api/v1
  4. credentials:
  5. bearer:
  6. token: "bGFza2RqZmxha3NkamZsa2Fqc2Rsa2ZqYWtsc2RqZmtramRmYWxkc2tm"
  7. discovery:
  8. name: /example/discovery
  9. prefix: configuration
  10. service: acmecorp

Using the boot configuration above, OPA will fetch discovery bundles from:

  1. https://example.com/control-plane-api/v1/configuration/example/discovery
  2. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
  3. services[discovery.service].url | |
  4. + discovery.prefix |
  5. + discovery.name

OPA generates it’s subsequent configuration by querying the Rego and JSON files contained inside the discovery bundle. The query is defined by the discovery.name field from the boot configuration: data.<discovery.name>. For example. with the boot configuration above, OPA executes the following query:

  1. data.example.discovery

As an alternative, you can also provide a decision field, to specifiy the name of the query. For example, with this configuration:

  1. services:
  2. - name: acmecorp
  3. url: https://example.com/control-plane-api/v1
  4. credentials:
  5. bearer:
  6. token: "bGFza2RqZmxha3NkamZsa2Fqc2Rsa2ZqYWtsc2RqZmtramRmYWxkc2tm"
  7. discovery:
  8. name: /example/discovery
  9. prefix: configuration
  10. decision: config

OPA executes the following query:

If the discovery bundle contained the following Rego file:

  1. package example
  2. discovery = {
  3. "bundles": {
  4. "main": {
  5. "service": "acmecorp",
  6. "resource": bundle_name
  7. },
  8. },
  9. "default_decision": "acmecorp/httpauthz/allow"
  10. }
  11. bundle_name = "acmecorp/httpauthz"

The subsequent configuration would be:

  1. {
  2. "bundles": {
  3. "main": {
  4. "service": "acmecorp",
  5. "resource": "acmecorp/httpauthz"
  6. },
  7. },
  8. "default_decision": "acmecorp/httpauthz/allow"
  9. }

The discovery bundle contents above are essentially static. The same result could be achieved by constructing the discovery bundle with a static JSON file:

  1. {
  2. "example": {
  3. "discovery": {
  4. "bundles": {
  5. "main": {
  6. "service": "acmecorp",
  7. "resource": "acmecorp/httpauthz"
  8. },
  9. },
  10. "default_decision": "acmecorp/httpauthz/allow"
  11. }
  12. }
  13. }

For an example of how to configure OPA dynamically see the section below.

The subsequent configuration does not have to specify services or include a reference to a service in the bundle, status, or decision_log sections. If the either the services or references to services are missing, OPA will default them to the value from the boot configuration.

Let’s see an example of how the discovery feature can be used to dynamically configure an OPA to download one of two bundles based on a label in the boot configuration. Let’s say the label region indicates the region in which the OPA is running and it’s value will decide the bundle to download.

Below is a policy file which generates an OPA congfiguration.

example.rego

  1. package example
  2. discovery = {
  3. "bundles": {
  4. "main": {
  5. "service": "acmecorp",
  6. "resource": bundle_name #line 7
  7. }
  8. }
  9. }
  10. rt = opa.runtime()
  11. region = rt.config.labels.region
  12. bundle_name = region_bundle[region]
  13. # region-bundle information
  14. region_bundle = {
  15. "US": "example/test1/p",
  16. "UK": "example/test2/p"
  17. }

The bundle_name variable in line 7 of the above policy will be dynamically selected based on the value of the label region. So if an OPA was started with region: "US", then the bundle_name will be example/test1/p.

Start an OPA with a boot configuration as shown below:

config.yaml

  1. services:
  2. - name: acmecorp
  3. url: https://example.com/control-plane-api/v1
  4. credentials:
  5. bearer:
  6. token: "bGFza2RqZmxha3NkamZsa2Fqc2Rsa2ZqYWtsc2RqZmtramRmYWxkc2tm"
  7. discovery:
  8. name: /example/discovery
  9. labels:
  10. region: "US"

Run OPA:

  1. opa run -s -c config.yaml

You should see a log like below, which shows the bundle being downloaded. In this case, the bundle name is example/test1/p as region is US.

  1. INFO Bundle downloaded and activated successfully. name=example/test1/p plugin=bundle

Now start another OPA with a boot configuration as shown below. Notice the region is UK:

config.yaml

  1. services:
  2. - name: acmecorp
  3. url: https://example.com/control-plane-api/v1
  4. credentials:
  5. bearer:
  6. token: "bGFza2RqZmxha3NkamZsa2Fqc2Rsa2ZqYWtsc2RqZmtramRmYWxkc2tm"
  7. discovery:
  8. name: /example/discovery
  9. labels:
  10. region: "UK"

Run OPA:

  1. opa run -s -c config.yaml

In this case, the bundle being downloaded is example/test2/p as region is UK.

    Limitations

    The discovery feature cannot be used to dynamically modify services, and discovery. This means that these configuration settings should be included in the bootup configuration file provided to OPA.