Gate: Open Policy Agent Evaluator
This plugin supports all intercept modes (request, response, request_response)
This plugin embeds an Open Policy Agent engine into Gate and restricts access to specific HTTP resources based on OPA policy evaluations. To evaluate an OPA policy you need two pieces of input:
- The Rego policy
- Input data, passed to the Rego policy as the special
inputobject
When it comes to policy distribution, the plugin currently supports three different methods to distribute policies.
The first is to inline policies, this approach works best when you have a limited number of policies and performance is really important.
The second approach is to use a remote bundle. The remote bundle can be pulled from any remote host, including the SlashID distribution hub.
The third approach is to point Gate at a local .rego file via policy_file_path. Gate reads the file once at plugin init, so this works well when the policy lives in version control next to the rest of your deployment configuration and is mounted into the Gate container — no opa build step required.
Policy input object example
As mentioned, the input option is the second ingredient necessary for OPA. The plugin passes the follow input format to OPA.
Note that this format is compatible with OPA-Envoy, so you can reuse your existing OPA policies with Gate.
The following is an example of input object Gate makes available to Rego policies during evaluation in its current v1.2 format. The version.gate field can be checked by a policy to branch on schema changes — it is bumped whenever fields are added to the input. The most recent additive change (v1.1 → v1.2) added the optional request.http.body and request.parsed_body fields described below.
{
"version": {
"gate": "v1.2"
},
"request": {
"http": {
"headers": {
"Accept": [
"application/json, text/plain, */*"
],
"Accept-Encoding": [
"gzip, compress, deflate, br"
],
"Authorization": [
"Bearer eyJhb..."
],
"Cookie": [
"foo=bar"
],
"User-Agent": [
"axios/1.3.4"
]
},
"cookies": {
"foo": "bar"
},
"host": "example.com",
"method": "POST",
"path": "/some/path",
"protocol": "HTTP/1.1",
"query": "option=value",
"scheme": "https",
"url": "https://example.com/some/path?option=value",
"body": "{\"role\":\"admin\"}"
},
"parsed_path": [
"some",
"path"
],
"parsed_query": {
"option": [
"value"
]
},
"parsed_body": {
"role": "admin"
},
"parsed_token": {
"header": {
"alg": "RS256",
"kid": "pYsNGA"
},
"payload": {
...
},
"signature": "JvVt..."
},
"time": "2023-04-09T18:38:41.117405838Z",
"token": "eyJh..."
}
}
The request.http.body and request.parsed_body fields are only populated when the plugin is configured with capture_request_body: true. parsed_body is only set when the request's Content-Type is application/json or application/x-www-form-urlencoded and the body successfully parses; for any other Content-Type (or when parsing fails), only the raw request.http.body is exposed. For form bodies, parsed_body is shaped like Go's url.Values — each value is an array of strings, so a Rego policy reads input.request.parsed_body.role[0] rather than input.request.parsed_body.role.
Note that if the plugin is configured to execute on responses as well then the input object contains both the request and response objects. The request is the same as the object shown above. The whole input object looks as follows:
{
"version": {
"gate": "v1.2"
},
"request": {
"http": {
...
}
},
"response": {
"http": {
"contentType": "application/json",
"status": 200,
"body": "{}",
"headers": {
"Accept": [
"application/json, text/plain, */*"
],
"Accept-Encoding": [
"gzip, compress, deflate, br"
],
"Authorization": [
"Bearer eyJhb..."
],
"User-Agent": [
"axios/1.3.4"
]
}
},
"time": "2023-04-09T18:38:46.117405838Z",
"parsed_path": [
"some",
"path"
],
"parsed_query": {
"option": [
"value"
]
}
}
}
The output of an evaluation is expected to be a boolean to be found at the location specified in the policy_decision_path plugin parameter.
Configuring Gate
- Environment variables
- HCL
- JSON
- TOML
- YAML
GATE_PLUGINS_<PLUGIN NUMBER>_TYPE=opa
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_HEADER_WITH_TOKEN=<Header with token>
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_COOKIE_WITH_TOKEN=<Cookie with token>
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_SLASHID_BASE_URL=<SlashID base URL>
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_POLICY=<Embedded Rego policy>
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_POLICY_BUNDLE_URL=<URL of a remote OPA bundle>
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_POLICY_FILE_PATH=<Path to a local Rego file>
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_POLICY_DECISION_PATH=<Location of the policy evaluation decision>
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_MONITORING_MODE=<boolean - Run the plugin in monitoring mode or enforcement mode. Default is enforcementn>
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_CAPTURE_REQUEST_BODY=<boolean - Buffer the request body and expose it to Rego. Default is false>
GATE_PLUGINS_<PLUGIN NUMBER>_PARAMETERS_MAX_REQUEST_BODY_SIZE=<integer - Cap on buffered body size in bytes. Default is 1048576 (1 MiB)>
In the Environment variables configuration, <PLUGIN NUMBER> defined plugin execution order.
gate = {
plugins = [
// ...
{
type = "opa"
parameters = {
header_with_token = <Header with token>
cookie_with_token = <Cookie with token>
policy = "<Embedded Rego policy>"
policy_bundle_url = "<URL of a remote OPA bundle>"
policy_file_path = "<Path to a local Rego file>"
policy_decision_path = "<Location of the policy evaluation decision>"
monitoring_mode = "<Monitoring mode>"
capture_request_body = "<Capture request body>"
max_request_body_size = "<Max request body size>"
}
}
// ...
]
}
{
"gate": {
"plugins": [
// ...
{
"type": "opa",
"parameters": {
"header_with_token": "<Header with token>",
"cookie_with_token": "<Cookie with token>",
"policy": "<Embedded Rego policy>",
"policy_bundle_url": "<URL of a remote OPA bundle>",
"policy_file_path": "<Path to a local Rego file>",
"policy_decision_path": "<Location of the policy evaluation decision>",
"monitoring_mode": "<Monitoring mode>",
"capture_request_body": "<Capture request body>",
"max_request_body_size": "<Max request body size>"
}
}
// ...
]
}
}
[[gate.plugins]]
type = "opa"
parameters.header_with_token = "<Header with token>"
parameters.cookie_with_token = "<Cookie with token>"
parameters.policy = "<Embedded Rego policy>"
parameters.policy_bundle_url = "<URL of a remote OPA bundle>"
parameters.policy_file_path = "<Path to a local Rego file>"
parameters.policy_decision_path = "<Location of the policy evaluation decision>"
parameters.monitoring_mode = "<Monitoring mode>"
parameters.capture_request_body = "<Capture request body>"
parameters.max_request_body_size = "<Max request body size>"
gate:
plugins:
// ...
- type: opa
parameters:
header_with_token: <Header with token>
cookie_with_token: <Cookie with token>
policy: <Embedded Rego policy>
policy_bundle_url: <URL of a remote OPA bundle>
policy_file_path: <Path to a local Rego file>
policy_decision_path: <Location of the policy evaluation decision>
monitoring_mode: <Monitoring mode>
capture_request_body: <Capture request body>
max_request_body_size: <Max request body size>
// ...
where:
<Header with token>is the request header containing the token, if any. This option andcookie_with_tokenare mutually exclusive.<Cookie with token>is the request cookie containing the token, if any. This option andheader_with_tokenare mutually exclusive.<Embedded Rego policy>a Rego policy you can inline directly in Gate's configuration to accept or deny incoming requests. Mutually exclusive withpolicy_bundle_urlandpolicy_file_path; exactly one of the three must be set.<URL of a remote OPA bundle>location of a policy and data OPA bundle built withopa buildGate will fetch periodically to gather updated policies and data. Mutually exclusive withpolicyandpolicy_file_path; exactly one of the three must be set.<Path to a local Rego file>filesystem path to a.regopolicy that Gate reads at startup. Useful when you want to keep the policy in version control and mount it into the container without runningopa buildor hosting a bundle server. The file is read once at plugin init; restart Gate to pick up changes. Mutually exclusive withpolicyandpolicy_bundle_url; exactly one of the three must be set.<Location of the policy evaluation decision>a Rego policy decision path to a boolean value: iftruethe request is granted, otherwise it's rejected with a 403 status.<Monitoring mode>a boolean indicating whether to run the plugin in monitoring mode or enforcement mode. Iftruethe plugin runs in monitoring mode, by default it isfalse(enforcement mode).<Capture request body>a boolean indicating whether to buffer the HTTP request body and expose it to Rego policies viainput.request.http.body(raw) andinput.request.parsed_body(auto-parsed forapplication/jsonandapplication/x-www-form-urlencoded). Defaults tofalse. Enabling this forces the request body to be fully buffered before reaching upstream handlers, which loses streaming semantics; it should only be turned on when a policy actually needs the body.<Max request body size>an integer setting the maximum buffered request body size, in bytes. Requests with a body larger than this cap are rejected with413 Request Entity Too Large. Only applies whencapture_request_bodyistrue. Defaults to1048576(1 MiB).
If neither <Header with token> nor <Cookie with token> are set, the plugin attempts to get a bearer token from the Authorization header, and strips the Bearer prefix from it.
Disabling plugin for specific URLs
You can enable or disable this plugin for specific URLs by using the enabled option in the URLs configuration.
- Environment variables
- HCL
- JSON
- TOML
- YAML
GATE_URLS_0_PATTERN=svc-example.com/*
GATE_URLS_0_TARGET=http://example:8080
GATE_URLS_1_PATTERN=svc-another-example.com/
GATE_URLS_1_TARGET=https://another-example:8080
gate = {
urls = [
{
pattern = "svc-example.com/*"
target = "http://example:8080"
},
{
pattern = "svc-another-example.com/"
target = "https://another-example:8080"
}
]
// ...
}
{
"gate": {
"urls": [
{
"pattern": "svc-example.com/*",
"target": "http://example:8080",
},
{
"pattern": "svc-another-example.com/",
"target": "https://another-example:8080"
}
],
// ...
URL are matched in the order they are defined in the configuration file.
[[gate.urls]]
pattern = "svc-example.com/*"
target = "http://example:8080"
[[gate.urls]]
pattern = "svc-another-example.com/"
target = "https://another-example:8080"
URL are matched in the order they are defined in the configuration file.
gate:
urls:
- pattern: svc-example.com/*
target: http://example:8080
- pattern: svc-another-example.com/
target: https://another-example:8080
URL are matched in the order they are defined in the configuration file.