跳到主要内容

Egress Policy(出网策略)

Egress Policy 插件在请求到达上游之前,同步调用 CyberGuard 对请求内容进行检测,并依据配置的策略链决定放行(PASS)拦截(BLOCK)

启用条件

Egress Policy 插件只有在同时满足以下条件时,才会被挂进请求链路:

  1. 静态配置 plugins.egress_policy.enable = true
  2. 当前 route 配置了 plugin_config.egress_policy

两个条件缺一不可。若只满足其中之一,插件不会进入该 route 的请求链路。

备注

Egress Policy 只挂在 OnForwardChat hook 上,仅对 /v1/chat/completions 等 chat 请求生效;embeddings、models 等请求不受影响。

执行位置

在插件执行顺序中,Egress Policy 位于:

  • 之后upstream-ratelimit(先检查配额)
  • 之前guardrequestcache

静态配置

config.toml 中通过 [plugins.egress_policy] 配置全局参数:

[plugins.egress_policy]
enable = true
check_service_url = "http://cyberguard:9019"
timeout_ms = 5000
字段类型说明
enablebool是否全局启用插件,必须为 true 才能激活
check_service_urlstringCyberGuard gRPC 服务地址,静态配置优先于路由配置
timeout_msint64单次 gRPC 调用超时(毫秒),静态配置优先于路由配置;0 时使用默认值 5000ms

路由配置

路由级配置通过 RoutePluginConfig 管理接口写入,plugin_key 固定为 egress_policyconfig 为以下结构的 JSON:

{
"check_service_url": "http://cyberguard:9019",
"timeout_ms": 3000,
"policy_chain_json": "{ ... }",
"policies": [
{
"id": "block-unsafe",
"name": "拦截 unsafe 内容",
"priority": 10,
"enabled": true,
"matches": [
{ "source": "GUARD", "key": "status", "operator": "EQUALS", "value": "unsafe" }
],
"action": {
"type": "BLOCK",
"status_code": 403,
"message": "请求内容不合规,已被拦截"
}
}
]
}
字段类型说明
check_service_urlstring覆盖全局 check_service_url(仅当静态配置为空时生效)
timeout_msint64覆盖全局 timeout_ms(仅当静态配置为 0 时生效)
policy_chain_jsonstring传递给 CyberGuard 的 PolicyChain JSON,当有 source=GUARD 策略时必填
policiesarray策略列表,按 priority 升序评估(First Match Wins)

policy 字段

字段类型说明
idstring唯一标识,会记录在请求日志中
namestring人类可读名称(可选)
priorityint优先级,值越小越先匹配
enabledbool是否参与匹配,false 时直接跳过
matchesarray匹配条件列表,AND 语义(全部满足才触发)
actionobject匹配成功后的动作

match 字段

字段类型说明
sourcestring取值来源:GUARDHEADER
keystring字段名。GUARD 时为 statusactionHEADER 时为 HTTP 请求头名称(大小写不敏感)
operatorstring比较运算符,见下表
valuestring期望值(EQUALS / NOT_EQUALS 使用)
valuesstring[]期望值列表(IN 使用)

支持的 operator

语义
EQUALS等于(大小写不敏感)
NOT_EQUALS不等于(大小写不敏感)
INvalues 列表中(大小写不敏感)
EXISTS字段存在且非空
NOT_EXISTS字段不存在或为空

GUARD source 的 key 取值:

key说明
statusCyberGuard 检测结果:safeunsafe
actionCyberGuard 建议动作:passalertblockoverride

action 字段

字段类型说明
typestringBLOCKPASS
status_codeintBLOCK 时返回的 HTTP 状态码,默认 403
messagestringBLOCK 时返回的错误消息,默认 请求内容不合规,已被拦截

工作流程

请求 → reqIR 为 nil?
├─ Yes → bypass(直接放行)
└─ No → 调用 CyberGuard gRPC
├─ 超时 / 错误 / 无消息 → bypass(fail-open,直接放行)
└─ 成功 → 按 priority 升序评估策略
├─ 匹配到 BLOCK 策略 → 返回错误响应(拦截)
├─ 匹配到 PASS 策略 → 放行
└─ 没有策略匹配 → 默认放行

决策结果(effect_action)

含义
pass请求放行,到达上游
block请求被拦截,不到达上游
bypassCyberGuard 不可用(超时/错误),fail-open 放行

拦截响应

当策略命中 BLOCK 时,插件直接设置 TerminalResponse,不调用 next:

HTTP/<status_code>
Content-Type: application/json

{
"error": {
"message": "<policy.action.message>",
"type": "egress_policy_blocked",
"param": null,
"code": null
}
}

默认 status_code403,默认 message请求内容不合规,已被拦截

请求日志

每次请求都会向 ext_fields["egress_policy"] 写入决策记录:

{
"egress_policy": {
"effect_action": "block",
"matched_policy_id": "block-unsafe",
"guard_status": "unsafe",
"guard_action": "block"
}
}
字段类型说明
effect_actionstringpassblockbypass
matched_policy_idstring命中策略的 id,无匹配或 bypass 时为空
guard_statusstringCyberGuard 检测状态(safe / unsafe),bypass 时为空
guard_actionstringCyberGuard 建议动作,bypass 时为空

参见 日志扩展字段 中关于 ext_fields 的说明。

Prometheus 指标

插件暴露以下 Prometheus 指标(通过管理服务器 GET /metrics 获取):

aidy_egress_policy_decisions_total(Counter)

每次策略决策都会递增,包含 bypass。

标签说明
route_id触发决策的 route ID
effect_actionpassblockbypass
guard_statussafeunsafe 或空字符串(bypass 时)

aidy_egress_policy_bypass_total(Counter)

仅在 effect_action=bypass 时递增,用于独立监控 CyberGuard 服务可用性。

标签说明
route_id触发 bypass 的 route ID

aidy_egress_policy_cyberguard_duration_seconds(Histogram)

记录每次同步 CyberGuard gRPC 调用的耗时。

Buckets:.05, .1, .25, .5, 1, 2, 5, 10(秒)

标签说明
route_id触发调用的 route ID
resultok(成功)或 error(超时/连接失败等)

配置示例

示例 1:拦截 unsafe 请求

# config.toml
[plugins.egress_policy]
enable = true
check_service_url = "http://cyberguard:9019"
timeout_ms = 5000
{
"policies": [
{
"id": "block-unsafe",
"priority": 10,
"enabled": true,
"matches": [
{ "source": "GUARD", "key": "status", "operator": "EQUALS", "value": "unsafe" }
],
"action": { "type": "BLOCK", "status_code": 403 }
}
]
}

示例 2:根据请求头拦截特定租户

{
"policies": [
{
"id": "block-risky-tenant",
"priority": 5,
"enabled": true,
"matches": [
{ "source": "HEADER", "key": "X-Risk-Level", "operator": "IN", "values": ["high", "critical"] },
{ "source": "GUARD", "key": "status", "operator": "EQUALS", "value": "unsafe" }
],
"action": { "type": "BLOCK", "status_code": 403, "message": "高风险租户请求已拦截" }
},
{
"id": "pass-all",
"priority": 100,
"enabled": true,
"matches": [],
"action": { "type": "PASS" }
}
]
}

示例 3:仅观察(不拦截)

若只需采集指标、不做任何拦截,可不配置任何策略或仅配置 PASS 策略;插件会正常调用 CyberGuard 并记录日志,最终默认放行。