> 原始 Markdown 孪生体（构建期从源 Markdown 生成）。渲染页：https://docs.gatellm.io/zh-CN/howto/configure-header-acl · 文档索引：https://docs.gatellm.io/zh-CN/llms.txt


# 配置 Header ACL 访问规则

访问规则基于 HTTP 请求头做准入控制，支持三级作用域：全局、密钥组、访问密钥。规则按 `sort_order` 升序匹配，首条命中决定 allow/deny。

入口：控制台 → **访问密钥** → **Access Rules** 页签。

> **注意**：访问规则同时作用于**推理路由**（`/v1/chat/completions`、`/v1/messages` 等）和**模型列表路由**（`/v1/models`、`/v1beta/models`）。拒绝某个 header 模式时，模型列表查询也会被拦截。运维人员需知晓此行为，避免误配导致客户端无法获取模型列表。

## 规则字段

| 字段 | 说明 |
|------|------|
| **Rule Name** | 规则唯一标识，全局唯一。建议使用作用域前缀（如 `key:mykey__rule1`）避免命名冲突。创建后不可修改。 |
| **Order** | 排序权重，升序匹配，数值越小越先评估。相同 order 的规则按作用域 **全局 → 密钥组 → 访问密钥** 的顺序评估（即同 order 下全局规则先命中）；因此密钥级或组级规则若要覆盖同 order 的全局规则，必须填更小的 order。 |
| **Header Name** | 要匹配的 HTTP 请求头名称（如 `User-Agent`、`X-Forwarded-For`）。大小写不敏感。Match Type 为 `any` 时**留空**（该类型不检查请求头）。 |
| **Match Type** | 匹配方式，见下表。 |
| **Match Value** | 匹配值。`exists` / `absent` / `any` 类型不需要填。 |
| **Action** | `allow`（放行）或 `deny`（拒绝，返回 403）。 |
| **Scope** | 作用域：`Global`（所有请求）、`Key`（指定一个或多个 access key）、`Group`（指定一个或多个密钥组）。Key 和 Group 互斥，支持多选。 |
| **Scope value** | 当 Scope 为 Key 或 Group 时，从下拉列表选择目标密钥或密钥组。 |
| **Enabled** | 规则开关。禁用后规则在加载时被忽略，不影响其他规则。 |

> 表里是控制台 UI 标签，下文配置示例用底层字段名，对应：Header Name = `condition_key`、Match Type = `condition_type`、Match Value = `condition_value`、Action = `action`、Scope = `scope`、Order = `sort_order`。

## 匹配类型

| 类型 | 说明 | 示例 |
|------|------|------|
| `exact` | 请求头值与 Match Value 完全相等 | `User-Agent` = `BadBot/1.0` |
| `prefix` | 请求头值以 Match Value 开头 | `User-Agent` 以 `python-requests/` 开头 |
| `regex` | 请求头值匹配正则表达式 | `User-Agent` 匹配 `(?i)curl|wget|scrapy` |
| `exists` | 请求头存在（不论值） | 检查 `X-Custom-Header` 是否存在 |
| `absent` | 请求头不存在 | 检查 `Authorization` 是否缺失 |
| `any` | 条件恒真，不检查任何请求头，规则仅靠作用域（Scope）生效 | 对某 key「全部放行/拒绝」：Match Type=`any` + Scope=`Key`，Header Name 留空 |

字段语义、匹配类型、regex 匹配语义和评估逻辑的完整参考见 [Header ACL 规则字段](/zh-CN/reference/header-acl-rules.md)。

## 常见用法

- **屏蔽爬虫**：`condition_key = User-Agent`，`condition_type = regex`，`condition_value = (?i)scrapy|crawl|spider`，`action = deny`，`scope = Global`。
- **限制特定 IP 段**：`condition_key = X-Forwarded-For`，`condition_type = prefix`，`condition_value = 192.168.`，`action = deny`，`scope = Global`。⚠️ `X-Forwarded-For` 是**客户端可伪造**的头——只有当网关前面有一层你信任的反向代理会覆写它时才可靠；否则恶意客户端能随意填这个头绕过限制。真要做 IP 准入，优先靠网关直连看到的真实来源 IP（配合 `TRUSTED_PROXIES`），而非可伪造的 `X-Forwarded-For`。
- **仅允许带自定义头的请求**：`condition_key = X-Api-Token`，`condition_type = absent`，`action = deny`，`scope = Global`（缺少该头则拒绝）。
- **针对特定密钥组限制**：设置 `scope = Group`，选择目标密钥组，规则仅对该组内的密钥生效。
- **对某个密钥全部放行/拒绝（无需 header 条件）**：设置 Match Type = `any`、Scope = `Key` 并选择目标密钥，Header Name 留空——该规则对此密钥的**所有**请求生效。若要用它覆盖一条全局 deny，把它的 order 设得比全局规则更小（同 order 时全局优先）。

## 验证规则是否生效

配好规则后用 curl 发对照请求验证。以上面「屏蔽爬虫」规则（`User-Agent` 含 `scrapy` → deny）为例：

```bash
# 对照组：正常 UA，期望放行（200）
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:7890/v1/chat/completions \
  -H "Authorization: Bearer <你的访问密钥>" \
  -H "User-Agent: my-app/1.0" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}'

# 实验组：命中 UA，期望拦截（403）
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:7890/v1/chat/completions \
  -H "Authorization: Bearer <你的访问密钥>" \
  -H "User-Agent: scrapy/2.11" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}'
```

分别返回 200 与 403 即规则生效。若实验组仍 200，检查：① 规则 `Enabled` 是否开着；② 是否被更小 order 的 allow 规则先命中；③ 作用域（Scope）是否覆盖了这把密钥。

## 复杂配置示例

下面几个例子演示如何用 `regex` 与 first-match-wins 语义组合出更复杂的策略。字段以 `key = value` 行内给出，`order` 越小越先评估。

### 示例 1：一条规则表达「包含 A 或 包含 B 或以 C 开头」

```text
condition_key   = User-Agent
condition_type  = regex
condition_value = aaaa|bbb|^aaa
action          = deny
scope           = Global
```

- `aaaa` 段：值**包含** `aaaa`（不锚定 = contains）。
- `bbb` 段：值**包含** `bbb`。
- `^aaa` 段：值**以** `aaa` **开头**（首锚只作用于本分支）。
- 三段用 `|` 连接，命中任一段即 deny。等价于把三条规则合并成一条。

### 示例 2：大小写不敏感的多关键词包含

```text
condition_key   = User-Agent
condition_type  = regex
condition_value = (?i:scrapy|crawl|spider|bot)
action          = deny
scope           = Global
```

`(?i:...)` 让括号内的 alternation 忽略大小写，`Scrapy`、`CRAWL`、`Spider` 均命中。注意 `(?i)` 只作用于其包裹的范围，不影响规则其他部分。

### 示例 3：多值精确匹配（exact 做不到的场景）

`exact` 一次只能比对一个值。要「值恰好是 v1、v2、v3 之一」，用首尾双锚的 alternation：

```text
condition_key   = X-Api-Version
condition_type  = regex
condition_value = ^(v1|v2|v3)$
action          = allow
scope           = Global
```

`^...$` 把搜索语义收紧为完全匹配，`v1`、`v2`、`v3` 命中，`v10`、`xv1x` 不命中。

### 示例 4：用 contains 语义拦截含特定子串的值

引擎没有独立的 `contains` 类型，但不锚定的 `regex` 本身就是 contains。要拦截「`X-Debug` 头值里含 `debug` 子串」：

```text
condition_key   = X-Debug
condition_type  = regex
condition_value = debug
action          = deny
scope           = Global
```

例如 `debug` 命中 `debug`、`my-debug-flag`，但**不命中** `DEBUG`（值匹配区分大小写）；需忽略大小写时写 `(?i)debug`。

### 示例 5：白名单模式（default_action 兜底拒绝 + 单条放行）

需求：某密钥组只允许带受信前缀的客户端，其余一律拒绝。利用「无规则命中时回退到 default_action」：

1. 把该密钥组（或全局）的 `acl_default_action` 设为 `Deny`。
2. 加一条放行规则：

```text
order           = 10
condition_key   = User-Agent
condition_type  = regex
condition_value = ^trusted-
action          = allow
scope           = Group   （选中目标密钥组）
```

效果：值以 `trusted-` 开头 → 命中规则 → allow；其余值无规则命中 → 落到 default_action → deny。无需写一条「拒绝一切」的兜底规则。

### 示例 6：放行优先于拒绝（order 编排）

需求：拒绝常见爬虫，但放行自家的监控探针（其 UA 也含 `bot` 字样，会被爬虫规则误伤）。靠 first-match-wins，**放行规则的 order 必须更小**：

```text
# 规则 A：先放行自家探针
order           = 10
condition_key   = User-Agent
condition_type  = regex
condition_value = ^health-probe/
action          = allow
scope           = Global

# 规则 B：再拒绝爬虫
order           = 20
condition_key   = User-Agent
condition_type  = regex
condition_value = (?i:scrapy|crawl|spider|bot)
action          = deny
scope           = Global
```

`health-probe/bot-1` 先被规则 A 命中 → allow，不再评估规则 B；普通爬虫 UA 不命中 A，落到 B → deny。若把两条 order 写反，探针会被 B 先拦下。

### 示例 7：「不含某子串」需反向编排

正则引擎不支持 lookahead，「值**不含** `internal` 才放行」写不成单条正则。反向编排：

1. `acl_default_action = Deny`。
2. 加一条「含 `internal` 则放行」：`condition_type = regex`，`condition_value = internal`，`action = allow`。

语义即「含 internal → allow；否则（含其他值或不含）→ 无命中 → default deny」。若需求是「含 internal 反而拒绝、其余放行」，则把 default_action 设 `Allow`，再加一条 `internal` → `deny` 即可。

## 评估逻辑

1. 合并当前请求作用域内的所有规则：全局规则 + 当前 key 的 per-key 规则 + 当前 group 的 per-group 规则。
2. 按 `sort_order` 升序排列（数值越小越先评估；相同 order 时按作用域 全局 → 密钥组 → 访问密钥 顺序），跳过 `enabled = false` 的规则。
3. 逐条评估：按 `condition_type` 匹配（`any` 不读请求头，条件恒真；其余类型取请求头 `condition_key` 的值匹配）。
4. **首条命中**的规则决定结果：`deny` → 立即返回 403；`allow` → 放行到下一层。
5. 无规则命中时，按 default_action 链回退：per-key `acl_default_action` → per-group `acl_default_action` → 全局 `Allow`。

## 常见问题

**Q：客户端报 403 但密钥是对的？**
检查是否被 Header ACL 拦下。规则同时作用于推理路由和模型列表路由，可能客户端先调 `/v1/models` 时就被拦截。临时禁用相关规则或调高 order 排查。

**Q：规则改了不生效？**
规则编译失败时被跳过并记录告警。检查 `condition_value` 是否符合语法（不支持 lookahead / 回溯引用）。`condition_value` 长度上限 1024 字节。

**Q：怎么让某密钥组的规则覆盖全局规则？**
同 order 下全局先命中。要覆盖必须填更小的 order，或把全局规则改成 `enabled = false`。

**Q：怎么对某个密钥「无条件放行/拒绝」？**
Match Type=`any` + Scope=`Key` + Header Name 留空。规则仅靠作用域生效，对此密钥的所有请求生效。

**Q：模型列表调用也被拦截？**
是的，规则同时作用于 `/v1/models` 和 `/v1beta/models`。运维需知晓此行为，避免误配导致客户端无法获取模型列表。

**下一步**：[Header ACL 规则字段](/zh-CN/reference/header-acl-rules.md) 看 regex 语义和表达力边界的完整参考；[访问密钥与密钥组字段](/zh-CN/reference/access-keys-groups-fields.md) 看密钥组维度；[审计与安全配置](/zh-CN/reference/audit-and-security-config.md) 看限流与 IP 封禁。
