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
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: my-policy
spec:
type: middleware.http.opa
version: v1
metadata:
# `includedHeaders` is a 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
- name: includedHeaders
value: "x-my-custom-header, x-jwt-header"
# `defaultStatus` is the status code to return for denied responses
- name: defaultStatus
value: 403
# `readBody` controls whether the middleware reads the entire request body in-memory and make it
# availble for policy decisions.
- name: readBody
value: "false"
# `rego` is the open policy agent policy to evaluate. required
# The policy package must be http and the policy must set data.http.allow
- name: rego
value: |
package http
default allow = true
# Allow may also be an object and include other properties
# 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:
allow = {
"status_code": 301,
"additional_headers": {
"location": "https://my.site/authorize"
}
} {
not jwt.payload["my-claim"]
}
# You can also allow the request and add additional headers to it:
allow = {
"allow": true,
"additional_headers": {
"x-my-claim": my_claim
}
} {
my_claim := jwt.payload["my-claim"]
}
jwt = { "payload": payload } {
auth_header := input.request.headers["Authorization"]
[_, jwt] := split(auth_header, " ")
[_, payload, _] := io.jwt.decode(jwt)
}
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
Field | Details | Example |
---|---|---|
rego | The Rego policy language | See above |
defaultStatus | The status code to return for denied responses | “https://accounts.google.com“ , “https://login.salesforce.com“ |
readBody | If 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” |
includedHeaders | A 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.
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: appconfig
spec:
httpPipeline:
handlers:
- name: my-policy
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.
type Input struct {
request HTTPRequest
}
type HTTPRequest struct {
// The request method (e.g. GET,POST,etc...)
method string
// The raw request path (e.g. "/v2/my-path/")
path string
// The path broken down into parts for easy consumption (e.g. ["v2", "my-path"])
path_parts string[]
// The raw query string (e.g. "?a=1&b=2")
raw_query string
// The query broken down into keys and their values
query map[string][]string
// The request headers
// NOTE: By default, no headers are included. You must specify what headers
// you want to receive via `spec.metadata.includedHeaders` (see above)
headers map[string]string
// The request scheme (e.g. http, https)
scheme string
// The request body (e.g. http, https)
body string
}
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:
package http
default allow = false
which is the same as:
package http
default allow = {
"allow": false
}
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:
package http
default allow = {
"allow": false,
"status_code": 401
}
Adding response headers
To redirect, add headers and set the status_code
to the returned result:
package http
default allow = {
"allow": false,
"status_code": 301,
"additional_headers": {
"Location": "https://my.redirect.site"
}
}
Adding request headers
You can also set additional headers on the allowed request:
package http
default allow = false
allow = { "allow": true, "additional_headers": { "X-JWT-Payload": payload } } {
not input.path[0] == "forbidden"
// Where `jwt` is the result of another rule
payload := base64.encode(json.marshal(jwt.payload))
}
Result structure
type Result bool
// or
type Result struct {
// Whether to allow or deny the incoming request
allow bool
// Overrides denied response status code; Optional
status_code int
// Sets headers on allowed request or denied response; Optional
additional_headers map[string]string
}
Related links
Last modified June 19, 2023: Merge pull request #3565 from dapr/aacrawfi/skip-secrets-close (b1763bf)