diff --git a/docs/en/latest/plugins/opa.md b/docs/en/latest/plugins/opa.md
index 0413d6e347b3..fc702355ee33 100644
--- a/docs/en/latest/plugins/opa.md
+++ b/docs/en/latest/plugins/opa.md
@@ -6,7 +6,7 @@ keywords:
- Plugin
- Open Policy Agent
- opa
-description: This document contains information about the Apache APISIX opa Plugin.
+description: The opa plugin integrates with Open Policy Agent, enabling unified policy definition and enforcement for authorization in API operations.
---
+
+
+
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
## Description
-The `opa` Plugin can be used to integrate with [Open Policy Agent (OPA)](https://www.openpolicyagent.org). OPA is a policy engine that helps defininig and enforcing authorization policies, which determines whether a user or application has the necessary permissions to perform a particular action or access a particular resource. Using OPA with APISIX decouples authorization logics from APISIX.
+The `opa` Plugin supports the integration with [Open Policy Agent (OPA)](https://www.openpolicyagent.org), a unified policy engine and framework that helps define and enforce authorization policies. Authorization logic is defined in [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) and stored in OPA.
+
+Once configured, the OPA engine will evaluate the client request to a protected Route to determine whether the request should have access to the Upstream resource based on the defined policies.
## Attributes
-| Name | Type | Required | Default | Valid values | Description |
-|-------------------|---------|----------|---------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| host | string | True | | | Host address of the OPA service. For example, `https://localhost:8181`. |
-| ssl_verify | boolean | False | true | | When set to `true` verifies the SSL certificates. |
-| policy | string | True | | | OPA policy path. A combination of `package` and `decision`. While using advanced features like custom response, you can omit `decision`. When specifying a namespace, use the slash format (`examples/echo`) instead of dot notation (`examples.echo`). |
-| timeout | integer | False | 3000ms | [1, 60000]ms | Timeout for the HTTP call. |
-| keepalive | boolean | False | true | | When set to `true`, keeps the connection alive for multiple requests. |
-| keepalive_timeout | integer | False | 60000ms | [1000, ...]ms | Idle time after which the connection is closed. |
-| keepalive_pool | integer | False | 5 | [1, ...]ms | Connection pool limit. |
-| with_route | boolean | False | false | | When set to true, sends information about the current Route. |
-| with_service | boolean | False | false | | When set to true, sends information about the current Service. |
-| with_consumer | boolean | False | false | | When set to true, sends information about the current Consumer. Note that this may send sensitive information like the API key. Make sure to turn it on only when you are sure it is safe. |
+| Name | Type | Required | Default | Valid values | Description |
+|------|------|----------|---------|--------------|-------------|
+| host | string | True | | | Address of the OPA server. |
+| policy | string | True | | | Policy path to evaluate. The plugin appends this value to `/v1/data/`, so it must point to an OPA document/decision whose `result` is an object containing an `allow` field. The returned object can also include `reason`, `headers`, and `status_code`. For example, use a path such as `rbac` only if querying `/v1/data/rbac` returns an object with this structure, rather than a bare boolean result. |
+| ssl_verify | boolean | False | true | | If true, verify the OPA server's SSL certificate. |
+| timeout | integer | False | 3000 | [1, 60000] | Timeout for the HTTP call in milliseconds. |
+| keepalive | boolean | False | true | | If true, keep the connection alive for multiple requests. |
+| keepalive_timeout | integer | False | 60000 | >= 1000 | Idle time in milliseconds after which the connection is closed. |
+| keepalive_pool | integer | False | 5 | >= 1 | The number of idle connections. |
+| with_route | boolean | False | | | If true, send information of the current Route. |
+| with_service | boolean | False | | | If true, send information of the current Service. |
+| with_consumer | boolean | False | | | If true, send information of the current Consumer. Note that the Consumer information may include sensitive information such as the API key. Only set this option to `true` if you are sure it is safe to do so. |
+| send_headers_upstream | array[string] | False | | >= 1 item | List of header names to forward from the OPA response to the Upstream service when the request is allowed. |
-## Data definition
+## Data Definition
-### APISIX to OPA service
+### APISIX to OPA Service
The JSON below shows the data sent to the OPA service by APISIX:
@@ -89,7 +99,7 @@ Each of these keys are explained below:
- `var` contains the basic information about the requested connection (IP, port, request timestamp etc).
- `route`, `service` and `consumer` contains the same data as stored in APISIX and are only sent if the `opa` Plugin is configured on these objects.
-### OPA service to APISIX
+### OPA Service to APISIX
The JSON below shows the response from the OPA service to APISIX:
@@ -109,219 +119,1267 @@ The JSON below shows the response from the OPA service to APISIX:
The keys in the response are explained below:
- `allow` is indispensable and indicates whether the request is allowed to be forwarded through APISIX.
-- `reason`, `headers`, and `status_code` are optional and are only returned when you configure a custom response. See the next section use cases for this.
+- `reason`, `headers`, and `status_code` are optional and are only returned when you configure a custom response.
+
+## Examples
+
+:::note
+
+You can fetch the `admin_key` from `conf/config.yaml` and save to an environment variable with the following command:
+
+```bash
+admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
+```
+
+:::
+
+Before proceeding, you should have a running OPA server. Start one using Docker or deploy it to Kubernetes:
-## Example usage
+
-First, you need to launch the Open Policy Agent environment:
+
```shell
-docker run -d --name opa -p 8181:8181 openpolicyagent/opa:0.35.0 run -s
+docker run -d --name opa-server -p 8181:8181 openpolicyagent/opa:1.6.0 run --server --addr :8181 --log-level debug
```
-### Basic usage
+To verify that the OPA server is installed and the port is exposed properly, run:
-Once you have the OPA service running, you can create a basic policy:
+```shell
+curl http://127.0.0.1:8181 | grep Version
+```
+
+You should see a response similar to the following:
+
+```text
+Version: 1.6.0
+```
+
+
+
+
+
+Create a Deployment and Service for OPA in your cluster:
+
+```yaml title="opa-server.yaml"
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ namespace: aic
+ name: opa
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: opa
+ template:
+ metadata:
+ labels:
+ app: opa
+ spec:
+ containers:
+ - name: opa
+ image: openpolicyagent/opa:1.6.0
+ args:
+ - run
+ - --server
+ - --addr=:8181
+ - --log-level=debug
+ ports:
+ - containerPort: 8181
+---
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: opa
+spec:
+ selector:
+ app: opa
+ ports:
+ - port: 8181
+ targetPort: 8181
+```
+
+Apply the configuration to your cluster:
```shell
-curl -X PUT '127.0.0.1:8181/v1/policies/example1' \
- -H 'Content-Type: text/plain' \
- -d 'package example1
+kubectl apply -f opa-server.yaml
+```
+
+Wait for the OPA pod to be ready. Once ready, the OPA server will be available within the cluster at `http://opa.aic.svc.cluster.local:8181`. To push policies to it from outside the cluster, set up a port-forward:
-import input.request
+```shell
+kubectl port-forward -n aic svc/opa 8181:8181 &
+```
+
+
+
+
+
+### Implement a Basic Policy
+
+The following example implements a basic authorization policy in OPA to allow only GET requests.
+
+Create an OPA policy that only allows HTTP GET requests:
+
+```shell
+curl "http://127.0.0.1:8181/v1/policies/getonly" -X PUT \
+ -H "Content-Type: text/plain" \
+ -d '
+package getonly
default allow = false
-allow {
- # HTTP method must GET
- request.method == "GET"
+allow if {
+ input.request.method == "GET"
}'
```
-Then, you can configure the `opa` Plugin on a specific Route:
+Create a Route with the `opa` Plugin:
+
+
+
+
```shell
-curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/r1' \
- -H 'X-API-KEY: ' \
- -H 'Content-Type: application/json' \
- -d '{
- "uri": "/*",
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "opa-route",
+ "uri": "/anything",
"plugins": {
- "opa": {
- "host": "http://127.0.0.1:8181",
- "policy": "example1"
- }
+ "opa": {
+ "host": "http://127.0.0.1:8181",
+ "policy": "getonly"
+ }
},
"upstream": {
- "nodes": {
- "httpbin.org:80": 1
- },
- "type": "roundrobin"
+ "type": "roundrobin",
+ "nodes": {
+ "httpbin.org:80": 1
+ }
}
-}'
+ }'
+```
+
+Update `host` to your OPA server address. The `policy` is set to `getonly`.
+
+
+
+
+
+```yaml title="adc.yaml"
+services:
+ - name: opa-service
+ routes:
+ - name: opa-route
+ uris:
+ - /anything
+ plugins:
+ opa:
+ host: "http://127.0.0.1:8181"
+ policy: getonly
+ upstream:
+ type: roundrobin
+ nodes:
+ - host: httpbin.org
+ port: 80
+ weight: 1
```
-Now, to test it out:
+Update `host` to your OPA server address. The `policy` is set to `getonly`.
+
+Synchronize the configuration to the gateway:
```shell
-curl -i -X GET 127.0.0.1:9080/get
+adc sync -f adc.yaml
```
+
+
+
+
+
+
+
+
+```yaml title="opa-ic.yaml"
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ type: ExternalName
+ externalName: httpbin.org
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+ namespace: aic
+ name: opa-plugin-config
+spec:
+ plugins:
+ - name: opa
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: getonly
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ parentRefs:
+ - name: apisix
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /anything
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
+```
+
+Apply the configuration to your cluster:
+
```shell
-HTTP/1.1 200 OK
+kubectl apply -f opa-ic.yaml
+```
+
+
+
+
+
+```yaml title="opa-ic.yaml"
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ ingressClassName: apisix
+ externalNodes:
+ - type: Domain
+ name: httpbin.org
+---
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ ingressClassName: apisix
+ http:
+ - name: opa-route
+ match:
+ paths:
+ - /anything
+ upstreams:
+ - name: httpbin-external-domain
+ plugins:
+ - name: opa
+ enable: true
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: getonly
```
-Now if we try to make a request to a different endpoint the request will fail:
+Apply the configuration to your cluster:
+```shell
+kubectl apply -f opa-ic.yaml
```
-curl -i -X POST 127.0.0.1:9080/post
+
+
+
+
+
+
+
+
+
+To verify the policy, send a GET request to the Route:
+
+```shell
+curl -i "http://127.0.0.1:9080/anything"
```
+You should receive an `HTTP/1.1 200 OK` response.
+
+Send another request to the Route using PUT:
+
```shell
-HTTP/1.1 403 FORBIDDEN
+curl -i "http://127.0.0.1:9080/anything" -X PUT
```
-### Using custom response
+You should receive an `HTTP/1.1 403 Forbidden` response.
+
+### Understand Data Format
+
+The following example helps you understand the data and the format APISIX pushes to OPA to support authorization logic writing. The example continues with the policy and the Route in the [last example](#implement-a-basic-policy).
-You can also configure custom responses for more complex scenarios:
+Now, update the Plugin on the previously created Route to include Route information:
+
+
+
+
```shell
-curl -X PUT '127.0.0.1:8181/v1/policies/example2' \
- -H 'Content-Type: text/plain' \
- -d 'package example2
+curl "http://127.0.0.1:9180/apisix/admin/routes/opa-route" -X PATCH \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "plugins": {
+ "opa": {
+ "with_route": true
+ }
+ }
+ }'
+```
+
+
+
+
+
+Update `adc.yaml` to add `with_route: true`:
+
+```yaml title="adc.yaml"
+services:
+ - name: opa-service
+ routes:
+ - name: opa-route
+ uris:
+ - /anything
+ plugins:
+ opa:
+ host: "http://127.0.0.1:8181"
+ policy: getonly
+ with_route: true
+ upstream:
+ type: roundrobin
+ nodes:
+ - host: httpbin.org
+ port: 80
+ weight: 1
+```
-import input.request
+Synchronize the configuration to the gateway:
+
+```shell
+adc sync -f adc.yaml
+```
+
+
+
+
+
+
+
+
+
+Update `opa-ic.yaml` to add `with_route: true`:
+
+```yaml title="opa-ic.yaml"
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ type: ExternalName
+ externalName: httpbin.org
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+ namespace: aic
+ name: opa-plugin-config
+spec:
+ plugins:
+ - name: opa
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: getonly
+ with_route: true
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ parentRefs:
+ - name: apisix
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /anything
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
+```
+
+Apply the updated configuration to your cluster:
+
+```shell
+kubectl apply -f opa-ic.yaml
+```
+
+
+
+
+
+Update `opa-ic.yaml` to add `with_route: true`:
+
+```yaml title="opa-ic.yaml"
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ ingressClassName: apisix
+ externalNodes:
+ - type: Domain
+ name: httpbin.org
+---
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ ingressClassName: apisix
+ http:
+ - name: opa-route
+ match:
+ paths:
+ - /anything
+ upstreams:
+ - name: httpbin-external-domain
+ plugins:
+ - name: opa
+ enable: true
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: getonly
+ with_route: true
+```
+
+Apply the updated configuration to your cluster:
+
+```shell
+kubectl apply -f opa-ic.yaml
+```
+
+
+
+
+
+
+
+
+
+Send a request to the Route:
+
+```shell
+curl -i "http://127.0.0.1:9080/anything"
+```
+
+In the OPA server log (with `--log-level debug`), the `req_body` will now include Route information in addition to the request and var fields.
+
+### Return Custom Response
+
+The following example demonstrates how you can return a custom response code and message when the request is unauthorized.
+
+Create an OPA policy that only allows HTTP GET requests and returns `302` with a custom message when the request is unauthorized:
+
+```shell
+curl "http://127.0.0.1:8181/v1/policies/customresp" -X PUT \
+ -H "Content-Type: text/plain" \
+ -d '
+package customresp
default allow = false
-allow {
- request.method == "GET"
+allow if {
+ input.request.method == "GET"
}
-# custom response body (Accepts a string or an object, the object will respond as JSON format)
-reason = "test" {
- not allow
+reason := "The resource has temporarily moved. Please follow the new URL." if {
+ not allow
}
-# custom response header (The data of the object can be written in this way)
-headers = {
- "Location": "http://example.com/auth"
-} {
- not allow
+headers := {
+ "Location": "http://example.com/auth"
+} if {
+ not allow
}
-# custom response status code
-status_code = 302 {
- not allow
-}'
+status_code := 302 if {
+ not allow
+}
+'
```
-Now you can test it out by changing the `opa` Plugin's policy parameter to `example2` and then making a request:
+Create a Route with the `opa` Plugin:
+
+
+
+
```shell
-curl -i -X GET 127.0.0.1:9080/get
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "opa-route",
+ "uri": "/anything",
+ "plugins": {
+ "opa": {
+ "host": "http://127.0.0.1:8181",
+ "policy": "customresp"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "httpbin.org:80": 1
+ }
+ }
+ }'
```
+
+
+
+
+```yaml title="adc.yaml"
+services:
+ - name: opa-service
+ routes:
+ - name: opa-route
+ uris:
+ - /anything
+ plugins:
+ opa:
+ host: "http://127.0.0.1:8181"
+ policy: customresp
+ upstream:
+ type: roundrobin
+ nodes:
+ - host: httpbin.org
+ port: 80
+ weight: 1
+```
+
+Synchronize the configuration to the gateway:
+
+```shell
+adc sync -f adc.yaml
```
-HTTP/1.1 200 OK
+
+
+
+
+
+
+
+
+
+```yaml title="opa-ic.yaml"
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ type: ExternalName
+ externalName: httpbin.org
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+ namespace: aic
+ name: opa-customresp-plugin-config
+spec:
+ plugins:
+ - name: opa
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: customresp
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ parentRefs:
+ - name: apisix
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /anything
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-customresp-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
```
-Now if you make a failing request, you will see the custom response from the OPA service:
+Apply the configuration to your cluster:
+```shell
+kubectl apply -f opa-ic.yaml
```
-curl -i -X POST 127.0.0.1:9080/post
+
+
+
+
+
+```yaml title="opa-ic.yaml"
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ ingressClassName: apisix
+ externalNodes:
+ - type: Domain
+ name: httpbin.org
+---
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ ingressClassName: apisix
+ http:
+ - name: opa-route
+ match:
+ paths:
+ - /anything
+ upstreams:
+ - name: httpbin-external-domain
+ plugins:
+ - name: opa
+ enable: true
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: customresp
```
+Apply the configuration to your cluster:
+
+```shell
+kubectl apply -f opa-ic.yaml
```
-HTTP/1.1 302 FOUND
+
+
+
+
+
+
+
+
+
+Send a GET request to the Route:
+
+```shell
+curl -i "http://127.0.0.1:9080/anything"
+```
+
+You should receive an `HTTP/1.1 200 OK` response.
+
+Send a POST request to the Route:
+
+```shell
+curl -i "http://127.0.0.1:9080/anything" -X POST
+```
+
+You should receive an `HTTP/1.1 302 Moved Temporarily` response:
+
+```text
+HTTP/1.1 302 Moved Temporarily
+...
Location: http://example.com/auth
-test
+The resource has temporarily moved. Please follow the new URL.
+```
+
+### Implement RBAC
+
+The following example demonstrates how to implement authentication and RBAC using the `jwt-auth` and `opa` Plugins. You will be implementing RBAC logics such that:
+
+* An `user` role can only read the Upstream resources.
+* An `admin` role can read and write the Upstream resources.
+
+Create an OPA policy for RBAC of two example Consumers, where `john` has the `user` role and `jane` has the `admin` role:
+
+```shell
+curl "http://127.0.0.1:8181/v1/policies/rbac" -X PUT \
+ -H "Content-Type: text/plain" \
+ -d '
+package rbac
+
+# Assign roles to users
+user_roles := {
+ "john": ["user"],
+ "jane": ["admin"]
+}
+
+# Map permissions to HTTP methods
+permission_methods := {
+ "read": "GET",
+ "write": "POST"
+}
+
+# Assign role permissions
+role_permissions := {
+ "user": ["read"],
+ "admin": ["read", "write"]
+}
+
+# Get JWT authorization token
+bearer_token := t if {
+ t := input.request.headers.authorization
+}
+
+# Decode the token to get role and permission
+token := {"payload": payload} if {
+ [_, payload, _] := io.jwt.decode(bearer_token)
+}
+
+# Normalize permission to a list
+normalized_permissions := ps if {
+ ps := token.payload.permission
+ not is_string(ps)
+}
+
+normalized_permissions := [ps] if {
+ ps := token.payload.permission
+ is_string(ps)
+}
+
+# Implement RBAC logic
+default allow = false
+
+allow if {
+ # Look up the list of roles for the user
+ roles := user_roles[input.consumer.username]
+
+ # For each role in that list
+ r := roles[_]
+
+ # Look up the permissions list for the role
+ permissions := role_permissions[r]
+
+ # For each permission
+ p := permissions[_]
+
+ # Check if the permission matches the request method
+ permission_methods[p] == input.request.method
+
+ # Check if the normalized permissions include the permission
+ p in normalized_permissions
+}
+'
```
-### Sending APISIX data
+Create two Consumers `john` and `jane` in APISIX and configure their `jwt-auth` Credentials:
+
+
+
+
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers" \
+ -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "username": "john"
+}'
+```
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers" \
+ -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "username": "jane"
+}'
+```
+
+Configure the `jwt-auth` Credentials for the Consumers, using the default algorithm `HS256`:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "cred-john-jwt-auth",
+ "plugins": {
+ "jwt-auth": {
+ "key": "john-key",
+ "secret": "john-hs256-secret-that-is-very-long"
+ }
+ }
+ }'
+```
-Let's think about another scenario, when your decision needs to use some APISIX data, such as `route`, `consumer`, etc., how should we do it?
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "cred-jane-jwt-auth",
+ "plugins": {
+ "jwt-auth": {
+ "key": "jane-key",
+ "secret": "jane-hs256-secret-that-is-very-long"
+ }
+ }
+ }'
+```
-If your OPA service needs to make decisions based on APISIX data like Route and Consumer details, you can configure the Plugin to do so.
+
+
+
+
+```yaml title="adc.yaml"
+consumers:
+ - username: john
+ credentials:
+ - name: cred-john-jwt-auth
+ type: jwt-auth
+ config:
+ key: john-key
+ secret: john-hs256-secret-that-is-very-long
+ - username: jane
+ credentials:
+ - name: cred-jane-jwt-auth
+ type: jwt-auth
+ config:
+ key: jane-key
+ secret: jane-hs256-secret-that-is-very-long
+```
-The example below shows a simple `echo` policy which will return the data sent by APISIX as it is:
+Synchronize the configuration to the gateway:
```shell
-curl -X PUT '127.0.0.1:8181/v1/policies/echo' \
- -H 'Content-Type: text/plain' \
- -d 'package echo
+adc sync -f adc.yaml
+```
-allow = false
-reason = input'
+
+
+
+
+
+
+
+
+```yaml title="opa-consumers-ic.yaml"
+apiVersion: apisix.apache.org/v1alpha1
+kind: Consumer
+metadata:
+ namespace: aic
+ name: john
+spec:
+ gatewayRef:
+ name: apisix
+ credentials:
+ - type: jwt-auth
+ name: cred-john-jwt-auth
+ config:
+ key: john-key
+ secret: john-hs256-secret-that-is-very-long
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: Consumer
+metadata:
+ namespace: aic
+ name: jane
+spec:
+ gatewayRef:
+ name: apisix
+ credentials:
+ - type: jwt-auth
+ name: cred-jane-jwt-auth
+ config:
+ key: jane-key
+ secret: jane-hs256-secret-that-is-very-long
```
-Now we can configure the Plugin on the Route to send APISIX data:
+Apply the configuration to your cluster:
+
+```shell
+kubectl apply -f opa-consumers-ic.yaml
+```
+
+When using the Ingress Controller, APISIX prefixes Consumer names with the Kubernetes namespace. For example, a Consumer named `john` in the `aic` namespace becomes `aic_john`. Update the OPA RBAC policy to use the prefixed names accordingly.
+
+
+
+
+
+The ApisixConsumer CRD has a known issue where `private_key` is incorrectly required during the configuration. This issue will be addressed in a future release. At the moment, the example cannot be completed with APISIX CRDs.
+
+
+
+
+
+
+
+
+
+Create a Route and configure the `jwt-auth` and `opa` Plugins:
+
+
+
+
```shell
-curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/r1' \
- -H 'X-API-KEY: ' \
- -H 'Content-Type: application/json' \
- -d '{
- "uri": "/*",
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "opa-route",
+ "methods": ["GET", "POST"],
+ "uris": ["/get","/post"],
"plugins": {
- "opa": {
- "host": "http://127.0.0.1:8181",
- "policy": "echo",
- "with_route": true
- }
+ "jwt-auth": {},
+ "opa": {
+ "host": "http://127.0.0.1:8181",
+ "policy": "rbac",
+ "with_consumer": true
+ }
},
"upstream": {
- "nodes": {
- "httpbin.org:80": 1
- },
- "type": "roundrobin"
+ "type": "roundrobin",
+ "nodes": {
+ "httpbin.org:80": 1
+ }
}
-}'
+ }'
+```
+
+
+
+
+
+Update `adc.yaml` to add the Route with `jwt-auth` and `opa` Plugins:
+
+```yaml title="adc.yaml"
+consumers:
+ - username: john
+ credentials:
+ - name: cred-john-jwt-auth
+ type: jwt-auth
+ config:
+ key: john-key
+ secret: john-hs256-secret-that-is-very-long
+ - username: jane
+ credentials:
+ - name: cred-jane-jwt-auth
+ type: jwt-auth
+ config:
+ key: jane-key
+ secret: jane-hs256-secret-that-is-very-long
+services:
+ - name: opa-service
+ routes:
+ - name: opa-route
+ uris:
+ - /get
+ - /post
+ methods:
+ - GET
+ - POST
+ plugins:
+ jwt-auth: {}
+ opa:
+ host: "http://127.0.0.1:8181"
+ policy: rbac
+ with_consumer: true
+ upstream:
+ type: roundrobin
+ nodes:
+ - host: httpbin.org
+ port: 80
+ weight: 1
```
-Now if you make a request, you can see the data from the Route through the custom response:
+Synchronize the configuration to the gateway:
```shell
-curl -X GET 127.0.0.1:9080/get
+adc sync -f adc.yaml
+```
+
+
+
+
+
+
+
+
+
+```yaml title="opa-route-ic.yaml"
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ type: ExternalName
+ externalName: httpbin.org
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+ namespace: aic
+ name: opa-rbac-plugin-config
+spec:
+ plugins:
+ - name: jwt-auth
+ config:
+ _meta:
+ disable: false
+ - name: opa
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: rbac
+ with_consumer: true
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ namespace: aic
+ name: opa-rbac-route
+spec:
+ parentRefs:
+ - name: apisix
+ rules:
+ - matches:
+ - path:
+ type: Exact
+ value: /get
+ method: GET
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-rbac-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
+ - matches:
+ - path:
+ type: Exact
+ value: /post
+ method: POST
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-rbac-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
+```
+
+Apply the configuration to your cluster:
+
+```shell
+kubectl apply -f opa-route-ic.yaml
+```
+
+
+
+
+
+The ApisixConsumer CRD has a known issue where `private_key` is incorrectly required during the configuration. This issue will be addressed in a future release. At the moment, the example cannot be completed with APISIX CRDs.
+
+
+
+
+
+
+
+
+
+#### Verify as `john`
+
+To issue a JWT for `john`, you could use [JWT.io's JWT encoder](https://jwt.io) or other utilities. If you are using [JWT.io's JWT encoder](https://jwt.io), do the following:
+
+* Fill in `HS256` as the algorithm.
+* Update the secret in the **Valid secret** section to be `john-hs256-secret-that-is-very-long`.
+* Update payload with role `user`, permission `read`, and Consumer key `john-key`; as well as `exp` or `nbf` in UNIX timestamp.
+
+Your payload should look similar to the following:
+
+```json
{
- "type": "http",
- "request": {
- xxx
- },
- "var": {
- xxx
- },
- "route": {
- xxx
- }
+ "role": "user",
+ "permission": "read",
+ "key": "john-key",
+ "nbf": 1729132271
}
```
-## Delete Plugin
+Copy the generated JWT and save to a variable:
-To remove the `opa` Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect.
+```text
+export john_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsInBlcm1pc3Npb24iOiJyZWFkIiwia2V5Ijoiam9obi1rZXkiLCJuYmYiOjE3MjkxMzIyNzF9.rAHMTQfnnGFnKYc3am_lpE9pZ9E8EaOT_NBQ5Ss8pk4
+```
-:::note
-You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
+Send a GET request to the Route with the JWT of `john`:
-```bash
-admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
+```shell
+curl -i "http://127.0.0.1:9080/get" -H "Authorization: Bearer ${john_jwt_token}"
```
-:::
+You should receive an `HTTP/1.1 200 OK` response.
+
+Send a POST request to the Route with the same JWT:
```shell
-curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
+curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: Bearer ${john_jwt_token}"
+```
+
+You should receive an `HTTP/1.1 403 Forbidden` response.
+
+#### Verify as `jane`
+
+Similarly, to issue a JWT for `jane`, you could use [JWT.io's JWT encoder](https://jwt.io) or other utilities. If you are using [JWT.io's JWT encoder](https://jwt.io), do the following:
+
+* Fill in `HS256` as the algorithm.
+* Update the secret in the **Valid secret** section to be `jane-hs256-secret-that-is-very-long`.
+* Update payload with role `admin`, permission `["read","write"]`, and Consumer key `jane-key`; as well as `exp` or `nbf` in UNIX timestamp.
+
+Your payload should look similar to the following:
+
+```json
{
- "methods": ["GET"],
- "uri": "/hello",
- "plugins": {},
- "upstream": {
- "type": "roundrobin",
- "nodes": {
- "127.0.0.1:1980": 1
- }
- }
-}'
+ "role": "admin",
+ "permission": ["read","write"],
+ "key": "jane-key",
+ "nbf": 1729132271
+}
```
+
+Copy the generated JWT and save to a variable:
+
+```text
+export jane_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJwZXJtaXNzaW9uIjpbInJlYWQiLCJ3cml0ZSJdLCJrZXkiOiJqYW5lLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.meZ-AaGHUPwN_GvVOE3IkKuAJ1wqlCguaXf3gm3Ww8s
+```
+
+Send a GET request to the Route with the JWT of `jane`:
+
+```shell
+curl -i "http://127.0.0.1:9080/get" -H "Authorization: Bearer ${jane_jwt_token}"
+```
+
+You should receive an `HTTP/1.1 200 OK` response.
+
+Send a POST request to the Route with the same JWT:
+
+```shell
+curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: Bearer ${jane_jwt_token}"
+```
+
+You should also receive an `HTTP/1.1 200 OK` response.
diff --git a/docs/zh/latest/plugins/opa.md b/docs/zh/latest/plugins/opa.md
index b718e5d27be6..0e9c1e0d569e 100644
--- a/docs/zh/latest/plugins/opa.md
+++ b/docs/zh/latest/plugins/opa.md
@@ -6,7 +6,7 @@ keywords:
- Plugin
- Open Policy Agent
- opa
-description: 本篇文档介绍了 Apache APISIX 通过 opa 插件与 Open Policy Agent 对接的相关信息。
+description: opa 插件与 Open Policy Agent 集成,支持在 API 操作中统一定义和执行授权策略。
---
+
+
+
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
## 描述
-`opa` 插件可用于与 [Open Policy Agent](https://www.openpolicyagent.org) 进行集成,实现后端服务的认证授权与访问服务等功能解耦,减少系统复杂性。
+`opa` 插件支持与 [Open Policy Agent (OPA)](https://www.openpolicyagent.org) 集成,OPA 是一个统一的策略引擎和框架,可用于定义和执行授权策略。授权逻辑以 [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) 语言编写并存储在 OPA 中。
+
+配置后,OPA 引擎将根据定义的策略评估客户端对受保护路由的请求,以决定是否允许其访问上游资源。
## 属性
-| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
-|-------------------|---------|----------|---------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| host | string | 是 | | | OPA 服务的主机地址,例如 `https://localhost:8181`。 |
-| ssl_verify | boolean | 否 | true | | 当设置为 `true` 时,将验证 SSL 证书。 |
-| policy | string | 是 | | | OPA 策略路径,是 `package` 和 `decision` 配置的组合。当使用高级功能(如自定义响应)时,你可以省略 `decision` 配置。指定命名空间时,请使用斜杠格式(例如 `examples/echo`),而不是点号格式(例如 `examples.echo`)。 |
-| timeout | integer | 否 | 3000ms | [1, 60000]ms | 设置 HTTP 调用超时时间。 |
-| keepalive | boolean | 否 | true | | 当设置为 `true` 时,将为多个请求保持连接并处于活动状态。 |
-| keepalive_timeout | integer | 否 | 60000ms | [1000, ...]ms | 连接断开后的闲置时间。 |
-| keepalive_pool | integer | 否 | 5 | [1, ...]ms | 连接池限制。 |
-| with_route | boolean | 否 | false | | 当设置为 `true` 时,发送关于当前 Route 的信息。 |
-| with_service | boolean | 否 | false | | 当设置为 `true` 时,发送关于当前 Service 的信息。 |
-| with_consumer | boolean | 否 | false | | 当设置为 `true` 时,发送关于当前 Consumer 的信息。注意,这可能会发送敏感信息,如 API key。请确保在安全的情况下才打开它。 |
+| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
+|------|------|--------|--------|--------|------|
+| host | string | 是 | | | OPA 服务器地址。 |
+| policy | string | 是 | | | 要评估的策略路径。该值会被直接拼接到 OPA 的 `/v1/data/`。`data.result` 必须是一个包含 `allow` 字段的对象,并可按需包含 `reason`、`headers`、`status_code` 等字段;因此,`policy` 应指向返回该对象结构的路径,而不是仅返回 `true` 或 `false` 的布尔规则。 |
+| ssl_verify | boolean | 否 | true | | 若为 true,则验证 OPA 服务器的 SSL 证书。 |
+| timeout | integer | 否 | 3000 | [1, 60000] | HTTP 调用超时时间(毫秒)。 |
+| keepalive | boolean | 否 | true | | 若为 true,则为多个请求保持连接活跃。 |
+| keepalive_timeout | integer | 否 | 60000 | >= 1000 | 连接空闲后关闭的等待时间(毫秒)。 |
+| keepalive_pool | integer | 否 | 5 | >= 1 | 空闲连接数。 |
+| with_route | boolean | 否 | | | 若为 true,发送当前路由的信息。 |
+| with_service | boolean | 否 | | | 若为 true,发送当前 Service 的信息。 |
+| with_consumer | boolean | 否 | | | 若为 true,发送当前消费者的信息。注意,Consumer 信息可能包含 API key 等敏感信息,仅在确认安全的情况下将此选项设为 `true`。 |
+| send_headers_upstream | array[string] | 否 | | >= 1 | 请求被允许时,需要从 OPA 响应转发到上游服务的请求头名称列表。 |
## 数据定义
### APISIX 向 OPA 发送信息
-下述示例代码展示了如何通过 APISIX 向 OPA 服务发送数据:
+下述示例展示了 APISIX 向 OPA 服务发送的数据格式:
```json
{
@@ -82,16 +92,16 @@ description: 本篇文档介绍了 Apache APISIX 通过 opa 插件与 Open Polic
}
```
-上述代码具体释义如下:
+各字段说明如下:
-- `type` 代表请求类型(如 `http` 或 `stream`);
-- `request` 则需要在 `type` 为 `http` 时使用,包含基本的请求信息(如 URL、头信息等);
-- `var` 包含关于请求连接的基本信息(如 IP、端口、请求时间戳等);
-- `route`、`service` 和 `consumer` 包含的数据与 APISIX 中存储的数据相同,只有当这些对象上配置了 `opa` 插件时才会发送。
+- `type` 表示请求类型(`http` 或 `stream`)。
+- `request` 在 `type` 为 `http` 时使用,包含基本的请求信息(URL、请求头等)。
+- `var` 包含请求连接的基本信息(IP、端口、请求时间戳等)。
+- `route`、`service` 和 `consumer` 包含与 APISIX 中存储的相同数据,仅在这些对象上配置了 `opa` 插件时才会发送。
### OPA 向 APISIX 返回数据
-下述示例代码展示了 OPA 服务对 APISIX 发送请求后的响应数据:
+下述示例展示了 OPA 服务对 APISIX 的响应数据格式:
```json
{
@@ -106,224 +116,1270 @@ description: 本篇文档介绍了 Apache APISIX 通过 opa 插件与 Open Polic
}
```
-上述响应中的代码释义如下:
+各字段说明如下:
-- `allow` 配置是必不可少的,它表示请求是否允许通过 APISIX 进行转发;
-- `reason`、`headers` 和 `status_code` 是可选的,只有当你配置一个自定义响应时才会返回这些选项信息,具体使用方法可查看后续测试用例。
+- `allow` 是必填字段,表示请求是否允许通过 APISIX 转发。
+- `reason`、`headers` 和 `status_code` 是可选字段,仅在配置了自定义响应时返回。
-## 测试插件
+## 使用示例
+
+:::note
+
+你可以这样从 `conf/config.yaml` 中获取 `admin_key` 并存入环境变量:
+
+```bash
+admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
+```
+
+:::
+
+在开始之前,你需要一个运行中的 OPA 服务器。可以通过 Docker 启动或部署到 Kubernetes:
+
+
+
+
+
+```shell
+docker run -d --name opa-server -p 8181:8181 openpolicyagent/opa:1.6.0 run --server --addr :8181 --log-level debug
+```
-首先启动 OPA 环境:
+验证 OPA 服务器安装正常且端口已正确暴露:
```shell
-docker run -d --name opa -p 8181:8181 openpolicyagent/opa:0.35.0 run -s
+curl http://127.0.0.1:8181 | grep Version
```
-### 基本用法
+你应该看到类似如下的响应:
+
+```text
+Version: 1.6.0
+```
-一旦你运行了 OPA 服务,就可以进行基本策略的创建:
+
+
+
+
+在集群中创建 OPA 的 Deployment 和 Service:
+
+```yaml title="opa-server.yaml"
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ namespace: aic
+ name: opa
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: opa
+ template:
+ metadata:
+ labels:
+ app: opa
+ spec:
+ containers:
+ - name: opa
+ image: openpolicyagent/opa:1.6.0
+ args:
+ - run
+ - --server
+ - --addr=:8181
+ - --log-level=debug
+ ports:
+ - containerPort: 8181
+---
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: opa
+spec:
+ selector:
+ app: opa
+ ports:
+ - port: 8181
+ targetPort: 8181
+```
+
+将配置应用到集群:
```shell
-curl -X PUT '127.0.0.1:8181/v1/policies/example1' \
- -H 'Content-Type: text/plain' \
- -d 'package example1
+kubectl apply -f opa-server.yaml
+```
-import input.request
+等待 OPA Pod 就绪。就绪后,OPA 服务器将在集群内通过 `http://opa.aic.svc.cluster.local:8181` 访问。如需从集群外部推送策略,请设置端口转发:
+
+```shell
+kubectl port-forward -n aic svc/opa 8181:8181 &
+```
+
+
+
+
+
+### 实现基本策略
+
+以下示例在 OPA 中实现一个仅允许 GET 请求的基本授权策略。
+
+创建一个仅允许 HTTP GET 请求的 OPA 策略:
+
+```shell
+curl "http://127.0.0.1:8181/v1/policies/getonly" -X PUT \
+ -H "Content-Type: text/plain" \
+ -d '
+package getonly
default allow = false
-allow {
- # HTTP method must GET
- request.method == "GET"
+allow if {
+ input.request.method == "GET"
}'
```
-然后在指定路由上配置 `opa` 插件:
+创建带有 `opa` 插件的路由:
+
+
+
+
```shell
-curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/r1' \
- -H 'X-API-KEY: ' \
- -H 'Content-Type: application/json' \
- -d '{
- "uri": "/*",
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "opa-route",
+ "uri": "/anything",
"plugins": {
- "opa": {
- "host": "http://127.0.0.1:8181",
- "policy": "example1"
- }
+ "opa": {
+ "host": "http://127.0.0.1:8181",
+ "policy": "getonly"
+ }
},
"upstream": {
- "nodes": {
- "httpbin.org:80": 1
- },
- "type": "roundrobin"
+ "type": "roundrobin",
+ "nodes": {
+ "httpbin.org:80": 1
+ }
}
-}'
+ }'
+```
+
+将 `host` 替换为你的 OPA 服务器地址,`policy` 设置为 `getonly`。
+
+
+
+
+
+```yaml title="adc.yaml"
+services:
+ - name: opa-service
+ routes:
+ - name: opa-route
+ uris:
+ - /anything
+ plugins:
+ opa:
+ host: "http://127.0.0.1:8181"
+ policy: getonly
+ upstream:
+ type: roundrobin
+ nodes:
+ - host: httpbin.org
+ port: 80
+ weight: 1
+```
+
+将 `host` 替换为你的 OPA 服务器地址,`policy` 设置为 `getonly`。
+
+将配置同步到网关:
+
+```shell
+adc sync -f adc.yaml
+```
+
+
+
+
+
+
+
+
+
+```yaml title="opa-ic.yaml"
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ type: ExternalName
+ externalName: httpbin.org
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+ namespace: aic
+ name: opa-plugin-config
+spec:
+ plugins:
+ - name: opa
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: getonly
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ parentRefs:
+ - name: apisix
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /anything
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
+```
+
+将配置应用到集群:
+
+```shell
+kubectl apply -f opa-ic.yaml
+```
+
+
+
+
+
+```yaml title="opa-ic.yaml"
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ ingressClassName: apisix
+ externalNodes:
+ - type: Domain
+ name: httpbin.org
+---
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ ingressClassName: apisix
+ http:
+ - name: opa-route
+ match:
+ paths:
+ - /anything
+ upstreams:
+ - name: httpbin-external-domain
+ plugins:
+ - name: opa
+ enable: true
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: getonly
+```
+
+将配置应用到集群:
+
+```shell
+kubectl apply -f opa-ic.yaml
+```
+
+
+
+
+
+
+
+
+
+向路由发送 GET 请求以验证策略:
+
+```shell
+curl -i "http://127.0.0.1:9080/anything"
```
-使用如下命令进行测试:
+你应该收到 `HTTP/1.1 200 OK` 响应。
+
+使用 PUT 方法向路由发送请求:
```shell
-curl -i -X GET 127.0.0.1:9080/get
+curl -i "http://127.0.0.1:9080/anything" -X PUT
```
+你应该收到 `HTTP/1.1 403 Forbidden` 响应。
+
+### 了解数据格式
+
+以下示例帮助你了解 APISIX 推送给 OPA 的数据格式,以便编写授权逻辑。该示例基于[上一个示例](#实现基本策略)中的策略和路由。
+
+更新之前创建的路由上的插件,使其包含路由信息:
+
+
+
+
+
```shell
-HTTP/1.1 200 OK
+curl "http://127.0.0.1:9180/apisix/admin/routes/opa-route" -X PATCH \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "plugins": {
+ "opa": {
+ "with_route": true
+ }
+ }
+ }'
+```
+
+
+
+
+
+更新 `adc.yaml`,添加 `with_route: true`:
+
+```yaml title="adc.yaml"
+services:
+ - name: opa-service
+ routes:
+ - name: opa-route
+ uris:
+ - /anything
+ plugins:
+ opa:
+ host: "http://127.0.0.1:8181"
+ policy: getonly
+ with_route: true
+ upstream:
+ type: roundrobin
+ nodes:
+ - host: httpbin.org
+ port: 80
+ weight: 1
+```
+
+将配置同步到网关:
+
+```shell
+adc sync -f adc.yaml
+```
+
+
+
+
+
+
+
+
+
+更新 `opa-ic.yaml`,添加 `with_route: true`:
+
+```yaml title="opa-ic.yaml"
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ type: ExternalName
+ externalName: httpbin.org
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+ namespace: aic
+ name: opa-plugin-config
+spec:
+ plugins:
+ - name: opa
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: getonly
+ with_route: true
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ parentRefs:
+ - name: apisix
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /anything
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
```
-如果尝试向不同的端点发出请求,会出现请求失败的状态:
+将更新后的配置应用到集群:
```shell
-curl -i -X POST 127.0.0.1:9080/post
+kubectl apply -f opa-ic.yaml
```
+
+
+
+
+更新 `opa-ic.yaml`,添加 `with_route: true`:
+
+```yaml title="opa-ic.yaml"
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ ingressClassName: apisix
+ externalNodes:
+ - type: Domain
+ name: httpbin.org
+---
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ ingressClassName: apisix
+ http:
+ - name: opa-route
+ match:
+ paths:
+ - /anything
+ upstreams:
+ - name: httpbin-external-domain
+ plugins:
+ - name: opa
+ enable: true
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: getonly
+ with_route: true
+```
+
+将更新后的配置应用到集群:
+
```shell
-HTTP/1.1 403 FORBIDDEN
+kubectl apply -f opa-ic.yaml
```
-### 使用自定义响应
+
+
+
+
+
+
+
-除了基础用法外,你还可以为更复杂的使用场景配置自定义响应,参考示例如下:
+向路由发送请求:
```shell
-curl -X PUT '127.0.0.1:8181/v1/policies/example2' \
- -H 'Content-Type: text/plain' \
- -d 'package example2
+curl -i "http://127.0.0.1:9080/anything"
+```
+
+在 OPA 服务器日志(启用 `--log-level debug`)中,`req_body` 将在请求和变量字段之外还包含路由信息。
+
+### 返回自定义响应
-import input.request
+以下示例演示如何在请求未授权时返回自定义响应码和消息。
+
+创建一个仅允许 HTTP GET 请求、并在未授权时返回 `302` 及自定义消息的 OPA 策略:
+
+```shell
+curl "http://127.0.0.1:8181/v1/policies/customresp" -X PUT \
+ -H "Content-Type: text/plain" \
+ -d '
+package customresp
default allow = false
-allow {
- request.method == "GET"
+allow if {
+ input.request.method == "GET"
}
-# custom response body (Accepts a string or an object, the object will respond as JSON format)
-reason = "test" {
- not allow
+reason := "The resource has temporarily moved. Please follow the new URL." if {
+ not allow
}
-# custom response header (The data of the object can be written in this way)
-headers = {
- "Location": "http://example.com/auth"
-} {
- not allow
+headers := {
+ "Location": "http://example.com/auth"
+} if {
+ not allow
}
-# custom response status code
-status_code = 302 {
- not allow
-}'
+status_code := 302 if {
+ not allow
+}
+'
+```
+
+创建带有 `opa` 插件的路由:
+
+
+
+
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "opa-route",
+ "uri": "/anything",
+ "plugins": {
+ "opa": {
+ "host": "http://127.0.0.1:8181",
+ "policy": "customresp"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "httpbin.org:80": 1
+ }
+ }
+ }'
+```
+
+
+
+
+
+```yaml title="adc.yaml"
+services:
+ - name: opa-service
+ routes:
+ - name: opa-route
+ uris:
+ - /anything
+ plugins:
+ opa:
+ host: "http://127.0.0.1:8181"
+ policy: customresp
+ upstream:
+ type: roundrobin
+ nodes:
+ - host: httpbin.org
+ port: 80
+ weight: 1
```
-同时,你可以将 `opa` 插件的策略参数调整为 `example2`,然后发出请求进行测试:
+将配置同步到网关:
```shell
-curl -i -X GET 127.0.0.1:9080/get
+adc sync -f adc.yaml
```
+
+
+
+
+
+
+
+
+```yaml title="opa-ic.yaml"
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ type: ExternalName
+ externalName: httpbin.org
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+ namespace: aic
+ name: opa-customresp-plugin-config
+spec:
+ plugins:
+ - name: opa
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: customresp
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ parentRefs:
+ - name: apisix
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /anything
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-customresp-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
+```
+
+将配置应用到集群:
+
```shell
-HTTP/1.1 200 OK
+kubectl apply -f opa-ic.yaml
```
-此时如果你发出一个失败请求,将会收到来自 OPA 服务的自定义响应反馈,如下所示:
+
+
+
+
+```yaml title="opa-ic.yaml"
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ ingressClassName: apisix
+ externalNodes:
+ - type: Domain
+ name: httpbin.org
+---
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ namespace: aic
+ name: opa-route
+spec:
+ ingressClassName: apisix
+ http:
+ - name: opa-route
+ match:
+ paths:
+ - /anything
+ upstreams:
+ - name: httpbin-external-domain
+ plugins:
+ - name: opa
+ enable: true
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: customresp
+```
+
+将配置应用到集群:
+
+```shell
+kubectl apply -f opa-ic.yaml
+```
+
+
+
+
+
+
+
+
+
+向路由发送 GET 请求:
```shell
-curl -i -X POST 127.0.0.1:9080/post
+curl -i "http://127.0.0.1:9080/anything"
```
+你应该收到 `HTTP/1.1 200 OK` 响应。
+
+向路由发送 POST 请求:
+
```shell
-HTTP/1.1 302 FOUND
+curl -i "http://127.0.0.1:9080/anything" -X POST
+```
+
+你应该收到 `HTTP/1.1 302 Moved Temporarily` 响应:
+
+```text
+HTTP/1.1 302 Moved Temporarily
+...
Location: http://example.com/auth
-test
+The resource has temporarily moved. Please follow the new URL.
+```
+
+### 实现 RBAC
+
+以下示例演示如何结合 `jwt-auth` 和 `opa` 插件实现认证和 RBAC,其中:
+
+* `user` 角色仅能读取上游资源。
+* `admin` 角色可以读取和写入上游资源。
+
+为示例消费者 `john`(`user` 角色)和 `jane`(`admin` 角色)创建 OPA RBAC 策略:
+
+```shell
+curl "http://127.0.0.1:8181/v1/policies/rbac" -X PUT \
+ -H "Content-Type: text/plain" \
+ -d '
+package rbac
+
+# Assign roles to users
+user_roles := {
+ "john": ["user"],
+ "jane": ["admin"]
+}
+
+# Map permissions to HTTP methods
+permission_methods := {
+ "read": "GET",
+ "write": "POST"
+}
+
+# Assign role permissions
+role_permissions := {
+ "user": ["read"],
+ "admin": ["read", "write"]
+}
+
+# Get JWT authorization token
+bearer_token := t if {
+ t := input.request.headers.authorization
+}
+
+# Decode the token to get role and permission
+token := {"payload": payload} if {
+ [_, payload, _] := io.jwt.decode(bearer_token)
+}
+
+# Normalize permission to a list
+normalized_permissions := ps if {
+ ps := token.payload.permission
+ not is_string(ps)
+}
+
+normalized_permissions := [ps] if {
+ ps := token.payload.permission
+ is_string(ps)
+}
+
+# Implement RBAC logic
+default allow = false
+
+allow if {
+ # Look up the list of roles for the user
+ roles := user_roles[input.consumer.username]
+
+ # For each role in that list
+ r := roles[_]
+
+ # Look up the permissions list for the role
+ permissions := role_permissions[r]
+
+ # For each permission
+ p := permissions[_]
+
+ # Check if the permission matches the request method
+ permission_methods[p] == input.request.method
+
+ # Check if the normalized permissions include the permission
+ p in normalized_permissions
+}
+'
+```
+
+在 APISIX 中创建消费者 `john` 和 `jane`,并配置 `jwt-auth` 凭证:
+
+
+
+
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers" \
+ -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "username": "john"
+}'
+```
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers" \
+ -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "username": "jane"
+}'
+```
+
+为消费者配置 `jwt-auth` 凭证,使用默认算法 `HS256`:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "cred-john-jwt-auth",
+ "plugins": {
+ "jwt-auth": {
+ "key": "john-key",
+ "secret": "john-hs256-secret-that-is-very-long"
+ }
+ }
+ }'
```
-### 发送 APISIX 数据
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "cred-jane-jwt-auth",
+ "plugins": {
+ "jwt-auth": {
+ "key": "jane-key",
+ "secret": "jane-hs256-secret-that-is-very-long"
+ }
+ }
+ }'
+```
-如果你的 OPA 服务需要根据 APISIX 的某些数据(如 Route 和 Consumer 的详细信息)来进行后续操作时,则可以通过配置插件来实现。
+
+
+
+
+```yaml title="adc.yaml"
+consumers:
+ - username: john
+ credentials:
+ - name: cred-john-jwt-auth
+ type: jwt-auth
+ config:
+ key: john-key
+ secret: john-hs256-secret-that-is-very-long
+ - username: jane
+ credentials:
+ - name: cred-jane-jwt-auth
+ type: jwt-auth
+ config:
+ key: jane-key
+ secret: jane-hs256-secret-that-is-very-long
+```
-下述示例展示了一个简单的 `echo` 策略,它将原样返回 APISIX 发送的数据:
+将配置同步到网关:
```shell
-curl -X PUT '127.0.0.1:8181/v1/policies/echo' \
- -H 'Content-Type: text/plain' \
- -d 'package echo
+adc sync -f adc.yaml
+```
-allow = false
-reason = input'
+
+
+
+
+
+
+
+
+```yaml title="opa-consumers-ic.yaml"
+apiVersion: apisix.apache.org/v1alpha1
+kind: Consumer
+metadata:
+ namespace: aic
+ name: john
+spec:
+ gatewayRef:
+ name: apisix
+ credentials:
+ - type: jwt-auth
+ name: cred-john-jwt-auth
+ config:
+ key: john-key
+ secret: john-hs256-secret-that-is-very-long
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: Consumer
+metadata:
+ namespace: aic
+ name: jane
+spec:
+ gatewayRef:
+ name: apisix
+ credentials:
+ - type: jwt-auth
+ name: cred-jane-jwt-auth
+ config:
+ key: jane-key
+ secret: jane-hs256-secret-that-is-very-long
```
-现在就可以在路由上配置插件来发送 APISIX 数据:
+将配置应用到集群:
+
+```shell
+kubectl apply -f opa-consumers-ic.yaml
+```
+
+使用 Ingress Controller 时,APISIX 会在消费者名称前加上 Kubernetes 命名空间前缀。例如,`aic` 命名空间中名为 `john` 的消费者会变为 `aic_john`。请相应更新 OPA RBAC 策略中的用户名。
+
+
+
+
+
+ApisixConsumer CRD 存在已知问题,`private_key` 在配置时被错误地设为必填项。该问题将在未来版本中修复。目前无法通过 APISIX CRD 完成此示例。
+
+
+
+
+
+
+
+
+
+创建路由并配置 `jwt-auth` 和 `opa` 插件:
+
+
+
+
```shell
-curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/r1' \
- -H 'X-API-KEY: ' \
- -H 'Content-Type: application/json' \
- -d '{
- "uri": "/*",
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "opa-route",
+ "methods": ["GET", "POST"],
+ "uris": ["/get","/post"],
"plugins": {
- "opa": {
- "host": "http://127.0.0.1:8181",
- "policy": "echo",
- "with_route": true
- }
+ "jwt-auth": {},
+ "opa": {
+ "host": "http://127.0.0.1:8181",
+ "policy": "rbac",
+ "with_consumer": true
+ }
},
"upstream": {
- "nodes": {
- "httpbin.org:80": 1
- },
- "type": "roundrobin"
+ "type": "roundrobin",
+ "nodes": {
+ "httpbin.org:80": 1
+ }
}
-}'
+ }'
```
-此时如果你提出一个请求,则可以通过自定义响应看到来自路由的数据:
+
+
+
+
+更新 `adc.yaml`,添加带有 `jwt-auth` 和 `opa` 插件的路由:
+
+```yaml title="adc.yaml"
+consumers:
+ - username: john
+ credentials:
+ - name: cred-john-jwt-auth
+ type: jwt-auth
+ config:
+ key: john-key
+ secret: john-hs256-secret-that-is-very-long
+ - username: jane
+ credentials:
+ - name: cred-jane-jwt-auth
+ type: jwt-auth
+ config:
+ key: jane-key
+ secret: jane-hs256-secret-that-is-very-long
+services:
+ - name: opa-service
+ routes:
+ - name: opa-route
+ uris:
+ - /get
+ - /post
+ methods:
+ - GET
+ - POST
+ plugins:
+ jwt-auth: {}
+ opa:
+ host: "http://127.0.0.1:8181"
+ policy: rbac
+ with_consumer: true
+ upstream:
+ type: roundrobin
+ nodes:
+ - host: httpbin.org
+ port: 80
+ weight: 1
+```
+
+将配置同步到网关:
```shell
-curl -X GET 127.0.0.1:9080/get
+adc sync -f adc.yaml
+```
+
+
+
+
+
+
+
+
+
+```yaml title="opa-route-ic.yaml"
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: aic
+ name: httpbin-external-domain
+spec:
+ type: ExternalName
+ externalName: httpbin.org
+---
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+ namespace: aic
+ name: opa-rbac-plugin-config
+spec:
+ plugins:
+ - name: jwt-auth
+ config:
+ _meta:
+ disable: false
+ - name: opa
+ config:
+ host: "http://opa.aic.svc.cluster.local:8181"
+ policy: rbac
+ with_consumer: true
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ namespace: aic
+ name: opa-rbac-route
+spec:
+ parentRefs:
+ - name: apisix
+ rules:
+ - matches:
+ - path:
+ type: Exact
+ value: /get
+ method: GET
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-rbac-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
+ - matches:
+ - path:
+ type: Exact
+ value: /post
+ method: POST
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: apisix.apache.org
+ kind: PluginConfig
+ name: opa-rbac-plugin-config
+ backendRefs:
+ - name: httpbin-external-domain
+ port: 80
```
+将配置应用到集群:
+
```shell
+kubectl apply -f opa-route-ic.yaml
+```
+
+
+
+
+
+ApisixConsumer CRD 存在已知问题,`private_key` 在配置时被错误地设为必填项。该问题将在未来版本中修复。目前无法通过 APISIX CRD 完成此示例。
+
+
+
+
+
+
+
+
+
+#### 以 `john` 身份验证
+
+要为 `john` 生成 JWT,可使用 [JWT.io 的 JWT 编码器](https://jwt.io) 或其他工具。若使用 [JWT.io 的 JWT 编码器](https://jwt.io),请执行以下操作:
+
+* 将算法填写为 `HS256`。
+* 将 **Valid secret** 部分的密钥更新为 `john-hs256-secret-that-is-very-long`。
+* 在 payload 中填入角色 `user`、权限 `read`、Consumer key `john-key`,以及 UNIX 时间戳格式的 `exp` 或 `nbf`。
+
+你的 payload 应类似如下:
+
+```json
{
- "type": "http",
- "request": {
- xxx
- },
- "var": {
- xxx
- },
- "route": {
- xxx
- }
+ "role": "user",
+ "permission": "read",
+ "key": "john-key",
+ "nbf": 1729132271
}
```
-## 删除插件
-
-当你需要禁用 `opa` 插件时,可以通过以下命令删除相应的 JSON 配置,APISIX 将会自动重新加载相关配置,无需重启服务:
+复制生成的 JWT 并存入变量:
-:::note
+```text
+export john_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsInBlcm1pc3Npb24iOiJyZWFkIiwia2V5Ijoiam9obi1rZXkiLCJuYmYiOjE3MjkxMzIyNzF9.rAHMTQfnnGFnKYc3am_lpE9pZ9E8EaOT_NBQ5Ss8pk4
+```
-您可以这样从 `config.yaml` 中获取 `admin_key` 并存入环境变量:
+使用 `john` 的 JWT 向路由发送 GET 请求:
-```bash
-admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
+```shell
+curl -i "http://127.0.0.1:9080/get" -H "Authorization: Bearer ${john_jwt_token}"
```
-:::
+你应该收到 `HTTP/1.1 200 OK` 响应。
+
+使用相同 JWT 向路由发送 POST 请求:
```shell
-curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
+curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: Bearer ${john_jwt_token}"
+```
+
+你应该收到 `HTTP/1.1 403 Forbidden` 响应。
+
+#### 以 `jane` 身份验证
+
+同样地,为 `jane` 生成 JWT,可使用 [JWT.io 的 JWT 编码器](https://jwt.io) 或其他工具。若使用 [JWT.io 的 JWT 编码器](https://jwt.io),请执行以下操作:
+
+* 将算法填写为 `HS256`。
+* 将 **Valid secret** 部分的密钥更新为 `jane-hs256-secret-that-is-very-long`。
+* 在 payload 中填入角色 `admin`、权限 `["read","write"]`、Consumer key `jane-key`,以及 UNIX 时间戳格式的 `exp` 或 `nbf`。
+
+你的 payload 应类似如下:
+
+```json
{
- "methods": ["GET"],
- "uri": "/hello",
- "plugins": {},
- "upstream": {
- "type": "roundrobin",
- "nodes": {
- "127.0.0.1:1980": 1
- }
- }
-}'
+ "role": "admin",
+ "permission": ["read","write"],
+ "key": "jane-key",
+ "nbf": 1729132271
+}
```
+
+复制生成的 JWT 并存入变量:
+
+```text
+export jane_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJwZXJtaXNzaW9uIjpbInJlYWQiLCJ3cml0ZSJdLCJrZXkiOiJqYW5lLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.meZ-AaGHUPwN_GvVOE3IkKuAJ1wqlCguaXf3gm3Ww8s
+```
+
+使用 `jane` 的 JWT 向路由发送 GET 请求:
+
+```shell
+curl -i "http://127.0.0.1:9080/get" -H "Authorization: Bearer ${jane_jwt_token}"
+```
+
+你应该收到 `HTTP/1.1 200 OK` 响应。
+
+使用相同 JWT 向路由发送 POST 请求:
+
+```shell
+curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: Bearer ${jane_jwt_token}"
+```
+
+你也应该收到 `HTTP/1.1 200 OK` 响应。