OPA
opa 插件支持与 Open Policy Agent (OPA) 集成,OPA 是一个统一的策略引擎和框架,有助于定义和强制执行授权策略。授权逻辑在 Rego 中定义并存储在 OPA 中。
配置后,OPA 引擎将评估对受保护路由的客户端请求,根据定义的策略确定请求是否有权访问上游资源。
示例
以下示例展示了如何在不同场景下使用 opa 插件。
在继续之前,你应该拥有一个正在运行的 OPA 服务器,或者在 Docker 中启动一个新的:
- Docker
- Kubernetes
docker run -d --name opa-server -p 8181:8181 openpolicyagent/opa:1.6.0 run --server --addr :8181 --log-level debug
run -s将 OPA 作为服务器启动。--log-level debug打印调试信息,以检查 APISIX 推送到 OPA 的数据。
要验证 OPA 服务器是否已安装且端口正确暴露,请运行:
curl http://127.0.0.1:8181 | grep Version
你应该看到类似以下的响应:
Version: 1.6.0
在集群中为 OPA 创建 Deployment 和 Service:
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
将配置应用到集群:
kubectl apply -f opa-server.yaml
等待 OPA Pod 就绪。就绪后,可以在集群内通过 http://opa.aic.svc.cluster.local:8181 访问 OPA 服务器。要从集群外向其推送策略,请设置端口转发:
kubectl port-forward -n aic svc/opa 8181:8181 &
实现基本策略
以下示例在 OPA 中实现了一个基本的授权策略,仅允许 GET 请求。
创建一个仅允许 HTTP GET 请求的 OPA 策略:
curl "http://127.0.0.1:8181/v1/policies/getonly" -X PUT \
-H "Content-Type: text/plain" \
-d '
package getonly
default allow = false
allow if {
input.request.method == "GET"
}'
创建一个使用 opa 插件的路由:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "opa-route",
"uri": "/anything",
"plugins": {
"opa": {
"host": "http://192.168.2.104:8181",
"policy": "getonly"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
❶ 配置 OPA 服务器地址。请替换为你的 IP 地址。
❷ 将授权策略设置为 getonly。
services:
- name: opa-service
routes:
- name: opa-route
uris:
- /anything
plugins:
opa:
host: "http://192.168.2.104:8181"
policy: getonly
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
❶ 配置 OPA 服务器地址。请替换为你的 IP 地址。
❷ 将授权策略设置为 getonly。
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
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
❶ 配置 OPA 服务器地址。
❷ 将授权策略设置为 getonly。
将配置应用到集群:
kubectl apply -f 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
❶ 配置 OPA 服务器地址。
❷ 将授权策略设置为 getonly。
将配置应用到集群:
kubectl apply -f opa-ic.yaml
要验证该策略,向路由发送一个 GET 请求:
curl -i "http://127.0.0.1:9080/anything"
你应该收到 HTTP/1.1 200 OK 响应。
向路由发送另一个使用 PUT 的请求:
curl -i "http://127.0.0.1:9080/anything" -X PUT
你应该收到 HTTP/1.1 403 Forbidden 响应。
理解数据格式
以下示例有助于你理解 APISIX 推送到 OPA 以支持授权逻辑编写的数据及其格式。该示例沿 用上一个示例中的策略和路由。
假设你的 OPA 服务器已使用 --log-level debug 启动,并且你已完成上一个示例中的验证步骤,向示例路由发送了请求。
查看 OPA 服务器日志。你应该看到类似以下的条目:
{
"client_addr": "192.168.215.1:58467",
"level": "info",
"msg": "Received request.",
"req_body": "{\"input\":{\"type\":\"http\",\"var\":{\"server_port\":\"9080\",\"timestamp\":1752400020,\"server_addr\":\"192.168.107.3\",\"remote_port\":\"58544\",\"remote_addr\":\"192.168.107.1\"},\"request\":{\"host\":\"127.0.0.1\",\"path\":\"/anything\",\"headers\":{\"host\":\"127.0.0.1:9080\",\"accept\":\"*/*\",\"user-agent\":\"curl/8.6.0\"},\"query\":{},\"port\":9080,\"scheme\":\"http\",\"method\":\"PUT\"}}}",
"req_id": 12,
"req_method": "POST",
"req_params": {},
"req_path": "/v1/data/getonly",
"time": "2025-07-14T15:07:00Z"
}
其中 req_body 显示了 APISIX 推送的数据:
{
"input": {
"type": "http",
"var": {
"server_port": "9080",
"timestamp": 1752400020,
"server_addr": "192.168.107.3",
"remote_port": "58544",
"remote_addr": "192.168.107.1"
},
"request": {
"host": "127.0.0.1",
"path": "/anything",
"headers": {
"host": "127.0.0.1:9080",
"accept": "*/*",
"user-agent": "curl/8.6.0"
},
"query": {},
"port": 9080,
"scheme": "http",
"method": "PUT"
}
}
}
现在,更新之前创建的路由上的插件以包含路由信息:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes/opa-route" -X PATCH \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"plugins": {
"opa": {
"with_route": true
}
}
}'
更新 adc.yaml,添加 with_route: true:
services:
- name: opa-service
routes:
- name: opa-route
uris:
- /anything
plugins:
opa:
host: "http://192.168.2.104:8181"
policy: getonly
with_route: true
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
更新 opa-ic.yaml,添加 with_route: true:
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
将更新后的配置应用到集群:
kubectl apply -f opa-ic.yaml
更新 opa-ic.yaml,添加 with_route: true:
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
将更新后的配置应用到集群:
kubectl apply -f opa-ic.yaml
向路由发送请求:
curl -i "http://127.0.0.1:9080/anything"
在 OPA 服务器日志中,你应该看到一个新的条目:
{
"client_addr": "192.168.215.1:43706",
"level": "info",
"msg": "Received request.",
"req_body": "{\"input\":{\"route\":{\"id\":\"opa-route\",\"uri\":\"/anything\",\"update_time\":1752395758,\"plugins\":{\"opa\":{\"keepalive_pool\":5,\"keepalive_timeout\":60000,\"host\":\"http://172.17.1.196:8181\",\"ssl_verify\":true,\"with_route\":true,\"with_service\":false,\"with_consumer\":false,\"timeout\":3000,\"keepalive\":true,\"policy\":\"getonly\"}},\"priority\":0,\"status\":1,\"create_time\":1752393063},\"type\":\"http\",\"var\":{\"server_port\":\"9080\",\"timestamp\":1752396233,\"server_addr\":\"192.168.107.3\",\"remote_port\":\"47838\",\"remote_addr\":\"192.168.107.1\"},\"request\":{\"host\":\"127.0.0.1\",\"path\":\"/anything\",\"headers\":{\"host\":\"127.0.0.1:9080\",\"accept\":\"*/*\",\"user-agent\":\"curl/8.6.0\"},\"query\":{},\"port\":9080,\"scheme\":\"http\",\"method\":\"GET\"}}}",
"req_id": 14,
"req_method": "POST",
"req_params": {},
"req_path": "/v1/data/getonly",
"time": "2025-07-13T08:43:53Z"
}
req_body 现在包含了路由信息:
{
"input": {
"route": {
"id": "opa-route",
"uri": "/anything",
"update_time": 1752395758,
"plugins": {
"opa": {
"keepalive_pool": 5,
"keepalive_timeout": 60000,
"host": "http://172.17.1.196:8181",
"ssl_verify": true,
"with_route": true,
"with_service": false,
"with_consumer": false,
"timeout": 3000,
"keepalive": true,
"policy": "getonly"
}
},
"priority": 0,
"status": 1,
"create_time": 1752393063
},
"type": "http",
"var": {
"server_port": "9080",
"timestamp": 1752396233,
"server_addr": "192.168.107.3",
"remote_port": "47838",
"remote_addr": "192.168.107.1"
},
"request": {
"host": "127.0.0.1",
"path": "/anything",
"headers": {
"host": "127.0.0.1:9080",
"accept": "*/*",
"user-agent": "curl/8.6.0"
},
"query": {},
"port": 9080,
"scheme": "http",
"method": "GET"
}
}
}
返回自定义响应
以下示例展示了如何在请求未获授权时返回自定义响应代码和消息。
创建一个仅允许 HTTP GET 请求并在未获授权时返回 302 和自定义消息的 OPA 策略:
curl "http://127.0.0.1:8181/v1/policies/customresp" -X PUT \
-H "Content-Type: text/plain" \
-d '
package customresp
default allow = false
allow if {
input.request.method == "GET"
}
reason := "The resource has temporarily moved. Please follow the new URL." if {
not allow
}
headers := {
"Location": "http://example.com/auth"
} if {
not allow
}
status_code := 302 if {
not allow
}
'
创建一个使用 opa 插件的路由:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "opa-route",
"uri": "/anything",
"plugins": {
"opa": {
"host": "http://192.168.2.104:8181",
"policy": "customresp"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
❶ 配置 OPA 服务器地址。请替换为你的 IP 地址。
❷ 将授权策略设置为 customresp。
services:
- name: opa-service
routes:
- name: opa-route
uris:
- /anything
plugins:
opa:
host: "http://192.168.2.104:8181"
policy: customresp
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
❶ 配置 OPA 服务器地址。请替换为你的 IP 地址。
❷ 将授权策略设置为 customresp。
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
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
❶ 配置 OPA 服务器地址。
❷ 将授权策略设置为 customresp。
将配置应用到集群:
kubectl apply -f 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
❶ 配置 OPA 服务器地址。
❷ 将授权策略设置为 customresp。
将配置应用到集群:
kubectl apply -f opa-ic.yaml
向路由发送一个 GET 请求:
curl -i "http://127.0.0.1:9080/anything"
你应该收到 HTTP/1.1 200 OK 响应。
向路由发送一个 POST 请求:
curl -i "http://127.0.0.1:9080/anything" -X POST
你应该收到 HTTP/1.1 302 Moved Temporarily 响应:
HTTP/1.1 302 Moved Temporarily
...
Location: http://example.com/auth
The resource has temporarily moved. Please follow the new URL.
实现 RBAC
以下示例展示了如何使用 jwt-auth 和 opa 插件实现身份认证和 RBAC。你将实现以下 RBAC 逻辑:
user角色只能读取上游资源。admin角色可以读取和写入上游资源。
为两个示例消费者创 建 RBAC 的 OPA 策略,其中 john 拥有 user 角色,jane 拥有 admin 角色:
curl "http://127.0.0.1:8181/v1/policies/rbac" -X PUT \
-H "Content-Type: text/plain" \
-d '
package rbac
# 为用户分配角色
user_roles := {
"john": ["user"],
"jane": ["admin"]
}
# 将权限映射到 HTTP 方法
permission_methods := {
"read": "GET",
"write": "POST"
}
# 分配角色权限
role_permissions := {
"user": ["read"],
"admin": ["read", "write"]
}
# 获取 JWT 授权令牌
bearer_token := t if {
t := input.request.headers.authorization
}
# 解码令牌以获取角色和权限
token := {"payload": payload} if {
[_, payload, _] := io.jwt.decode(bearer_token)
}
# 将权限规范化为列表
normalized_permissions := ps if {
ps := token.payload.permission
not is_string(ps)
}
normalized_permissions := [ps] if {
ps := token.payload.permission
is_string(ps)
}
# 实现 RBAC 逻辑
default result := {"allow": false}
result := {"allow": true} if {
# 查找用户的角色列表
roles := user_roles[input.consumer.username]
# 遍历列表中的每个角 色
r := roles[_]
# 查找角色的权限列表
permissions := role_permissions[r]
# 遍历每项权限
p := permissions[_]
# 检查权限是否与请求方法匹配
permission_methods[p] == input.request.method
# 检查规范化后的权限是否包含该权限
p in normalized_permissions
}
'
在 APISIX 中创建两个消费者 john 和 jane,并配置其 jwt-auth 凭证:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/consumers" \
-X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"username": "john"
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers" \
-X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"username": "jane"
}'
使用默认算法 HS256 为消费者配置 jwt-auth 凭据:
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "john-key",
"secret": "john-hs256-secret-that-is-very-long"
}
}
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jane-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jane-key",
"secret": "jane-hs256-secret-that-is-very-long"
}
}
}'
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
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
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
将配置应用到集群:
kubectl apply -f opa-consumers-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: john
spec:
ingressClassName: apisix
authParameter:
jwtAuth:
value:
key: john-key
secret: john-hs256-secret-that-is-very-long
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: jane
spec:
ingressClassName: apisix
authParameter:
jwtAuth:
value:
key: jane-key
secret: jane-hs256-secret-that-is-very-long
将配置应用到集群:
kubectl apply -f opa-consumers-ic.yaml
创建一个路由并配置 jwt-auth 和 opa 插件,如下所示:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "opa-route",
"methods": ["GET", "POST"],
"uris": ["/get","/post"],
"plugins": {
"jwt-auth": {},
"opa": {
"host": "http://192.168.2.104:8181",
"policy": "rbac/result",
"with_consumer": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
❶ 在路由上启用 jwt-auth 插件。
❷ 配置 OPA 服务器地址。请替换为你的 IP 地址。
❸ 将授权策略设置为 rbac/result。
❹ 将 with_consumer 设置为 true,以发送消费者信息。
更新 adc.yaml,添加启用了 jwt-auth 和 opa 插件的路由:
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://192.168.2.104:8181"
policy: rbac/result
with_consumer: true
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
❶ 在路由上启用 jwt-auth 插件。
❷ 配置 OPA 服务器地址。请替换为你的 IP 地址。
❸ 将授权策略设置为 rbac/result。
❹ 将 with_consumer 设置为 true,以发送消费者信息。
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
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/result
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
❶ 配置 OPA 服务器地址。
❷ 将授权策略设置为 rbac/result。
❸ 将 with_consumer 设置为 true,以发送消费者信息。
将配置应用到集群:
kubectl apply -f opa-route-ic.yaml
使用 Ingress Controller 时,APISIX 会为消费者名称添加 Kubernetes 命名空间前缀。例如,aic 命名空间中名为 john 的消费者会变为 aic_john。请更新 OPA RBAC 策略以使用带前缀的名称:
curl "http://127.0.0.1:8181/v1/policies/rbac" -X PUT \
-H "Content-Type: text/plain" \
-d '
package rbac
# 为用户分配角 色
user_roles := {
"aic_john": ["user"],
"aic_jane": ["admin"]
}
# 将权限映射到 HTTP 方法
permission_methods := {
"read": "GET",
"write": "POST"
}
# 分配角色权限
role_permissions := {
"user": ["read"],
"admin": ["read", "write"]
}
# 获取 JWT 授权令牌
bearer_token := t if {
t := input.request.headers.authorization
}
# 解码令牌以获取角色和权限
token := {"payload": payload} if {
[_, payload, _] := io.jwt.decode(bearer_token)
}
# 将权限规范化为列表
normalized_permissions := ps if {
ps := token.payload.permission
not is_string(ps)
}
normalized_permissions := [ps] if {
ps := token.payload.permission
is_string(ps)
}
# 实现 RBAC 逻辑
default result := {"allow": false}
result := {"allow": true} if {
# 查找用户的角色列表
roles := user_roles[input.consumer.username]
# 遍历列表中的每个角色
r := roles[_]
# 查找角色的权限列表
permissions := role_permissions[r]
# 遍历每项权限
p := permissions[_]
# 检查权限是否与请求方法匹配
permission_methods[p] == input.request.method
# 检查规范化后的权限是否包含该权限
p in normalized_permissions
}
'
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-rbac-route
spec:
ingressClassName: apisix
http:
- name: get-route
match:
methods:
- GET
paths:
- /get
upstreams:
- name: httpbin-external-domain
plugins:
- name: jwt-auth
enable: true
config:
_meta:
disable: false
- name: opa
enable: true
config:
host: "http://opa.aic.svc.cluster.local:8181"
policy: rbac/result
with_consumer: true
- name: post-route
match:
methods:
- POST
paths:
- /post
upstreams:
- name: httpbin-external-domain
plugins:
- name: jwt-auth
enable: true
config:
_meta:
disable: false
- name: opa
enable: true
config:
host: "http://opa.aic.svc.cluster.local:8181"
policy: rbac/result
with_consumer: true
将配置应用到集群:
kubectl apply -f opa-route-ic.yaml
使用 Ingress Controller 时,APISIX 会为消费者名称添加 Kubernetes 命名空间前缀。例如,aic 命名空间中名为 john 的消费者会变为 aic_john。请更新 OPA RBAC 策略以使用带前缀的名称:
curl "http://127.0.0.1:8181/v1/policies/rbac" -X PUT \
-H "Content-Type: text/plain" \
-d '
package rbac
user_roles := {
"aic_john": ["user"],
"aic_jane": ["admin"]
}
permission_methods := {
"read": "GET",
"write": "POST"
}
role_permissions := {
"user": ["read"],
"admin": ["read", "write"]
}
bearer_token := t if {
t := input.request.headers.authorization
}
token := {"payload": payload} if {
[_, payload, _] := io.jwt.decode(bearer_token)
}
normalized_permissions := ps if {
ps := token.payload.permission
not is_string(ps)
}
normalized_permissions := [ps] if {
ps := token.payload.permission
is_string(ps)
}
default result := {"allow": false}
result := {"allow": true} if {
roles := user_roles[input.consumer.username]
r := roles[_]
permissions := role_permissions[r]
p := permissions[_]
permission_methods[p] == input.request.method
p in normalized_permissions
}
'
验证 john
要为 john 颁发 JWT,你可以使用 JWT.io 的 JWT 编码器或其他工具。如果你使用 JWT.io 的 JWT 编码器,请执行以下操作:
- 将算法填写为
HS256。 - 将 Valid secret 部分中的密钥更新为
john-hs256-secret-that-is-very-long。 - 更新 payload,角色为
user,权限为read,消费者密钥为john-key;以及exp或nbf为 UNIX 时间戳。
如果你使用的是 API7 企业版,则 exp 或 nbf 的要求不是强制性的。你可以选择包含这些声明,并使用 claims_to_verify 参数配置要验证的声明。
你的 payload 应该类似于以下内容:
{
"role": "user",
"permission": "read",
"key": "john-key",
"nbf": 1729132271
}
复制生成的 JWT 并保存到变量:
export john_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsInBlcm1pc3Npb24iOiJyZWFkIiwia2V5Ijoiam9obi1rZXkiLCJuYmYiOjE3MjkxMzIyNzF9.rAHMTQfnnGFnKYc3am_lpE9pZ9E8EaOT_NBQ5Ss8pk4
使用 john 的 JWT 向路由发送 GET 请求:
curl -i "http://127.0.0.1:9080/get" -H "Authorization: ${john_jwt_token}"
你应该收到 HTTP/1.1 200 OK 响应。
使用相同的 JWT 向路由发送 POST 请求:
curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: ${john_jwt_token}"
你应该收到 HTTP/1.1 403 Forbidden 响应。
验证 jane
同样,要为 jane 颁发 JWT,你可以使用 JWT.io 的 JWT 编码器或其他工具。如果你使用 JWT.io 的 JWT 编码器,请执行以下操作:
- 将算法填写为
HS256。 - 将 Valid secret 部分中的密钥更新为
jane-hs256-secret-that-is-very-long。 - 更新 payload,角色为
admin,权限为["read","write"],消费者密钥为jane-key;以及exp或nbf为 UNIX 时间戳。
如果你使用的是 API7 企业版,则 exp 或 nbf 的要求不是强制性的。你可以选择包含这些声明,并使用 claims_to_verify 参数配置要验证的声明。
你的 payload 应该类似于以下内容:
{
"role": "admin",
"permission": ["read","write"],
"key": "jane-key",
"nbf": 1729132271
}
复制生成的 JWT 并保存到变量:
export jane_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJwZXJtaXNzaW9uIjpbInJlYWQiLCJ3cml0ZSJdLCJrZXkiOiJqYW5lLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.meZ-AaGHUPwN_GvVOE3IkKuAJ1wqlCguaXf3gm3Ww8s
使用 jane 的 JWT 向路由发送 GET 请求:
curl -i "http://127.0.0.1:9080/get" -H "Authorization: ${jane_jwt_token}"
你应该收到 HTTP/1.1 200 OK 响应。
使用相同的 JWT 向路由发送 POST 请求:
curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: ${jane_jwt_token}"
你应该也收到 HTTP/1.1 200 OK 响应。
如果你设置了 --log-level debug,要检查授权决策是否来自 OPA,你应该在 OPA 服务器中观察到以下日志:
{
"result":{
"allow": true,
"bearer_token": "eyJ...",
...
}
}