Loki 标签级访问控制(LBAC)实战指南:基于 X-Prom-Label-Policy 的多租户日志查询隔离
【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki
导读
标签级访问控制(Label-Based Access Control,LBAC)是 Loki(v3.7.3 起提供,实验特性)提供的一种细粒度查询隔离机制:它允许运维方为每个租户绑定一组 Prometheus 标签选择器(label selector),使该租户发起的查询只能命中与任一选择器匹配的日志流,从而实现"租户内再分权"的数据可见性边界。本文将基于 lbac.md 官方文档,结合 pkg/labelaccess 与 pkg/loki/labelaccess.go 的源码实现,完整讲解 LBAC 的安全模型、启用方式、标签策略头格式、多选择器组合、作用范围与边界限制,并给出可直接落地的网关侧配置示例。
什么是标签级访问控制(LBAC)
LBAC 的核心思想非常直接:把"租户能看哪些数据"从"整个租户的数据"细化为"满足若干标签选择器之一的数据"。
- 每个策略由一组标准的 Prometheus 标签选择器构成;
- 一个租户可以关联多个选择器,选择器之间按OR(析取)组合;
- 查询执行时,只有至少匹配其中一个选择器的日志流才会被返回,其余日志流在查询阶段即被过滤掉;
- 由于选择器之间是 OR 关系,整个策略体系等价于析取范式(Disjunctive Normal Form, DNF)。源码中 pkg/labelaccess/types/label.go 的
LabelPolicy结构体正是用一个Selector []*LabelMatcher表达"同一选择器内多个匹配器 AND",而LabelPolicySet(map[string][]*types.LabelPolicy,见 pkg/labelaccess/propagation.go)则通过"每个租户挂多个 Policy"表达"选择器之间 OR"。
从源码结构可以推断,这套 DNF 语义最终落在过滤器的ShouldFilter方法上(pkg/labelaccess/request_chunk_filterer.go):
- 遍历该租户的每个
LabelPolicy; - 在单个 Policy 内,所有
LabelMatcher必须全部匹配(AND); - 只要任意一个Policy 匹配,就返回
false(不过滤该 chunk/stream,即放行); - 全部 Policy 都不匹配,才返回
true(过滤掉)。
注意:LBAC 建立在 Loki 多租户机制之上,策略按租户隔离,因此必须运行在多租户模式下。
安全模型:信任边界与网关前置
Loki 本身不包含认证层。无论是租户标识还是标签策略,都来源于 HTTP 请求头:
| 请求头 | 含义 |
|---|---|
X-Scope-OrgID | 租户(tenant / org)标识,Loki 多租户的标准头 |
X-Prom-Label-Policy | 标签策略,格式为<tenant>:<URL-encoded selector> |
官方文档对此给出了一条硬性警告:
绝不直接将 Loki API 暴露给客户端。任何能直接触达 Loki 的客户端都可以自行伪造
X-Scope-OrgID与X-Prom-Label-Policy头,从而完全绕过标签级访问控制。必须在 Loki 前方部署认证网关,由网关完成请求认证、设置租户并附加正确的标签策略。
因此推荐架构为:
Client ──► Auth Gateway ──► Loki │ ├─ 1. 认证客户端身份 ├─ 2. 剥离用户提交的 X-Scope-OrgID / X-Prom-Label-Policy └─ 3. 依据认证后的身份,写入正确的 X-Scope-OrgID 与 X-Prom-Label-Policy网关必须负责剥离用户自带的两个头,并以认证身份推导出的值替换之。官方文档提及的开源网关参考实现为 grafana/db-auth-gateway;更通用的认证反向代理部署指引可参考仓库内 authentication 文档(若存在)。
这一信任边界在源码中同样成立:Loki 侧只是"信任"X-Prom-Label-Policy头并解析执行,见 pkg/labelaccess/middleware.go 中NewLabelAccessMiddleware的注释——"parses the X-Prom-Label-Policy header and injects the parsed label selectors into the request context"。
启用标签级访问控制
1. 确保多租户模式
多租户模式默认开启,显式配置为:
auth_enabled: true只有开启auth_enabled,请求才会携带租户标识,LBAC 才有"按租户"锚定策略的基础。
2. 开启 LBAC
lbac: enabled: true对应配置结构体见 pkg/labelaccess/config.go,其中Enabled字段被标记为category:"experimental",默认false。也可以通过命令行参数启用:
-lbac.enabled=true该 flag 的官方描述为"Enables label based access control through the X-Prom-Label-Policy header"。
3. 启用后发生什么
开启后,Loki 会在请求链路上解析X-Prom-Label-Policy头并强制执行其中的策略。从 pkg/loki/labelaccess.go 的setupLBAC()可以看到,LBAC 是一个贯穿几乎全部查询路径的横切特性,它注册了十余个内部模块并注入依赖:
- AuthMiddleware:合并
labelaccess.NewLabelAccessMiddleware到 HTTP 认证中间件链,负责解析策略头并注入请求上下文(pkg/loki/labelaccess.go); - LabelAccessStoreWrapper / LabelAccessIngesterWrapper / Filterers:分别包装 Store、Ingester,并为 Ingester 配置 ChunkFilterer,实现存储与内存侧过滤;
- LabelAccessV2Engine:为 V2 查询引擎设置
RequestStreamFilterer,实现数据对象上的流级过滤(pkg/loki/labelaccess.go,对应实现 pkg/labelaccess/request_stream_filterer.go); - LabelAccessInterceptors:为 Ingester / IndexGateway 的 gRPC 服务端与客户端追加 LBAC 拦截器,保证策略在 gRPC 链路上不丢失;
- LabelAccessTripperware / LabelAccessUserIDTransformer:处理查询前端中间件与缓存键的用户维度变换。
对应测试 pkg/loki/lbac_setup_test.go 验证了:开启 LBAC 时这些模块全部注册且依赖无环(TestSetupLBACEnabled),关闭时这些模块全部不注册(TestSetupLBACDisabled),并确保 LBAC 中间件只安装一次(TestLBACAuthMiddlewareRegisteredOnce)。
设置标签策略:X-Prom-Label-Policy 头的格式
标签策略通过X-Prom-Label-Policy请求头传给 Loki。该头由认证网关设置,而不是客户端。
单个策略的格式
每个头的值形如:
<tenant>:<URL-encoded selector>其中 selector 是一组标准标签匹配器,例如{env="dev"}。因为花括号、引号等字符不适合直接出现在 HTTP 头中,需要做URL 编码。官方示例:租户tenant1的策略{env="dev"}编码为:
X-Prom-Label-Policy: tenant1:%7Benv%3D%22dev%22%7D解码后即为tenant1:{env="dev"}。
一个租户关联多个选择器
要把多个选择器关联到同一租户,有两种等价写法:
方式一:重复头(每个值一条):
X-Prom-Label-Policy: tenant1:%7Benv%3D%22prod%22%7D X-Prom-Label-Policy: tenant1:%7Benv%3D%22dev%22%7D方式二:逗号分隔(合并到同一个头值中):
X-Prom-Label-Policy: tenant1:%7Benv%3D%22prod%22%7D,tenant1:%7Benv%3D%22dev%22%7D两种写法的解析路径在源码中都能找到依据:gRPC 侧解析函数ExtractLabelMatchersGRPC(pkg/labelaccess/codec.go)会对头值按逗号strings.Split(headerValue, ",")拆分后逐条解析;HTTP 侧同样按此语义合并成LabelPolicySet。多个选择器之间按OR组合——一个日志流只要匹配其中任意一个选择器就会被返回。
实战示例:排除带敏感标签的日志
最常见的 LBAC 需求是"排除带有某标签的日志"。例如排除所有带secret=true标签的日志行,使用不等匹配器:
{secret!="true"}对应网关头:
X-Prom-Label-Policy: tenant1:%7Bsecret%21%3D%22true%22%7D(!编码为%21,=编码为%3D,"编码为%22)
实战示例:多选择器组合策略
假设某租户需要同时满足:
- 能访问生产环境(
env="prod")日志,但生产环境中排除secret=true的日志; - 能访问开发环境(
env="dev")日志,开发环境不做敏感标签排除。
则策略应配置两个选择器:
{secret!="true", env="prod"} {env="dev"}该策略的执行语义如下:
| 选择器 | 匹配行为 |
|---|---|
{secret!="true", env="prod"} | 返回生产环境中没有secret: true标签的日志行(选择器内多个匹配器是 AND 关系) |
{env="dev"} | 返回开发环境的日志行,即使它们带有secret: true标签 |
这正是 DNF 的表达力所在:两个选择器 OR,第一个选择器内部 AND 两个条件,从而在一个策略内同时表达"排除 + 放行"的差异化规则。
对应到源码,每个选择器内部是 AND 逻辑(见上文ShouldFilter的单 Policy 全匹配判断),而不同选择器之间是 OR(任一匹配即放行)。测试文件 pkg/labelaccess/request_chunk_filterer_test.go 与 pkg/labelaccess/middleware_test.go 覆盖了这类组合场景。
标签策略的作用范围
需要特别明确 LBAC 的边界:
标签策略只作用于查询中的日志流选择器(Log stream selector),不作用于标签过滤表达式(Label filter expression)。
举例来说,查询:
{env="prod", secret!="true"} |= "error"中,LBAC 策略只会叠加/约束{env="prod", secret!="true"}这部分流选择器;|= "error"这类过滤表达式(以及解析后的 label filter)不在 LBAC 的管辖范围内。相关概念可参考仓库内日志查询文档中关于 Log stream selector 与 Label filter expression 的说明(以仓库实际文档为准)。
源码层面,Loki 会先解析查询的流选择器,再与策略选择器做合并/交集判断:例如 volume 查询场景下,中间件会把策略匹配器 AND 进查询的流选择器中,使索引层只返回租户被允许看到的流(pkg/labelaccess/middleware.go 的modifyVolumeQuery);多策略时则跳过查询改写、改由响应级过滤保证 OR 语义。
写入路径不受限:LBAC 只约束查询
LBAC 不作用于写入(push)请求。只要某租户被允许写入,它就可以推送携带任意标签的日志行,无论其查询策略如何。
这是"读时过滤"模型的自然结果:LBAC 在查询链路上做过滤,而不是在写入时对标签做校验或改写。如果你需要限制租户能写入的标签,需要另行配置写入侧校验(如租户限制、标签验证等机制),LBAC 本身不提供该能力。
已知边界:Alertmanager 与 ruler 不受 LBAC 约束
标签策略不会被 Alertmanager 和 ruler 强制执行。这意味着:
- ruler 服务处理的请求包含该租户的全部内容,不应用 LBAC 过滤;
- 例如列出 ruler 中某租户的全部规则组(rule groups)时,即使策略中的某个标签选择器会排除部分规则上的标签,这些规则仍会全部返回。
这一点对依赖 ruler 告警规则的部署方尤为重要:不要把敏感数据可见性的最终防线寄托在 ruler/Alertmanager 上,需要为告警与规则评估链路单独设计访问控制。
从模块依赖看,LBAC 的模块注入集中在Querier / QueryFrontend / QueryEngine / Ingester / IndexGateway / Server等查询相关链路(pkg/loki/labelaccess.go),ruler/Alertmanager 组件并不在 LBAC 的注入清单中,与文档描述的"不受约束"一致。
端到端落地检查清单
- 部署形态:Loki 必须开启
auth_enabled: true(默认即开启),运行在多租户模式; - 功能开关:配置
lbac.enabled: true或-lbac.enabled=true; - 网关前置:任何客户端不得直连 Loki API;由认证网关剥离并重写
X-Scope-OrgID与X-Prom-Label-Policy; - 策略编码:头值格式
<tenant>:<URL-encoded selector>;多选择器用重复头或逗号分隔;选择器间 OR、选择器内 AND; - 查询验证:用
logcli或 Grafana 以目标租户身份发起查询,确认只返回策略允许的日志流;对 volume 等索引类查询同样验证; - 边界确认:明确写入请求、ruler、Alertmanager 不受 LBAC 约束,按需为这些链路补充其他控制手段。
相关资源
- 官方文档原文:Use label-based access control with Loki
- LBAC 配置结构体与命令行参数:pkg/labelaccess/config.go
- LBAC 模块装配与依赖注入:pkg/loki/labelaccess.go、pkg/loki/lbac_setup_test.go
- 策略头解析与传播(
X-Prom-Label-Policy、URL 解码、FNV 哈希缓存键):pkg/labelaccess/propagation.go、pkg/labelaccess/codec.go - HTTP 中间件(解析、注入、volume 查询改写):pkg/labelaccess/middleware.go
- 过滤实现(V1 chunk 级 / V2 stream 级):pkg/labelaccess/request_chunk_filterer.go、pkg/labelaccess/request_stream_filterer.go
- 策略与匹配器的 protobuf 风格类型定义:pkg/labelaccess/types/label.go
- 多租户文档:multi-tenancy(以仓库实际路径为准)
【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考