🕸️ Tailscale ACL 访问控制:织一张懂规则的虚拟内网

默认的 Tailscale 网络"谁都连得上",可一旦接入生产设备,你就该给虚拟内网织一张懂得"谁、能碰谁、碰哪些端口"的策略网——这就是 ACL。
Tailscale ACL 访问控制策略配置
1. 概述
ACL(Access Control List)是 Tailscale 网络的安全策略核心:决定谁(src)能访问什么目标(dst)的哪些端口。
- 官方管理界面:Admin Console → Access Controls(Policies),编辑的是"HujSON"文本。
- 格式:HuJSON(Human JSON),是 JSON 的超集:支持注释(
//、/* */)、结尾允许多余逗号。 - 生效:保存即实时下发到各节点(网络层过滤),改错可能导致断连,务必谨慎。
- 版本:既有旧
acls语法(无限期支持),也有新grants语法(新功能只加在 grants 上)。两者可共存,新配置建议直接用grants。
策略文件顶层可包含以下 sections:
| Section | 用途 |
|---|---|
grants |
网络层 + 应用层访问策略(新语法,优先使用) |
acls |
网络层访问策略(旧语法) |
ssh |
控制谁可以使用 Tailscale SSH |
hosts |
给 IP / 子网起别名 |
groups |
定义用户组 |
ipsets |
定义网段集合 |
tagOwners |
定义谁有权给设备打标签 |
autoApprovers |
定义谁可免审批宣告子网路由 / 出口节点 |
nodeAttrs |
给设备/用户附加属性(如 app connector) |
postures |
设备姿态(OS、版本等)规则 |
tests |
对策略做断言测试 |
2. 语法基础
2.1 源对象 src
| 类型 | 示例 | 含义 |
|---|---|---|
| 任意 | * |
所有 tailnet 设备(含已批准的共享) |
| 用户 | alice@example.com |
该用户的所有设备 |
| 组 | group:dev |
组内所有用户 |
| Tailscale IP | 100.100.123.123 |
指定设备 |
| 子网 | 192.168.1.0/24 |
子网内的 IP(经子网路由器) |
| 标签 | tag:dev |
打了该标签的所有设备 |
| Hosts 别名 | my-laptop |
hosts 中定义的名称 |
| Autogroup 成员 | autogroup:member |
tailnet 所有成员 |
| Autogroup 管理员 | autogroup:admin |
Tailscale 管理员 |
2.2 目标对象 dst(旧 acls 语法:host:ports)
| 类型 | 示例 | 含义 |
|---|---|---|
| 任意 | *:* |
无限制 |
| 用户 | alice@example.com:* |
该用户所有设备的全部端口 |
| 组 | group:dev:* |
组内所有设备 |
| IP | 100.100.123.123:22 |
单设备指定端口 |
| 子网 | 192.168.1.0/24:* |
子网内全部 |
| 标签 | tag:prod:* |
标签设备的全部端口 |
| 出口上网 | autogroup:internet:* |
通过出口节点访问互联网 |
| 自己 | autogroup:self:* |
允许 src 访问自己的设备 |
2.3 端口写法
tag:web:80 # 单端口
tag:web:80,443 # 多端口
tag:web:80-90 # 端口范围
tag:web:* # 全部端口
192.168.1.0/24:0-65535 # 全端口等价写法
2.4 新 grants 语法
grants 把端口从 dst 中分离到 ip 字段(支持应用层协议,如 tcp:22):
// 旧 acls
{
"action": "accept",
"src": ["group:dev"],
"dst": ["tag:web:80", "tag:web:443"]
}
// 新 grants(首选)
{
"src": ["group:dev"],
"dst": ["tag:web"],
"ip": ["80", "443"]
}
IP 取值示例:"*"(全放行)、"tcp:22"、"udp:53"、"80,443"。
3. 各配置块详解
3.1 hosts —— IP 别名
策略文件不认设备"名称",只认 IP。想用名字,需先在 hosts 里定义:
{
"hosts": {
"my-laptop": "100.64.12.34",
"my-nas": "100.84.25.76",
"home-lan": "192.168.100.0/24"
}
}
3.2 groups —— 用户组
{
"groups": {
"group:admin": ["admin@example.com"],
"group:dev": ["alice@example.com", "bob@example.com"]
}
}
3.3 tags 与 tagOwners —— 设备按用途分类
设备打 tag 后即与个人账号"解耦",策略按用途而非人归属来管理。tag 必须先声明在 tagOwners 才能使用。
{
"tagOwners": {
"tag:prod": ["autogroup:admin"],
"tag:dev": ["autogroup:admin", "group:dev"],
"tag:ci": ["autogroup:admin"],
"tag:subnet-router": ["autogroup:admin"]
}
}
- 标签规则:必须以
tag:开头。 - 打标签方式(服务器上以标签身份认证):
tailscale up --auth-key=<key> --advertise-tags=tag:prod # 或在 Admin Console 设备菜单 "Edit tags"
易踩坑:打了 tag 的设备不属于其所属用户的
src/dst匹配范围。例如给dev1下的guest-2打上tag:test后,src: ["dev1"]的规则就不覆盖guest-2了;需要它在dst中用tag:test显式声明才能访问。
3.4 autoApprovers —— 路由/出口节点免审批
默认每次设备宣告子网路由或出口节点,都需管理员在控制台点"批准"。autoApprovers 可省去手动步骤:
{
"autoApprovers": {
"routes": {
"192.168.100.0/24": ["tag:subnet-router"],
"10.0.0.0/8": ["group:dev", "alice@example.com"]
},
"exitNode": ["tag:exit"]
}
}
- 值可以是用户邮箱、组、
autogroup或 tag。 - 建议用标签作为 autoApprover(避免因用户设备下线导致路由自动停止宣告)。
- 内部路由范围(RFC1918)签署本组设备通常安全;宽范围
0.0.0.0/0要精细限定到具体 tag。
3.5 ssh —— Tailscale SSH 授权
{
"ssh": [
// 所有人能 SSH 回自己
{"action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self"], "users": ["autogroup:nonroot", "root"]},
// 开发组能 SSH 进开发机,只用 deploy 账号
{"action": "accept", "src": ["group:dev"], "dst": ["tag:dev"], "users": ["deploy"]}
]
}
注意事项:
- 需配合目标主机执行
tailscale set --ssh。 users允许值:具体用户名、autogroup:nonroot(排除 root)、root。- 仅支持连接 Linux / macOS 开源版主机,Synology / QNAP 不支持。
3.6 tests —— 策略断言(保护关键连通性)
{
"tests": [
{"src": "my-laptop", "proto": "tcp", "accept": ["my-nas:22"], "deny": ["my-nas:443"]}
]
}
保存策略时自动校验,防止误删关键放行。accept 用 host:port 按旧格式写,一个写错就保存失败,能及时发现。
3.7 其他 sections(了解即可)
ipsets:把多个子网/网段打包成一个名称。nodeAttrs:给设备附加属性(App Connector 域名列表、允许 IPv4/IPv6 开关等)。postures:基于 OS 版本是否最新、是否经过密码认证等做准入。policy:可整体禁用 IPv4 等网络策略。
4. 完整示例
一个覆盖常见场景的策略文件(按 grants 新语法):
{
// 1. IP 别名
"hosts": {
"my-laptop": "100.64.12.34",
"my-nas": "100.84.25.76",
"home-lan": "192.168.100.0/24"
},
// 2. 用户组
"groups": {
"group:admin": ["admin@example.com"],
"group:dev": ["alice@example.com", "bob@example.com"]
},
// 3. 标签申明:谁有权打
"tagOwners": {
"tag:dev": ["autogroup:admin", "group:dev"],
"tag:prod": ["autogroup:admin"],
"tag:ci": ["autogroup:admin"],
"tag:subnet-router": ["autogroup:admin"],
"tag:exit": ["autogroup:admin"]
},
// 4. 放行规则
"grants": [
// 每个人都能连自己的设备
{"src": ["autogroup:member"], "dst": ["autogroup:self"], "ip": ["*"]},
// 只有我自己的笔记本能 SSH 到 NAS
{"src": ["my-laptop"], "dst": ["my-nas"], "ip": ["tcp:22"]},
// 开发组能访问开发机的 SSH + Web
{"src": ["group:dev"], "dst": ["tag:dev"], "ip": ["tcp:22", "tcp:80", "tcp:443"]},
// 只有管理员能碰生产环境全部端口
{"src": ["group:admin"], "dst": ["tag:prod"], "ip": ["*"]},
// CI 机器只能部署(SSH)
{"src": ["tag:ci"], "dst": ["tag:prod"], "ip": ["tcp:22"]},
// 管理员可访问家中局域网段(经子网路由器)
{"src": ["group:admin"], "dst": ["home-lan"], "ip": ["*"]},
// 开发组可通过出口节点访问互联网
{"src": ["group:dev"], "dst": ["autogroup:internet"], "ip": ["*"]}
],
// 5. Tailscale SSH
"ssh": [
{"action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self"], "users": ["autogroup:nonroot", "root"]},
{"action": "accept", "src": ["group:dev"], "dst": ["tag:dev"], "users": ["deploy"]}
],
// 6. 路由 / 出口节点自动批准
"autoApprovers": {
"routes": {"192.168.100.0/24": ["tag:subnet-router"]},
"exitNode": ["tag:exit"]
},
// 7. 保护关键链路
"tests": [
{"src": "my-laptop", "proto": "tcp", "accept": ["my-nas:22"], "deny": ["my-nas:443"]}
]
}
5. 在 Headscale 中配置 ACL
Headscale(自建控制面)也支持同一套 ACL 语法:
# /etc/headscale/config.yaml
policy:
mode: file # file | database
path: /etc/headscale/acl.hujson
mode: file:把规则写在path指向的文件(HuJSON 或 YAML 均可),改文件后重启/重载生效。mode: database:规则存在数据库,用 CLI 管理:headscale policy set -f /tmp/policy.hujson headscale policy get- 验证规则:
headscale configtest。 - Headscale 中用
用户(namespace)替代官方邮箱:如src: ["default"]。
6. 常见坑与排查
| 现象 | 原因 / 处理 |
|---|---|
| 保存策略就报错 | HuJSON 语法错、autogroup:member 与 autogroup:members 混用(统一单数)、tag 未在 tagOwners 声明、dst 直接写了设备名而非 hosts 别名 |
| 打 tag 的设备突然访问不了 | tag 设备不属于用户名下的匹配范围,需显式用 tag:xxx |
| 出口节点能 SSH 但上不了网 | 缺 dst: ["autogroup:internet"] 的放行规则 |
| 子网路由通了但策略拦着 | 在 ACL 中把子网 CIDR 加入 dst(如 192.168.1.0/24:*) |
| Tailscale SSH 连不上 | 目标机需 tailscale set --ssh,且 ACL 有对应 ssh 规则 |
| 验证当前生效策略 | tailscale debug prefs 查看下发的 ACL;状态页 Alert 会提示 ACL 改动风险 |
最佳实践:策略文件纳入版本管理(Git)便于审计回滚;改动前先用 tests 钉住关键连通性;个人/家庭网络保持默认"全互访 + 自己"即可,复杂的 ACL 多用于多用户团队。
评论交流