首页
/ gRPC 授权框架(Authorization)深度解析:AuthorizationEngine、RBAC 策略与审计日志实战指南

gRPC 授权框架(Authorization)深度解析:AuthorizationEngine、RBAC 策略与审计日志实战指南

2026-09-09 14:21:19作者:滑思眉Philip

导读

本指南以 gRPC 仓库中 src/core/lib/security/authorization/AGENTS.md 为核心主线,系统拆解 gRPC 服务端授权框架的完整实现:从 AuthorizationEngineAuthorizationPolicyProviderEvaluateArgs 三大核心抽象,到基于 Envoy RBAC 的 GrpcAuthorizationEngine 与基于 CEL 的实验性引擎,再到授权策略 JSON 的编写、服务端过滤器的决策流程与审计日志机制。读完本文,你将掌握如何为 gRPC 服务编写"按认证身份 + 请求元数据"的访问控制策略,理解策略如何在运行时被加载、翻译与热更新,并能定位到对应的源码文件继续深挖。

一、框架总览:用策略控制 gRPC 服务的访问

授权(Authorization)解决的是"这个请求是否有权访问该服务/方法"的问题。根据目录文档的说明,该目录包含 gRPC 授权框架的完整实现,其核心目标是:基于授权策略(Authorization Policy)中定义的一组规则,对进入的请求进行授权判定,从而依据已认证用户的身份以及其他请求元数据,控制对 gRPC 服务与方法的访问

与认证(Authentication,解决"你是谁")不同,授权回答的是"你能否做这件事"。gRPC 的授权框架把两者衔接起来:认证结果(如 mTLS 证书中的 SPIFFE ID、URI SAN、DNS SAN、Common Name)会作为授权判定的输入之一。

框架的顶层设计可以概括为一条链路:

策略来源(静态字符串 / 文件 / xDS)
        │  (AuthorizationPolicyProvider 提供策略)
        ▼
AuthorizationEngine(allow 引擎 + deny 引擎)
        │  Evaluate(EvaluateArgs)
        ▼
授权决策 Decision(kAllow / kDeny)
        │
        ▼
GrpcServerAuthzFilter 在服务端拦截并执行(拒绝则返回 PERMISSION_DENIED)

二、三大核心抽象:Engine、Provider 与 EvaluateArgs

2.1 AuthorizationEngine:授权决策的唯一入口

接口定义在 authorization_engine.h 中。它是一个继承自 RefCounted 的抽象接口,只暴露一个方法:

// Interface for gRPC Authorization Engine.
class AuthorizationEngine : public RefCounted<AuthorizationEngine> {
 public:
  struct Decision {
    enum class Type {
      kAllow,
      kDeny,
    };
    Type type;
    std::string matching_policy_name;
  };

  virtual Decision Evaluate(const EvaluateArgs& args) const = 0;
};

值得注意的细节:

  • Decision 结构:不仅返回 kAllow/kDeny,还携带 matching_policy_name(命中的策略名称)。这既便于排障,也是审计日志的关键信息。
  • 输入是 EvaluateArgs:一次调用就完成"根据请求上下文做出授权决定"。

2.2 EvaluateArgs:一次授权判定所需的全部请求信息

EvaluateArgs 定义在 evaluate_args.h 中,它封装了做出授权决策所需的上下文信息,主要分为两大类:

  • 请求级信息(来自 grpc_metadata_batch):GetPath()(如 /helloworld.Greeter/SayHello)、GetAuthority()GetMethod()GetHeaderValue(key, ...)(按 key 取请求头;key 出现多次时拼接为逗号分隔字符串)。
  • 通道级信息(来自 PerChannelArgs,源自 grpc_auth_contextChannelArgs):
    • 传输安全类型 GetTransportSecurityType()(如 ssl);
    • 对端身份:GetSpiffeId()GetUriSans()GetDnsSans()GetCommonName()GetSubject()
    • 本地/对端地址与端口:GetLocalAddress()GetPeerAddress()GetLocalPort()GetPeerPort()

这些字段正是策略引擎可以用来做匹配的"证据"。从源码注释看,调用方需保证 auth_context 的生命周期长于 PerChannelArgsPerChannelArgs 中保存的是 string_view,不持有底层数据)。

2.3 AuthorizationPolicyProvider:策略的提供者

grpc_authorization_policy_provider 定义在 authorization_policy_provider.h 中。它的核心设计是一个双引擎结构

struct AuthorizationEngines {
  grpc_core::RefCountedPtr<grpc_core::AuthorizationEngine> allow_engine;
  grpc_core::RefCountedPtr<grpc_core::AuthorizationEngine> deny_engine;
};
virtual AuthorizationEngines engines() = 0;

也就是说,一份授权策略会被拆解成 allow(允许)引擎deny(拒绝)引擎 两个独立引擎,服务端过滤器按"先 deny 后 allow"的顺序依次评估。该结构通过 ChannelArgs 传递,其 ChannelArgName 为 GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER。这样做的好处是:策略可以来自多种来源(本地文件、远程服务器、xDS),引擎本身只关心"如何判定",不关心"策略从哪来"。

三、两种 AuthorizationEngine 实现

框架提供了两个 AuthorizationEngine 的具体实现,对应两种不同的策略模型。

3.1 GrpcAuthorizationEngine:基于 Envoy RBAC 的规则引擎

定义在 grpc_authorization_engine.h(实现见 grpc_authorization_engine.cc)。它是生产可用的主力实现,设计上对齐 Envoy RBAC filter 的模型:

  • 一个 GrpcAuthorizationEngine 实例要么是 Allow 引擎、要么是 Deny 引擎(构造时通过 Rbac::Action 指定);
  • 判定依据是 RBAC 策略中的 Permission(权限,即请求必须具备的条件)Principal(主体,即调用方必须具备的条件)
  • 该引擎忽略 RBAC 配置中的 condition 字段(该字段留给 CEL 引擎使用),调用方有责任提供与之兼容的 RBAC 策略;
  • 每个策略被编译为一个 AuthorizationMatcher(见 matchers.h),包括 HeaderMatcherIpAuthorizationMatcher(支持 kDestIp/kSourceIp/kDirectRemoteIp/kRemoteIp 四种 IP 匹配)、PortAuthorizationMatcher 以及 AuthenticatedAuthorizationMatcher(按顺序取 URI SAN、DNS SAN,否则取 subject 字段作为主体名)。

Rbac 策略结构定义在 rbac_policy.h 中,关键组成:

  • Rbac::ActionkAllow / kDeny
  • Rbac::Permission:规则类型包括 kAndkOrkNotkAnykHeaderkPathkDestIpkDestPortkMetadatakReqServerName,支持组合与取反;
  • Rbac::Principal:规则类型包括 kAndkOrkNotkAnykPrincipalName(认证主体)、kSourceIpkDirectRemoteIpkRemoteIpkHeaderkPathkMetadata
  • Rbac::Policy:由一组 permissions 与一组 principals 组成;
  • Rbac 整体还携带 nameactionaudit_conditionlogger_configs(审计相关)。

从实现看,这是一套表达能力很强的组合式规则模型:既可以是简单的 ACL,也可以构建复杂的基于属性的策略(attribute-based)。

3.2 CelAuthorizationEngine:基于 CEL 表达式的实验性引擎

定义在 cel_authorization_engine.h 中。它基于 Common Expression Language(CEL)对 RBAC 策略中的 condition 字段 求值:

  • 可持有 1 个或 2 个策略;若为 2 个,第一个是 deny-if-matched(命中即拒绝),第二个是 allow-if-matched(命中即允许);
  • 所有策略都未命中时返回 UNDECIDED(无法判定);
  • GrpcAuthorizationEngine 相反,它忽略 principal 与 permission 字段
  • 内部借助 mock_cel/ 子目录(activation.hcel_value.hflat_expr_builder.h 等)实现 CEL 表达式的编译与求值。

需要特别强调的是(文档 Notes 也明确指出):CEL 引擎仍处于实验阶段,不建议用于生产环境。生产场景应使用 GrpcAuthorizationEngine

四、授权策略 JSON 详解:从配置到 RBAC 的翻译

策略提供者在初始化时会把 SDK 授权策略(一段 JSON 字符串)翻译为 RBAC 策略,翻译逻辑集中在 rbac_translator.ccGenerateRbacPolicies() 中。下面结合源码梳理完整的策略格式。

4.1 顶层结构

GenerateRbacPoliciesrbac_translator.cc)要求策略 JSON 必须是一个对象,支持四个顶层字段:

字段 类型 必填 说明
name string 策略名称
deny_rules array 拒绝规则列表,翻译为 Rbac::Action::kDeny 引擎
allow_rules array 允许规则列表,翻译为 Rbac::Action::kAllow 引擎;缺失直接报错
audit_logging_options object 审计日志配置(见 4.4)

源码对未知字段一律拒绝(返回 InvalidArgumentError),这是 gRPC 授权策略"严格校验"风格的体现。

4.2 规则(rule)的组成

deny_rules / allow_rules 中的每个规则由 ParseRulerbac_translator.cc)解析,支持三个字段:

  • name(string,必填):规则名,将作为 Decision.matching_policy_name 回传;
  • source(object):定义主体条件(principals)。当前仅支持 principals 数组,每个元素是对认证主体名的字符串匹配(如 SPIFFE ID);多个元素之间为 OR 语义(MakeOrPrincipal),整个 source 缺省时等价于"任意主体"(MakeAnyPrincipal);
  • request(object):定义请求条件(permissions),支持:
    • paths(array):请求路径(/service/method)的字符串匹配,多个 path 为 OR 语义;
    • headers(array):请求头匹配,每个元素形如 {"key": "...", "values": [...]},其中 values 多个值为 OR、多个 header 条件之间为 AND 语义;key 不能以 :grpc- 开头,host 等保留头会被拒绝(源码 IsUnsupportedHeader / kUnsupportedHeaders)。

下面是一个贴合上述规则的完整示例(结构依据 ParseRule/ParseRequest/ParseHeaders/ParsePrincipalsArray 的源码约束构造):

{
  "name": "org.example.greeter.Authz",
  "deny_rules": [
    {
      "name": "deny-blocklist",
      "request": {
        "headers": [
          { "key": "x-tenant", "values": ["blocked-tenant"] }
        ]
      }
    }
  ],
  "allow_rules": [
    {
      "name": "allow-admins",
      "source": {
        "principals": ["spiffe://example.com/team-a/admin"]
      },
      "request": {
        "paths": ["/helloworld.Greeter/SayHello"],
        "headers": [
          { "key": "x-version", "values": ["v1", "v2"] }
        ]
      }
    },
    {
      "name": "allow-health-checks",
      "request": {
        "paths": ["/grpc.health.v1.Health/Check"]
      }
    }
  ],
  "audit_logging_options": {
    "audit_condition": "ON_DENY_AND_ALLOW",
    "audit_loggers": [
      { "name": "stdout_logger", "is_optional": true }
    ]
  }
}

4.3 决策语义

  • deny_rules 中的规则命中即拒绝;allow_rules 命中才放行。
  • 结合 grpc_server_authz_filter.ccIsAuthorized 流程:先评估 deny 引擎,命中 kDeny 直接拒绝;再评估 allow 引擎,命中 kAllow 放行;两个引擎都未命中则默认拒绝("request denied, no matching policy found"),即白名单语义。

五、服务端授权过滤器:请求如何被拦截与判定

GrpcServerAuthzFiltergrpc_server_authz_filter.hgrpc_server_authz_filter.cc)是框架接入服务端的枢纽。它是一个 promise-based 服务端过滤器TypeName"grpc-server-authz",通过 MakePromiseBasedFilter<GrpcServerAuthzFilter, FilterEndpoint::kServer>() 注册):

  1. 过滤器在 Create 时从 ChannelArgs 中取出 grpc_auth_context(认证上下文)与 grpc_authorization_policy_provider(策略提供者),构造 EvaluateArgs::PerChannelArgs
  2. 每个 RPC 的客户端初始元数据到达时触发 OnClientInitialMetadata,调用 IsAuthorized(md)
  3. IsAuthorized 按上文"deny → allow → 兜底拒绝"的顺序做出判定;
  4. 判定失败时返回 absl::PermissionDeniedError("Unauthorized RPC request rejected.")(对应 gRPC 状态码 PERMISSION_DENIED),请求在进入业务 handler 之前即被拒绝;
  5. 过滤器中还埋有 grpc_authz_api 跟踪日志:VLOG(2) 打印 url_pathtransport_security_typeuri_sansdns_sanssubject 等评估上下文,INFO 级别打印命中/拒绝的策略名,便于排障。

六、策略提供者:静态加载与文件热更新

6.1 StaticDataAuthorizationPolicyProvider

实现见 grpc_authorization_policy_provider.cc。它在初始化时从静态字符串获取策略,翻译为 RBAC 后构造 allow/deny 两个引擎,之后始终返回同一组引擎(策略不变)。

6.2 FileWatcherAuthorizationPolicyProvider:文件热更新

同一文件中的另一个实现用于从文件路径加载策略,并支持周期性刷新:

  • Create(authz_policy_path, refresh_interval_sec):要求路径非空、refresh_interval_sec > 0(源码 GRPC_CHECK_GT),首次读取在构造函数内同步完成
  • 启动一个独立的刷新线程(FileWatcherAuthorizationPolicyProvider_refreshing_thread),每隔 refresh_interval_sec 秒调用 ForceUpdate() 重新读取文件;
  • 热更新语义:若文件内容未变化则跳过;若新内容解析失败(非法 JSON、缺少 allow_rules、字段类型错误等)或发生 I/O 错误,放弃本次更新并记录错误日志,继续使用最新一份有效策略——这保证了运行期策略损坏不会导致服务不可用;
  • Orphaned() 时通过 gpr_event_set 触发关闭事件并 Join 刷新线程。

6.3 C++ SDK 层的封装

面向 C++ 使用者的 API 在 include/grpcpp/security/authorization_policy_provider.hgrpc::experimental 命名空间)中定义:

// 静态策略:一次传入完整策略 JSON
std::shared_ptr<StaticDataAuthorizationPolicyProvider> provider =
    grpc::experimental::StaticDataAuthorizationPolicyProvider::Create(
        authz_policy_json, &status);

// 文件监听:路径 + 刷新间隔(秒)
std::shared_ptr<FileWatcherAuthorizationPolicyProvider> provider =
    grpc::experimental::FileWatcherAuthorizationPolicyProvider::Create(
        "/etc/grpc/authz_policy.json", /*refresh_interval_sec=*/10, &status);

创建出的 provider 可绑定到服务端,将授权检查挂载到服务器上。C 层对应的函数可见于 include/grpc/grpc_security.h 与 channel 参数 GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER(见 include/grpc/impl/channel_arg_names.h)。

6.4 与 xDS 的配合

文档 Notes 明确指出框架设计与 xDS 协同工作:xDS 可以在运行时动态下发/更新授权策略,AuthorizationPolicyProvider 抽象恰好隔离了"策略来源"与"策略评估",使得通过 xDS 推送的策略与静态/文件策略可以共用同一套引擎实现。这也是 Rbac 结构中"name 在 xDS 场景下可为空字符串"(见 rbac_policy.h)的原因。

七、审计日志(Audit Logging):记录每一次授权决策

审计日志是框架的可观测性组件,接口定义在 audit_logging.h,与公共头文件 include/grpc/grpc_audit_logging.h 对应:

  • AuditLoggerRegistry:审计日志器工厂的注册表,支持 RegisterFactoryParseConfig(将 JSON 配置解析为 AuditLoggerFactory::Config)与 CreateAuditLogger
  • 日志器:仓库内提供 stdout_logger 实现(stdout_logger.h / stdout_logger.cc),将授权决策输出到标准输出;
  • 触发条件Rbac::AuditCondition 支持 kNonekOnDenykOnAllowkOnDenyAndAllow 四种取值;在策略 JSON 的 audit_logging_options.audit_condition 中对应 "NONE""ON_DENY""ON_ALLOW""ON_DENY_AND_ALLOW"(解析逻辑见 rbac_translator.cc);
  • 日志器配置audit_loggers 数组中的每个元素支持 name(必填)、is_optional(布尔,可选)、config(对象,可选);当 is_optional 为 true 且该日志器名称未注册时静默跳过,否则报错。

审计日志的价值正如文档所述:既服务于安全审计(谁在何时访问了什么),也用于调试授权策略(为什么某请求被拒)。

八、源码验证:测试与跟踪日志

  • 单元测试test/core/security/grpc_authorization_policy_provider_test.cc 覆盖了 StaticDataAuthorizationPolicyProviderFileWatcherAuthorizationPolicyProvider 的创建、文件内容变化触发引擎更新、非法内容跳过更新等行为;FileWatcherAuthorizationPolicyProvider 还提供 SetCallbackForTesting 回调以便测试观测每次 reload 是否成功;
  • 跟踪日志:设置 grpc_authz_api 跟踪标志(参见 doc/trace_flags.md 的通道/过滤器跟踪项)即可观察每次授权判定的输入与结论,是排障授权策略的首选手段。

九、使用建议与限制(Notes)

根据目录文档的 Notes 与源码实现,归纳如下:

  1. 能力边界:授权框架是一套强大的安全工具,可支撑从简单 ACL 到复杂基于属性策略的各类访问控制;但策略的最终判定只依赖 EvaluateArgs 中可见的信息(认证身份、请求头、路径、地址端口等),复杂业务态判断需要自行扩展。
  2. 动态更新:框架天然适配 xDS 做运行时策略下发;若不用 xDS,可用 FileWatcherAuthorizationPolicyProvider 实现文件级热更新(注意其"失败保留旧策略"的语义)。
  3. 引擎选择:生产环境请使用 GrpcAuthorizationEngine(RBAC permission/principal 模型);CEL 引擎仍为实验特性,不建议用于生产
  4. 默认拒绝:服务端过滤器的语义是"没有规则命中即拒绝",编写 allow_rules 时务必覆盖健康检查(如 grpc.health.v1.Health/Check)等必要路径,避免误伤。
  5. 策略校验严格:策略 JSON 中未知字段、缺失 allow_rules、非法 header key 等都会导致解析失败,建议先在测试环境中验证策略(可参考 test/core/security/grpc_authorization_policy_provider_test.cc 中的策略样例)。
  6. 审计配合:开启审计日志(如 stdout_logger + ON_DENY_AND_ALLOW)可以为安全审计和策略调试提供决策记录。

如需继续深入,可直接阅读 src/core/lib/security/authorization/ 目录下的全部源码,包括 matchers.h(各类匹配器实现)、rbac_translator.cc(策略翻译)、grpc_server_authz_filter.cc(服务端决策流程)以及 mock_cel/(CEL 实验引擎的求值内核)。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23