系列专栏声明:比较流水,主要是写一些踩坑的点,和实践中与文档差距较大的地方的思考。这个专栏的典型特征可能是 次佳实践
,争取能在大量的最佳实践中生存。
Open Policy Agent 吸引我的点在于 Policy as Code
,所以看上去整个云原生概念吸引我的点就在于 X as Code
或者更准确的说是 X as Declarative DSL
。
TODO:会有 JWT
的部分,但是第一稿来不及写了,可以收藏以后来看。
在 PBAC: Policy Base Authorization Control
中有三个核心的概念,PIP
、PDP
、PEP
。
OPA
是一个 PDP
,即 规则引擎
。审计方在这里用 自然语言的 DSL
描述权限控制规则,OPA
执行这些规则并返回 allow
或 deny
。早期的权限规则是单向的,只能从需求被翻译成面向过程的代码,而很难从已经完成的代码反推出实际被执行的规则,也很难对规则进行更新或迁移。使用 DSL
是质变的关键,它使得需求和代码可以双向翻译了。
一、请求流向
这是上面概念图落地后的实例图,有两个比较明显的简化点是:
- 本文实际上只是第一道网关,只做简单的准入控制,不做行、列粒度的数据权限控制。所以和
Envoy(PEP)
配套的OPA(PDP)
没有去请求额外的PIP
获得更多属性,而是仅使用Envoy
带来的属性包括JWT
。因此这里通常只会携带当前用户的id
和关键role
。如果要做细粒度的权限控制,应该在Service
处再加一套PEP + OPA(PDP)
。 OPA
要求所有的Policy
都在内存里。它提供了 restful api 用来更新内存中的 policy,这是大部分入门介绍的方式,但看上去没有实际意义。另外还提供了从数据源实时同步的方式,这个应该才是 正确的方式,不在本文中详细描述了。本文在启动时从文件中加载。
docker-compose.yml
app:
container_name: app
image: hub.docker.com/example/app:latest
networks:
- traefik
# 这里不需要配置 traefik 相关的 labels,因为实际对外暴露的是 envoy
app-envoy:
container_name: app-envoy
image: envoyproxy/envoy-alpine:v1.17.4
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`api.example.com`)"
# 省略其它必须的 traefik labels
volumes:
- "/path/to/envoy.yaml:/etc/envoy/envoy.yaml"
app-opa:
container_name: app-opa
image: openpolicyagent/opa:0.35.0-envoy-5-rootless
networks:
- traefik
volumes:
- "/path/to/policy.rego:/config/policy.rego"
command:
- run
- --log-level=debug
- --set=decision_logs.console=true
- --skip-version-check # 这里关闭向 open telemetry 上报埋点
- --server
- --set=plugins.envoy_ext_authz_grpc.addr=:9191
- --set=plugins.envoy_ext_authz_grpc.path=com/example/app/opa/allow
- /config/policy.rego
几个踩坑点:
opa
使用的是内置envoy plugin
的 版本
envoy.yaml
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 80
filter_chains:
- filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
# 省略
route_config:
# 省略
http_filters:
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport_api_version: V3
grpc_service:
envoy_grpc:
cluster_name: app-opa
timeout: 0.5s
# 省略
- name: envoy.filters.http.cors
- name: envoy.filters.http.router
typed_config: {}
clusters:
- name: app
connect_timeout: 0.25s
type: strict_dns
dns_lookup_family: V4_ONLY
lb_policy: round_robin
load_assignment:
cluster_name: app
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: app
port_value: 8080
- name: app-opa
connect_timeout: 0.25s
type: strict_dns
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
load_assignment:
cluster_name: app-opa
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: app-opa
port_value: 9191
几个踩坑点:
envoy 1.17
开始默认使用v3
,所以升级上来的不少配置语法要改,文档比较难找,没有手把手教程,但实际改好以后平滑升级- 模拟在一个
Pod
内的场景,cluster
的定义用的是在 docker 内的 name,没有去 traefik 再兜一圈进来,所以也不需要为 app 配置 traefik labels 根据 host 做路由
policy.rego
package com.example.app.opa
import input.attributes.request.http as http_request
default allow = {
"allowed": false,
"headers": { "x-envoy-opa": "deny", "content-type": "application/json" },
"body": "{ \"code\": -1, \"message\": \"deny\" }",
"http_status": 403
}
allow = response {
http_request.method == "OPTIONS"
response := {
"allowed": true,
"headers": { "x-envoy-opa": "allow" }
}
}
allow = response {
allowed_methods := ["GET", "POST", "PUT", "DELETE"]
http_request.method == allowed_methods[_]
http_request.headers["Authorzation"] == "Basic BasicAccessToken"
glob.match("/app/open-api/*", [], http_request.path)
response := {
"allowed": true,
"headers": { "x-envoy-opa": "allow" }
}
}
几个踩坑点:
- 挺顺利的,
Rego
的语法值得详解一下,后面补充 - 使用的是 opa-envoy-plugin,所以有些语法不是 opa 自带的,而是 envoy-plugin 集成进来的,比如
glob.match
- 等有空研究 opa 调实时接口更新规则,再补充一下
参考资料:
- Envoy Getting Started
- Getting Started With Rego,关于 Rego 的一些反直觉的语法,但你可能需要适应这些语法,在 DSL 里写代码确实是全新的体验
- Policy Language
- https://github.com/NewbMiao/o...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。