Tailscale 是一个使用 WireGuard 协议的零配置自动组网工具,利用 Tailscale 可以很方便的将自己的设备互相连接并且使用 ACL 控制访问权限。以下内容基于我个人对 Tailscale 的理解以及使用体验。
下文内容主要围绕 Headscale 控制器的安装、配置,客户端的设置,drep 转发服务器配置以及 ACL 控制。
Contents
Headscale 控制器
我使用的 Tailscale 控制端是开源的 Headscale,它是官方控制端的不完整实现,但不依赖于 Tailscale 官方账号。因此用户拥有完整的控制权。
Headscale 的项目地址:https://github.com/juanfont/headscale
该控制器可以运行于各种 Linux 平台以及 OpenBSD 平台。安装方式可以参考官方文档。
Headscale 主要依赖配置文件 /etc/headscale/config.yaml
和数据库 /var/lib/headscale/db.sqlite
,在升级或者迁移 Headscale 控制器的时候也只需要保证上述文件一致即可。
Headscale 基本配置
Headscale 最少需要一个 https 端口作为客户端连接使用的 Endpoint,因此至少需要配置 server_url
和 listen_addr
。使用 https 协议则需要配置 TLS 或者外部的反代。以下方式是通过 Nginx 反代实现 https 协议的样例。
map $http_upgrade $connection_upgrade { default keep-alive; 'websocket' upgrade; '' close; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name headscale.example.com; # SSL ssl_certificate /etc/nginx/ssl/headscale.example.com/chain.pem; ssl_certificate_key /etc/nginx/ssl/headscale.example.com/privkey.pem; #ssl_trusted_certificate /etc/nginx/ssl/headscale.example.com/chain.pem; ssl_protocols TLSv1.2 TLSv1.3; # . files location ~ /\\.(?!well-known) { deny all; } location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $server_name; proxy_redirect http:// https://; proxy_buffering off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; } } server { listen 80; listen [::]:80; server_name headscale.example.com; # ACME-challenge location /.well-known { alias /var/www/html/.well-known; allow all; default_type "text/plain"; autoindex on; } location / { return 301 https://headscale.example.com$request_uri; } }
Headscale 的配置文件则修改为
server_url: https://headscale.example.com:443; listen_addr: 127.0.0.1:8080
以上设置可以让 Nginx 反代运行于本地 8080 端口的 Headscale。设置完成后即可启用 Headscale
sudo systemctl enable --now headscale
在开始连接 Tailscale 客户端之前,还需要为 Headscale 创建一个用户。在 Headscale 的默认规则下用户是互相隔离的。
headscale users create myfirstuser
列出已存在的用户:
headscale users list ID | Name | Created 1 | myfirstuser | 2023-12-01 08:00:00
Tailscale 客户端接入
Linux 客户端
可以通过官方文档找到安装指令,也可以通过下载静态编译的可执行文件安装。
假设通过包管理器安装了 Tailscale 客户端,则可以通过如下指令启用 Tailscale 后台服务。
sudo systemctl enable --now tailscaled
随后可以通过 Tailscale 命令行接入 Headscale
tailscale up --login-server=https://HEADSCALE_PUB_DOMAIN --accept-routes=true --accept-dns=false
执行上述命令后,命令行会返回如下信息,等待授权:
To authenticate, visit: https://HEADSCALE_PUB_DOMAIN:443/register/nodekey:566be15a7e65b1e53a76b0b3e55d2190f0cbbbc79f9ea8140154658ec29c605b
以 nodekey
开头的内容就是注册到 Headscale 所需的 Key,再次回到 Headscale 所在的终端,输入
headscale --user myfirstuser nodes register --key YOUR_MACHINE_KEY
此时就完成了将 Tailscale 客户端连入 Headscale 并分配给 myfirstuser 的操作。如果需要修改 Tailscale 的配置,如宣告 exit-node,则可以使用 headscale set -advertise-exit-node
设置 。
Windows 客户端
在下载并且完成 Windows 客户端的安装后,Tailscale 的后台服务会自动启动。Headscale 官方文档提示我们使用修改注册表的方式修改登陆服务器,但实际上我们只需要通过 Tailscale 命令行即可配置客户端:
tailscale up --login-server=https://HEADSCALE_PUB_DOMAIN --accept-routes=true --accept-dns=false --unattended
QNAP 客户端
该客户端的行为与普通 Linux 发行版类似,但是由于 QNAP 并不会将 qpkg 中的二进制加载到 $PATH
环境变量中,因此需要创建一个符号链接:
sudo ln -s /share/CACHEDEV1_DATA/.qpkg/Tailscale/tailscale /usr/local/bin/tailscale
或是直接在 qpkg 的目录中运行
cd /share/CACHEDEV1_DATA/.qpkg/Tailscale/ sudo ./tailscale
流量转发
在完成上文的操作后,一个对等网络已经生成,各个客户端之间可以通过 WireGuard 协议进行加密的连接,这样的网络被称为 Tailnet。与原生的 WireGuard VPN 类似,除了形成点对点连接以外,Tailscale 还可以转发流量。
以 Linux 平台为例,在安装了 Tailscale 客户端并且接入 Tailnet 网络的设备,可以将该设备连接的网段的流量转发。
首先需要打开目标设备的 IPV4 和 IPV6 转发
echo 'net.ipv4.ip_forward = 1' | tee /etc/sysctl.d/ipforwarding.conf echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.d/ipforwarding.conf sysctl -p /etc/sysctl.d/ipforwarding.conf
假设我们需要转发 192.168.100.0/24
。那么就需要通过该客户端向整个 Tailnet 宣告这段路由。
tailscale set --advertise-routes=192.168.100.0/24
接下来我们需要在 Headscale 中允许向所有客户端广播这条路由,那么之前设置过 --accept-routes=true
的客户端就会收到这条路由并通过这台目标设备访问这些 IP 地址。
查看 Headscale 中存在的路由,新增的路由默认是禁用的:
headscale route list ID | Machine | Prefix | Advertised | Enabled | Primary 1 | efs-ws | 192.168.100.0/24 | true | false | false
开启路由:
headscale route enable -r 1
此时检查路由表则可以看到这条新增路由
ip route show table 52 | grep "192.168.100.0/24" 192.168.100.0/24 dev tailscale0
类似的,你还可以申明 --advertise-exit-node=true
将客户端变为 exit-node
。这对于 Headscale 来说就是客户端向 Tailscale 宣告了一条 0.0.0.0/0
和 ::/0
的路由,因此依然需要在 headscale 命令行中启用这条规则,方法同上文。与普通的路由不同,这条路由并不会对启用了 —accept-routes=true
的设备生效,你需要启用 --exit-node IPaddr
指定客户端使用 exit-node 模式。此时路由表中会生成一条默认路由,让该设备所有的流量都通过 exit-node。如果同时启用 --exit-node-allow-lan-access
则还会丢弃本地 LAN 的路由,即不干扰 LAN 的路由。
Tailscale 自定义中继服务器
Tailscale 使用 DERP(Designated Encrypted Relay for Packets) 进行中继。它运行于 HTTPS 协议上,这能够适应大部分的网络状况。Tailscale 客户端总是先通过 DERP 服务器建立连接以保证服务总是可用,同时它会尝试在目标客户端之间建立直接连接,如果成功建立 UDP 直连,那么客户端之间的连接也会转换为点对点连接。DERP 服务器并没有本地的私钥,因此它是直接转发 WireGuard 的加密数据包,无需担心安全性。
当我们不满足于官方提供的 DERP 服务器时,则可以根据官方文档自建私有的 DERP 服务器,通过自建更合适的服务器可以提高在复杂 NAT 环境下连接的成功率,如果 NAT 穿透失败,它也可以提供更高的转发带宽。
自建 DERP 服务器需要满足如下条件:
- 域名,并将域名解析到服务器的 IP 地址
- 云服务器,或者有公网地址的设备
- 为了防止其他用户使用你的中继服务器导致以外的流量开销,该服务器还需要接入你的 Teilnet 网络
假设服务器已经接入 Teilnet,并且域名解析到 derp01.example.com,则可以通过如下的 docker-compose.yml 进行设置
version: "3" services: derper: image: fredliang/derper:latest container_name: derper restart: always ports: - 3477:3477 - 3478:3478/udp volumes: - /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock environment: - DERP_ADDR=:3477 - DERP_VERIFY_CLIENTS=true - DERP_DOMAIN=derp01.example.com
其中如果服务器仅运行一个 DERP Server,则可以修改配置为 DERP_ADDR=:443
此时该 Docker 镜像会使用内嵌的 acme 为服务器申请证书,而使用非 443 端口,如 DERP_ADDR=:3477
,则会禁用自动申请,你可以通过 Nginx 或者其他反向代理为 DERP 服务加上 TLS 证书。
在完成设置后可以访问 https://derp01.example.com,如果设置正确,则应当显示如下的内容
随后需要将该服务器加入配置。一种是 Tailscale 官方的设置,它通过 ACLs 进行设置
"derpMap": { "OmitDefaultRegions": true, "Regions": { "900": { "RegionID": 900, "RegionCode": "RG", "RegionName": "REGION", "Nodes": [ { "Name": "CURTISASIA-DERP", "RegionID": 900, "HostName": "[derp01](https://derp01.example.com).example.com", "DERPPort": 443, "STUNPort": 3478, }, ], }, }, },
而在 Headscale 中则可以设置为 derp.yaml:
# /etc/headscale/derp.yaml regions: 900: regionid: 900 regioncode: RG regionname: REGION nodes: - name: 900a regionid: 900 hostname: [derp01](https://derp01.example.com).example.com stunport: 3478 stunonly: false derpport: 443
此外还需要修改 Headscale 的配置文件:
# /etc/headscale/config.yaml derp: # List of externally available DERP maps encoded in JSON urls: - https://controlplane.tailscale.com/derpmap/default # Locally available DERP map files encoded in YAML # # This option is mostly interesting for people hosting # their own DERP servers: # https://tailscale.com/kb/1118/custom-derp-servers/ # # paths: # - /etc/headscale/derp-example.yaml paths: - /etc/headscale/derp.yaml # If enabled, a worker will be set up to periodically # refresh the given sources and update the derpmap # will be set up. auto_update_enabled: true # How often should we check for DERP updates? update_frequency: 24h
随后重启 headscale 服务:
systemctl restart headscale
在 Tailscale 客户端中使用如下命令则可以查看目前使用的 DERP 服务器:
tailscale netcheck Report: * UDP: true * IPv4: yes, xxxxxxxxxx:34289 * IPv6: yes, xxxxxxxxxx:51894 * MappingVariesByDestIP: true * HairPinning: false * PortMapping: UPnP, NAT-PMP, PCP * Nearest DERP: REGION * DERP latency: - RG: 13.2ms (REGION) - ams: 16.4ms (Amsterdam) - nl: 19.7ms (Nederland) - headscale: 22.2ms (Headscale Embedded DERP) - fra: 23.1ms (Frankfurt) - fr: 23.1ms (France) - par: 26.7ms (Paris) - lhr: 29.8ms (London)
测试 DERP 服务器:
tailscale ping 100.64.8.8 pong from efs-nas (100.64.8.8) via DERP(RG) in 241ms pong from efs-nas (100.64.8.8) via DERP(RG) in 241ms pong from efs-nas (100.64.8.8) via DERP(RG) in 244ms
说明建立连接时使用了自建的 DERP 服务器。
使用访问控制表 ACLs
上文已经提到 Tailscale 需要通过 ACLs 添加自定义 DERP 服务器,而 ACLs 更重要的作用是对设备进行访问控制。可以通过设置合理的规则,实现设备之间可控的访问。例如单向访问,允许或者禁止访问私有 IP 地址,仅允许设备访问特定 IP 地址的特定端口。
基本的功能可以参考官方文档,由于 Headscale 0.22.3 依然没有 autogroup:internet
因此需要添加如下的规则,使得所有用户均可使用 exit-node 访问公网,但不能访问私有 IP 地址:
{ // groups are collections of users having a common scope. A user can be in multiple groups // groups cannot be composed of groups "groups": { "group:user": ["user1", "user2"], }, "acls": [ // we alllow user to use outband server { "action": "accept", "src": ["group:user"], "dst": [ "tag:outband-servers:*", // Exclude Private IPv4 ranges "0.0.0.0/5:*", "8.0.0.0/7:*", "11.0.0.0/8:*", "12.0.0.0/6:*", "16.0.0.0/4:*", "32.0.0.0/3:*", "64.0.0.0/3:*", "96.0.0.0/6:*", "100.0.0.0/10:*", "100.128.0.0/9:*", "101.0.0.0/8:*", "102.0.0.0/7:*", "104.0.0.0/5:*", "112.0.0.0/4:*", "128.0.0.0/3:*", "160.0.0.0/5:*", "168.0.0.0/6:*", "172.0.0.0/12:*", "172.32.0.0/11:*", "172.64.0.0/10:*", "172.128.0.0/9:*", "173.0.0.0/8:*", "174.0.0.0/7:*", "176.0.0.0/4:*", "192.0.0.0/9:*", "192.128.0.0/11:*", "192.160.0.0/13:*", "192.169.0.0/16:*", "192.170.0.0/15:*", "192.172.0.0/14:*", "192.176.0.0/12:*", "192.192.0.0/10:*", "193.0.0.0/8:*", "194.0.0.0/7:*", "196.0.0.0/6:*", "200.0.0.0/5:*", "208.0.0.0/4:*", "224.0.0.0/3:*" ] }, // We still have to allow internal users communications since nothing guarantees that each user have // their own users. { "action": "accept", "src": ["group:user1"], "dst": ["group:user1:*"] }, { "action": "accept", "src": ["group:user2"], "dst": ["group:user2:*"] } ] }
该 ACL 规则并不支持对 IPv6 私有地址的管理,因此与 Tailscale 相同的功能仍然需要等待 Headscale 后续版本的实现。
简单总结
Tailscale 是一个很好的私有网络搭建方案,它所带来的零配置搭建体验较好,而通过扩展的 ACLs 也可以对网络提供完整的控制能力,对于个人小规模的建立私有网络是一个不错的方案。