OPA 权威手册:Rego、运行时、Bundle 与决策日志
OPA 的 HTTP 200 只表示查询被正常处理,不表示业务动作获得授权。查询路径从 data.orders.authz.decision 改为 data.orders.authz.allow 后,旧调用端可能收到没有 result 的合法响应;适配器若把空值转成默认布尔值,就会绕过策略。接入合同必须明确决定路径、返回类型、undefined、运行错误和最终 PEP 动作。
另一个现场中,同一策略在开发机允许,在共享环境持续拒绝。两边源码提交一致,排查几小时后才发现共享实例加载了旧 data,开发机则通过 -d . 同时读入了新角色映射;调用端日志只记了 allow,没有记录 decision path、policy revision、data revision 和脱敏 input。没有这些证据,“策略一样”只是对文件的判断,不是对一次求值的判断。
OPA 只负责算出决定
Open Policy Agent 是通用策略引擎。PEP 把主体、动作、资源和环境整理为 JSON input,OPA 用 Rego policy 与 base data 求值,并返回 JSON 决定。决定可以是布尔值,也可以是包含原因、过滤条件或补丁的对象;OPA 不替应用认证主体,不执行退款,也不自动决定超时后开放还是关闭。
subject + action + resource + context
|
v
PEP 构造 input
|
v
OPA: policy + input + data
|
v
decision / undefined / error -> PEP 执行业务动作本篇实验使用 orders 服务。只有资源 owner 能读取订单,support 角色在工单存在时也能读取;任何未知动作、缺少主体、错误资源类型和未定义结果都拒绝。这个模型刻意保留 data 依赖,便于观察 input 与 data 的差异。
安装时固定可追溯版本
OPA 官方提供 release 二进制、Homebrew 与 Docker 镜像,入口见 OPA 文档首页。团队基线应锁定审核过的 OPA 1.x 版本并校验摘要,不把 latest 或 edge 作为可重复构建输入。安装完成后先记录实际版本:
$version = "<opa-version>"
Invoke-WebRequest `
-Uri "https://openpolicyagent.org/downloads/$version/opa_windows_amd64.exe" `
-OutFile ".\opa.exe"
Invoke-WebRequest `
-Uri "https://openpolicyagent.org/downloads/$version/opa_windows_amd64.exe.sha256" `
-OutFile ".\opa.exe.sha256"
Get-FileHash .\opa.exe -Algorithm SHA256
.\opa.exe version下载页提供的摘要必须与 Get-FileHash 输出匹配;不匹配时停止使用并重新确认下载源、代理缓存和目标架构。macOS 可用 brew install opa 快速进入本地学习,但团队流水线仍要记录 opa version 与包来源。Linux 可下载对应架构的静态二进制,赋予执行权限后放入受管理的工具目录。
Docker 适合隔离验证:
docker pull openpolicyagent/opa:<opa-version>
docker image inspect openpolicyagent/opa:<opa-version> --format '{{json .RepoDigests}}'
docker run --rm openpolicyagent/opa:<opa-version> version正式镜像锁定 digest,升级通过显式 PR 完成。OPA 1.x server 默认监听 localhost:8181;容器若要从宿主访问,需要显式 --addr=0.0.0.0:8181,但扩大监听必须与 TLS、客户端身份、API 授权和网络隔离一起实施,不能只为“端口通”而裸露管理 API。Docker 部署说明给出了镜像入口与启动方式。
Rego v1 用规则描述结果
在项目中新建 policy/orders/authz.rego:
package orders.authz
default decision := {
"allow": false,
"reason": "default_deny",
}
decision := {
"allow": true,
"reason": "owner",
} if {
valid_request
input.subject.id == input.resource.owner_id
}
decision := {
"allow": true,
"reason": "support_ticket",
} if {
valid_request
"support" in data.roles[input.subject.id]
data.tickets[input.resource.id].open == true
}
valid_request if {
input.action == "read"
input.resource.type == "order"
is_string(input.subject.id)
is_string(input.resource.id)
is_string(input.resource.owner_id)
}OPA 1.x 默认采用 Rego v1 语义。规则体使用 if;集合增量规则使用 contains。import rego.v1 在 1.x 是 no-op,不是打开新语法的开关。旧写法 allow { ... } 在默认模式下应被解析或严格检查拒绝,兼容参数只用于迁移,不应成为新策略模板。Rego 语言说明与 1.0 升级指南应作为升级前核对入口。
这里选择结构化 decision,让 PEP 可以记录稳定 reason。两条 complete rule 若对同一输入同时成立且返回不同对象,会产生 eval_conflict_error。示例通过 owner 与 support 条件尽量避免重叠;生产策略还应为所有可能重叠的条件写冲突测试,不能期待 OPA 按文件顺序选择一个结果。
input 与 data 是两种生命周期
新建 data/roles.json:
{
"roles": {
"alice": ["customer"],
"sam": ["support"]
},
"tickets": {
"order-100": { "open": true }
}
}input 是一次求值的请求上下文,只在本次查询中存在。主体标识应由已完成认证的 PEP 写入,不能直接信任客户端随请求提交的 subject。data 包含加载到 OPA 的 base documents,以及由 Rego package 形成的 virtual documents;示例中的角色和工单状态是 base data,data.orders.authz.decision 是 virtual document。
不要用 data 充当业务数据库。角色映射小、更新可批量传播时适合随 bundle 或 SDK 更新;工单状态若高频变化、必须读后立即生效,把它复制到每个 OPA 实例会产生陈旧窗口。可选择由 PEP 先读取权威状态并放入 input,或者使用集中决策服务统一访问。直接在 Rego 中调用外部 HTTP 会把 DNS、TLS、超时、重试和外部可用性塞进授权热路径,只有在明确预算和缓存语义后才采用。
CLI 跑通第一个正向决定
创建 fixtures/owner-read.json:
{
"subject": { "id": "alice" },
"action": "read",
"resource": {
"type": "order",
"id": "order-100",
"owner_id": "alice"
},
"context": { "request_id": "req-owner-read" }
}格式化、严格检查并求值:
opa fmt --fail policy/
opa check --strict policy/
opa eval \
-d policy/ \
-d data/roles.json \
-i fixtures/owner-read.json \
'data.orders.authz.decision'预期 result[0].expressions[0].value 中出现 allow: true 和 reason: owner。项目脚本通常应固定 --format=json 后解析值,分别判断“查询执行失败、undefined、allow=false、allow=true”。--fail-defined 的语义是在查询有结果时返回非零,适合“禁止出现结果”的检查,不适合作为这里的成功退出契约;不要只依赖 shell 退出码推断业务授权。
更直接的自动化命令可以输出原始值:
opa eval --format=raw \
-d policy/ -d data/roles.json \
-i fixtures/owner-read.json \
'data.orders.authz.decision.allow'它应输出 true。若输出为空,先检查 package、decision path 和规则是否 undefined;若命令报类型或冲突错误,保存错误码与规则位置,不要把它转换为允许。
反向实验固定默认拒绝
创建错误 owner:
{
"subject": { "id": "mallory" },
"action": "read",
"resource": {
"type": "order",
"id": "order-100",
"owner_id": "alice"
}
}用同一命令求值时,预期是 allow: false 与 reason: default_deny。随后按顺序做四个破坏实验:
删除 subject.id,应稳定拒绝,证明缺字段不会开放。把 action 写成 Read,应稳定拒绝,证明动作枚举大小写不被猜测修正。临时删除 default decision,未知输入将得到 undefined,PEP 仍必须拒绝。
增加另一条同样匹配 owner、但返回 allow: false 的 complete rule,应出现 conflict,而不是得到不确定结果。
为它们写 Rego 测试:
package orders.authz_test
import data.orders.authz
test_owner_allowed if {
result := authz.decision with input as {
"subject": {"id": "alice"},
"action": "read",
"resource": {"type": "order", "id": "order-100", "owner_id": "alice"},
}
result.allow == true
result.reason == "owner"
}
test_wrong_owner_denied if {
result := authz.decision with input as {
"subject": {"id": "mallory"},
"action": "read",
"resource": {"type": "order", "id": "order-100", "owner_id": "alice"},
}
result.allow == false
}
test_missing_subject_denied if {
result := authz.decision with input as {
"action": "read",
"resource": {"type": "order", "id": "order-100", "owner_id": "alice"},
}
result.allow == false
}执行 opa test --coverage --format=json policy/ data/roles.json。成功特征是三个测试通过且 JSON 中有预期测试名;coverage 用于发现未经过的规则,不等于语义正确。再暂时把拒绝断言改成允许,确认测试确实失败,然后恢复。这一步能识别“测试文件未被发现”造成的假绿。
HTTP 服务要区分状态码与决定
本地启动:
opa run --server --log-format=json policy/ data/roles.json
curl -sS http://localhost:8181/health默认只监听 localhost。健康端点成功只说明进程响应,不说明目标策略已加载、调用路径正确或授权结果符合预期。发送决策请求:
curl -sS \
-H 'Content-Type: application/json' \
--data @fixtures/owner-read.json \
http://localhost:8181/v1/data/orders/authz/decisionREST Data API 期望请求体通常是 {"input": ...},直接把 fixture 作为顶层 body 会导致 input 缺失。更稳妥的文件应写成:
{
"input": {
"subject": { "id": "alice" },
"action": "read",
"resource": { "type": "order", "id": "order-100", "owner_id": "alice" }
}
}再发送该文件,响应 result.allow 应为 true。错误路径可能仍返回 200 但没有 result,因为查询本身有效而文档未定义;语法、编译或某些 API 错误则使用不同状态码。适配器必须同时检查 HTTP 状态、响应能否解析、result 是否存在、类型是否正确和 allow 是否严格为 true,其余情况统一进入明确的拒绝或业务降级分支。
服务化时不要让业务调用身份拥有写入 /v1/policies 或 /v1/data 的能力。OPA 默认 authentication 与 authorization 都是 off;一旦对远程网络监听,应按 OPA Security 配置 TLS、客户端身份、system.authz 和网络边界。TLS authentication 不会自动关闭明文 listener,实际监听 socket 也要检查。
Go SDK 把求值嵌入应用
Go 新项目使用带 /v1/ 的包路径。rego 包适合显式准备查询,sdk 包适合消费动态策略配置。下面展示 rego 的关键结构:
package authz
import (
"context"
"fmt"
"github.com/open-policy-agent/opa/v1/rego"
)
func Evaluate(ctx context.Context, module string, input any) (bool, error) {
query, err := rego.New(
rego.Query("data.orders.authz.decision"),
rego.Module("orders.authz.rego", module),
).PrepareForEval(ctx)
if err != nil {
return false, fmt.Errorf("prepare policy: %w", err)
}
results, err := query.Eval(ctx, rego.EvalInput(input))
if err != nil {
return false, fmt.Errorf("evaluate policy: %w", err)
}
if len(results) != 1 || len(results[0].Expressions) != 1 {
return false, fmt.Errorf("undefined or ambiguous decision")
}
decision, ok := results[0].Expressions[0].Value.(map[string]any)
if !ok {
return false, fmt.Errorf("unexpected decision type")
}
allow, ok := decision["allow"].(bool)
return ok && allow, nil
}准备查询应在启动或策略更新时完成,不要每个请求重复 parse/compile。真实项目还要注入 base data、使用带 deadline 的 context,并把错误映射为稳定业务结果。不要记录完整 input;记录 request ID、策略版本、资源类型、动作、reason 和耗时即可,主体与资源标识按数据分级做散列或遮盖。
旧的无 /v1/ Go 包在 OPA 1.x 生命周期中属于弃用路径,不等于立即删除。迁移时先换 import、编译和测试,再升级运行时;不要在同一个变更中同时重写策略语义、SDK 适配和失败策略。
Wasm 适合固定入口的本地执行
把决策入口编译进 bundle:
opa build \
-t wasm \
-e orders/authz/decision \
-o dist/orders-authz.tar.gz \
policy/orders/authz.rego
tar -tf dist/orders-authz.tar.gz产物应包含 Wasm 模块、manifest 与策略相关文件。entrypoint 使用斜杠路径,不是 REST URL。宿主按 OPA Wasm 文档加载模块、初始化 heap、设置 data 和 input、执行 entrypoint 并读取 JSON 结果。
Wasm 不是“在浏览器里启动 OPA server”。它没有 HTTP、bundle 拉取、状态上报或决策日志能力;这些都由宿主补齐。部分 built-ins 不能原生编译,需要宿主实现,升级前必须检查目标策略实际使用的 built-ins。对相同 fixture,应将 CLI 与 Wasm 结果做 golden comparison,尤其覆盖 undefined、类型错误和非 ASCII 数据,防止宿主适配层改变语义。
接进订单项目需要一层稳定适配器
业务代码不应散落 OPA URL 与 Rego path。建立 AuthorizationClient,输入使用有版本的 DTO,输出使用稳定枚举:
Decision = ALLOW | DENY | UNAVAILABLE | INVALID
reason = owner | support_ticket | default_deny | policy_error处理顺序是:从认证中间件取可信主体;从业务查询取资源 owner 与状态;构造最小 input;设置短于业务 deadline 的求值超时;严格解析结果;记录脱敏证据;只有 ALLOW 执行动作。DENY 返回业务拒绝,INVALID 表示契约或策略缺陷,UNAVAILABLE 进入事先选择的失败策略。
项目仓库可以这样组织:
services/orders/
internal/authz/client.go
internal/authz/contract.go
policy/orders/authz.rego
policy/orders/authz_test.rego
policy/data/roles.json
policy/fixtures/*.json
scripts/test-policy.shCI 先运行 opa fmt --fail、opa check --strict 和 opa test,再运行应用适配器测试。适配器测试要模拟 HTTP 200 无 result、非 JSON、超时、500、错误类型与 allow=false;否则策略单测再完整,也覆盖不到最危险的执行断点。
发布新策略时先记录不可变版本,在影子路径对同一批脱敏 input 同时求值新旧策略,只记录差异而不改变业务动作。对 allow→deny 与 deny→allow 分别评审;前者可能中断业务,后者可能扩大权限。达到约定样本量、错误率与延迟门槛后再分批切换,保留快速回读旧版本的能力。
排障按输入、版本、求值、执行四层推进
200 但没有 result
先用同一 body 查询确切 decision path,再执行 opa eval。常见原因是 package/path 拼错、规则未定义或请求体没有 input 包装。修复后必须同时看到 result 存在、类型为对象、allow 为布尔值,并确认 PEP 最终动作一致。
本地与共享环境结果不同
比较 OPA 精确版本、Rego 兼容参数、加载的 policy/data revision、完整 decision path 和规范化后的脱敏 input。不要只比较 Git 提交。将现场 input 保存为去敏 fixture,在两边用相同二进制与文件重放,逐项移除 data 差异。
规则突然 undefined
运行 opa check --strict 与 opa eval --strict-builtin-errors。Rego 某些 built-in 错误在默认求值中可能使表达式不成立,最终表现为 undefined;严格模式有助于把类型错误变成显式失败。PEP 仍要把 undefined 当拒绝,不能依赖调试参数保护生产。
CPU 与延迟上升
使用 opa eval --metrics、profiler 或 SDK metrics 找到高成本表达式,观察 input/data 大小、数组嵌套遍历、集合构造、字符串处理和外部 built-in。先减少重复扫描、预先索引 data、缩小 input,再评估缓存与运行形态。只增加副本可能把同一份大 data 复制更多次,并放大更新成本。
新策略加载后行为仍旧
区分下载、解析、编译、激活与业务调用。共享服务或 sidecar 的实例可能仍使用不同版本;健康检查成功也不能证明目标版本已激活。逐实例查询版本证据并重放同一 fixture,确认负载均衡后的每个实例给出一致结果,再恢复流量。
性能优化先改变算法与数据布局
OPA 通常把 policy 和 data 保存在内存中。性能预算至少包括 PEP 序列化、网络、队列、求值、日志遮盖和响应解析。基准样本要覆盖小/大 input、命中/拒绝、冷/热缓存、正常/峰值并发以及策略更新期间,报告 P50、P95、P99、吞吐、CPU、RSS 和错误率。
Rego 优化从减少搜索空间开始:尽早用等值条件约束对象;把频繁按 ID 查找的数组改成对象索引;避免在热路径构造巨大中间集合;把稳定的派生结果预计算进 data。使用 partial evaluation 或 Wasm 前先建立语义对比,优化后仍运行全部正反 fixture。
缓存授权结果时,key 至少包含主体、动作、资源、影响决策的上下文和策略/data 版本。只用 user + resource 会让不同动作串权;TTL 会延迟撤权,负缓存会延迟新授权。高风险写操作通常不适合长 TTL,撤权事件需要主动失效或版本切换,不能只等自然过期。
权限与敏感数据从监听端口开始
本机 CLI 只读取受审查目录;CI 使用只读 checkout 和固定二进制。HTTP 服务将决策读取与 policy/data 写入分离,业务身份只能访问必要 decision path。管理 API 放在独立网络入口,使用 TLS 与客户端身份,并以 system.authz 进一步限制方法和路径。OPA 自身的授权策略也要有 break-glass 与回退方案,避免错误规则把修复入口一起锁死。
策略包可能泄露角色、产品开关和内部对象关系,bundle 传输要加密,签名用于验证来源和完整性。签名不提供机密性,也不阻止合法旧包被重放;版本回退保护与审批要另行设计。input 与 result 进入决策日志前先遮盖 token、Cookie、密码、客户字段与可枚举资源信息,日志丢弃规则也要测试,避免拒绝事件被静默删除。
不要把生产真实请求直接放进公开 fixture。样例统一使用 alice、order-100 和 example.com;事故重放先去标识化,并限制访问、保留时间与删除责任。
HA 取舍由失败语义决定
进程内 SDK 与 Wasm 没有网络单点,策略却与应用一同升级、占用内存并共享崩溃边界。sidecar 把网络缩到 loopback,可在外部中断时继续使用最近策略,但每个实例都要加载和观测。共享 OPA 服务便于集中大 data 与弹性容量,却要求多副本、负载均衡、限流、隔离和客户端重试约束。
HA 设计必须回答四个问题:实例失联时使用旧策略多久;撤权多久必须生效;超时是拒绝、降级还是暂停;多副本版本不一致时由谁阻止流量。对退款、密钥读取和管理操作,失败关闭通常更合理;对低风险只读且能容忍陈旧决定的功能,可以在明确时限内使用本地最近版本。选择依据是业务损失与恢复目标,不是统一套用一个 fail-open 开关。
容量估算用峰值授权 QPS 乘以每个业务请求的决策次数,再加入重试与发布影子流量。共享服务要压测单实例上限、N-1 容量和策略更新风暴;sidecar 要计算总内存副本与节点密度;SDK/Wasm 要观察应用 GC 和启动时间。多副本 Ready 之后仍要证明它们加载了同一策略版本。
清理本地实验不留下隐形服务
前台 opa run 用 Ctrl+C 停止。后台进程应先找到确切 PID 与监听端口,再正常终止,不使用模糊进程名批量删除。容器实验按名称清理:
docker stop opa-lab 2>/dev/null || true
docker rm opa-lab 2>/dev/null || true
docker image inspect openpolicyagent/opa:<opa-version> >/dev/null 2>&1 && \
echo "image retained; remove only when no other project uses it"删除 dist/orders-authz.tar.gz、临时 fixture 与测试 data 前先确认它们没有被项目引用。共享环境还要撤销实验客户端证书或 token,删除临时网络规则、日志索引和缓存,不直接删除可能由其他服务共享的策略目录或镜像。最后检查 localhost:8181 不再监听,并确认 CI 没有遗留浮动下载任务。
升级从双版本语义比较开始
升级前记录旧新 OPA 版本、启动参数、Go 包路径、Wasm runtime、使用的 built-ins 和策略/data 版本。先在新 CLI 上运行格式化、严格检查、单元测试、覆盖率与所有反向 fixture;再对代表性脱敏 input 同时执行旧新版本,分类统计 allow→deny、deny→allow、reason 变化、undefined 与错误变化。
从 Rego v0 迁移时,先升级 bundle producer 并在 manifest 中明确 Rego 版本,再升级 consumers。--v0-compatible 是过渡工具,待所有模块完成 v1 语法和语义测试后移除。Go 集成分步骤迁到 /v1/ 包,Wasm 宿主单独回归 ABI 与 built-ins;避免把语言、运行时、数据模型和 PEP 错误处理塞进一次不可回退的发布。
滚动升级共享服务时保留旧池,新池先接影子请求,再接少量真实流量;指标不仅看错误率,还看决定差异、P99、CPU、内存和超时。若出现未解释的开放差异、undefined 增长、冲突或长尾突破门槛,立即回读旧池。升级完成后删除兼容参数、旧二进制、旧容器、废弃凭证和过期策略副本,但保留受控回滚窗口内需要的不可变制品。
Bundle 按完整快照原子激活
生产实例不应靠逐条写 Policy API 更新策略。Bundle 把 policy、data 与 manifest 作为一个发布单元,OPA 下载后先校验、解压、编译,全部成功才替换当前活跃快照。下载返回 200 只证明拿到了字节;签名错误、manifest 不兼容、Rego 编译失败或 data 冲突都可能让实例继续使用上一版。发布系统必须读取 Status 中的 active revision,而不是从对象存储访问日志推断已经生效。
Bundle 使用不可变 revision,内容地址或 ETag 只承担缓存与传输职责。签名公钥、可信身份和回滚保护由受控 bootstrap 配置提供;签名证明来源与完整性,不提供机密性,也不自动阻止合法旧包重放。敏感 data 仍需 TLS、访问控制和最小化。拆分多个 Bundle 时先验证根路径所有权,两个包不能同时写同一 data 子树或同名 policy module。
services:
policy_store:
url: https://policy.example.test
bundles:
orders:
service: policy_store
resource: bundles/orders.tar.gz
persist: true
polling:
min_delay_seconds: 30
max_delay_seconds: 60正向发布保存 bundle digest、manifest revision、签名验证结果与各实例 active revision。反向 fixture 分别放入坏签名、语法错误、重复根和旧 revision,断言新包不激活、最后成功版本继续服务、Status 给出可定位错误。persist: true 能帮助进程重启时恢复最近包,但持久文件的权限、完整性和磁盘水位也必须监控。
Discovery 只从最小可信配置长出
Discovery Bundle 可以下发 services、bundles、status、decision logs 等运行配置,适合管理大量 OPA 实例。它同时扩大了引导配置的权力:若 bootstrap 允许不受信来源替换下载地址、凭证或日志目的地,攻击者不必修改 Rego,就能让实例读取恶意 Bundle 或泄露决策数据。bootstrap 因此只包含可信 Discovery 服务、认证材料、签名根、TLS 与必要标签,并由镜像或受控 Secret 提供。
Discovery 更新也遵循下载、验证、解析和激活状态链。候选配置先在隔离实例验证服务引用、凭证范围、Bundle 根和插件参数,再分批推广。禁止让 Discovery 动态关闭自身的签名验证或把管理 API 改成匿名公网监听。break-glass 配置保留在独立受控路径,只有自动发现链损坏时启用,并在恢复后撤销。
配置版本与策略版本分开记录。一个实例可能使用新 Discovery revision,却仍因 Bundle 编译失败运行旧 policy revision;监控把两者压成单个“配置版本”,会掩盖真实漂移。实例标签还应包含环境、应用、区域和 owner,便于定位哪个故障域没有收敛。
Status 证明激活而不只证明下载
Status plugin 报告 Bundle 与 Discovery 的下载、激活、错误和 revision。发布验收至少比较期望 revision、active revision、最后成功激活时间、最近错误与实例覆盖率;只有所有目标实例达到策略定义的收敛比例,流量或业务变更才能继续。未上报、上报延迟和明确失败是三种状态,不能都显示为“待同步”。
多副本滚动升级时,把 Status 与真实决策探针关联。探针对固定脱敏 input 查询稳定 decision path,并记录实例、策略 revision 和结果;这能发现“Status 看似一致但 PEP 走错路径”以及“实例已激活但负载均衡仍访问旧池”。Status 接收端不可用不应清空已加载策略,但必须产生积压、丢弃或最后成功时间指标。
状态标签不能包含 Token、完整 URL 凭证或敏感 input。接收端对实例身份做认证,限制谁能伪造 revision;否则攻击者可以提交虚假“已激活”记录骗过推广控制器。状态保留期覆盖发布回滚与安全调查窗口,并与 bundle digest 关联。
Decision Log 先遮盖再离开进程
Decision Logs 能记录 decision id、path、input、result、bundle revision、metrics 和时间,为授权审计、影子比较和排障提供证据;它不是绝对不丢的事务日志。缓冲区满、上报端不可用、采样和遮盖错误都会产生缺口。高风险业务若要求不可抵赖审计,还需要 PEP 或业务系统记录最终执行动作,并用 decision id 与 OPA 结果关联。
遮盖规则在 OPA 进程内执行,先删除 Token、Cookie、密码、客户字段和可枚举资源信息,再进入队列。用正反 fixture 验证敏感路径确实被删除、必要的 action、结果类别和 revision 仍保留;规则异常时采用拒绝上报或最小记录,不能回退为上传完整 input。日志接收身份只允许写指定流,传输加密,存储按租户、用途和保留期授权。
{
"decision_id": "dec-example-001",
"path": "orders/authz/decision",
"input_digest": "sha256:example",
"result": { "allow": false, "reason": "owner_mismatch" },
"bundle_revision": "orders-r184.3",
"pep_action": "refund_blocked"
}容量测试覆盖峰值 QPS、大 input、拒绝风暴、接收端限流和断网恢复,记录队列长度、丢弃量、压缩比、上报延迟与磁盘影响。采样策略不能只保留 allow 而丢掉 deny,也不能让一个大租户挤掉其他租户的审计预算。需要关联业务动作时,PEP 记录 decision id 与动作类别,不复制完整策略输入。
管理面故障按下载、验证、激活和上报分层
“对象存储返回 200,但实例仍使用旧规则”先查 Status 的 active revision 与 error,再分别核对签名、manifest、编译和 data 根冲突。不要反复覆盖同一路径期待实例自行恢复;保留坏包 digest 作为失败证据,修复后发布新 revision,并证明所有目标实例收敛。
“新实例启动后没有策略”区分 bootstrap 不可读、Discovery 未激活、Bundle 下载失败和持久包权限错误。只因 readiness 端点为 200 就接入流量,会在空策略或 undefined 状态下暴露业务。readiness 应结合所需 Bundle 已激活、revision 达到最低水位和固定决策探针通过。
“决策日志突然变少”比较真实决策 QPS、采样、mask 错误、缓冲丢弃、上传状态和接收端限流。没有日志不等于没有请求。若接收端恢复后重试,限制批量大小、退避和最大保留,避免日志回放挤占实时授权 CPU 与网络。
管理能力随 OPA 一起升级和退出
升级 OPA 时同时回归 Rego 语义、Bundle manifest、签名、Discovery schema、Status payload、Decision Log mask、持久包和管理 API 授权。候选池用同一批脱敏 input 做旧新结果对比,用坏包与坏配置证明最后成功版本不会被替换,再验证日志遮盖与接收端故障。通过后逐批切流,保留旧池和旧 Bundle 的不可变回滚窗口。
退出 OPA 前先让 PEP 改读替代决策源,比较允许、拒绝、undefined、超时和系统错误;停止新 Bundle 发布和 Discovery 变更,导出策略、data、revision、Status 与必要审计;随后撤销下载和上报凭证、关闭管理端口、清理持久包与缓存。日志与备份按保留策略核销,不能因服务下线立即删除仍在审计窗口内的证据。
OPA 最终交付的不是一份能编译的策略,而是一条可证明的决策与管理链:可信 input 进入明确路径,policy 与 data 以原子 Bundle 激活,实例通过 Status 报告真实 revision,Decision Log 在脱敏后关联 PEP 动作,undefined 和错误稳定关闭,性能、权限、升级和退出都有 owner。做到这些,策略才从文本变成可以长期运行的工程部件。
