Apply Open Policy Agent (OPA) policies

Use middleware to apply Open Policy Agent (OPA) policies on incoming requests

The Open Policy Agent (OPA) HTTP middleware applies OPA Policies to incoming Dapr HTTP requests. This can be used to apply reusable authorization policies to app endpoints.

Component format

  1. apiVersion: dapr.io/v1alpha1
  2. kind: Component
  3. metadata:
  4. name: my-policy
  5. spec:
  6. type: middleware.http.opa
  7. version: v1
  8. metadata:
  9. # `includedHeaders` is a comma-separated set of case-insensitive headers to include in the request input.
  10. # Request headers are not passed to the policy by default. Include to receive incoming request headers in
  11. # the input
  12. - name: includedHeaders
  13. value: "x-my-custom-header, x-jwt-header"
  14. # `defaultStatus` is the status code to return for denied responses
  15. - name: defaultStatus
  16. value: 403
  17. # `readBody` controls whether the middleware reads the entire request body in-memory and make it
  18. # available for policy decisions.
  19. - name: readBody
  20. value: "false"
  21. # `rego` is the open policy agent policy to evaluate. required
  22. # The policy package must be http and the policy must set data.http.allow
  23. - name: rego
  24. value: |
  25. package http
  26. default allow = true
  27. # Allow may also be an object and include other properties
  28. # For example, if you wanted to redirect on a policy failure, you could set the status code to 301 and set the location header on the response:
  29. allow = {
  30. "status_code": 301,
  31. "additional_headers": {
  32. "location": "https://my.site/authorize"
  33. }
  34. } {
  35. not jwt.payload["my-claim"]
  36. }
  37. # You can also allow the request and add additional headers to it:
  38. allow = {
  39. "allow": true,
  40. "additional_headers": {
  41. "x-my-claim": my_claim
  42. }
  43. } {
  44. my_claim := jwt.payload["my-claim"]
  45. }
  46. jwt = { "payload": payload } {
  47. auth_header := input.request.headers["Authorization"]
  48. [_, jwt] := split(auth_header, " ")
  49. [_, payload, _] := io.jwt.decode(jwt)
  50. }

You can prototype and experiment with policies using the official OPA playground. For example, you can find the example policy above here.

Spec metadata fields

FieldDetailsExample
regoThe Rego policy languageSee above
defaultStatusThe status code to return for denied responseshttps://accounts.google.com, https://login.salesforce.com
readBodyIf set to true (the default value), the body of each request is read fully in-memory and can be used to make policy decisions. If your policy doesn’t depend on inspecting the request body, consider disabling this (setting to false) for significant performance improvements.“false”
includedHeadersA comma-separated set of case-insensitive headers to include in the request input. Request headers are not passed to the policy by default. Include to receive incoming request headers in the input“x-my-custom-header, x-jwt-header”

Dapr configuration

To be applied, the middleware must be referenced in configuration. See middleware pipelines.

  1. apiVersion: dapr.io/v1alpha1
  2. kind: Configuration
  3. metadata:
  4. name: appconfig
  5. spec:
  6. httpPipeline:
  7. handlers:
  8. - name: my-policy
  9. type: middleware.http.opa

Input

This middleware supplies a HTTPRequest as input.

HTTPRequest

The HTTPRequest input contains all the relevant information about an incoming HTTP Request.

  1. type Input struct {
  2. request HTTPRequest
  3. }
  4. type HTTPRequest struct {
  5. // The request method (e.g. GET,POST,etc...)
  6. method string
  7. // The raw request path (e.g. "/v2/my-path/")
  8. path string
  9. // The path broken down into parts for easy consumption (e.g. ["v2", "my-path"])
  10. path_parts string[]
  11. // The raw query string (e.g. "?a=1&b=2")
  12. raw_query string
  13. // The query broken down into keys and their values
  14. query map[string][]string
  15. // The request headers
  16. // NOTE: By default, no headers are included. You must specify what headers
  17. // you want to receive via `spec.metadata.includedHeaders` (see above)
  18. headers map[string]string
  19. // The request scheme (e.g. http, https)
  20. scheme string
  21. // The request body (e.g. http, https)
  22. body string
  23. }

Result

The policy must set data.http.allow with either a boolean value, or an object value with an allow boolean property. A true allow will allow the request, while a false value will reject the request with the status specified by defaultStatus. The following policy, with defaults, demonstrates a 403 - Forbidden for all requests:

  1. package http
  2. default allow = false

which is the same as:

  1. package http
  2. default allow = {
  3. "allow": false
  4. }

Changing the rejected response status code

When rejecting a request, you can override the status code the that gets returned. For example, if you wanted to return a 401 instead of a 403, you could do the following:

  1. package http
  2. default allow = {
  3. "allow": false,
  4. "status_code": 401
  5. }

Adding response headers

To redirect, add headers and set the status_code to the returned result:

  1. package http
  2. default allow = {
  3. "allow": false,
  4. "status_code": 301,
  5. "additional_headers": {
  6. "Location": "https://my.redirect.site"
  7. }
  8. }

Adding request headers

You can also set additional headers on the allowed request:

  1. package http
  2. default allow = false
  3. allow = { "allow": true, "additional_headers": { "X-JWT-Payload": payload } } {
  4. not input.path[0] == "forbidden"
  5. // Where `jwt` is the result of another rule
  6. payload := base64.encode(json.marshal(jwt.payload))
  7. }

Result structure

  1. type Result bool
  2. // or
  3. type Result struct {
  4. // Whether to allow or deny the incoming request
  5. allow bool
  6. // Overrides denied response status code; Optional
  7. status_code int
  8. // Sets headers on allowed request or denied response; Optional
  9. additional_headers map[string]string
  10. }