使用Cloudflare Teams实现零信任网络访问

Posted by Panda2134's Blog on January 27, 2022

很久以前就有听说 Cloudflare Teams 这一零信任网络服务,且其免费套餐足以个人用户使用,不过因为各种原因一直觉得必要性不大,没有进行配置;在2021年底和2022年初接连爆出 Grafana 目录遍历Log4j2 RCEPwnKit 提权 等一系列惊人漏洞后,感觉可能还是有必要提高一下自己手头基础设施的安全等级。考虑到手头的服务中,有一个直接暴露在公网的 Grafana 和一个 Minecraft 服务器,决定先从访问鉴权做起,防止把此类服务暴露在公网。

Disclaimer: Cloudflare Teams 的服务器位于新加坡和美国洛杉矶,从国内访问可能较慢。一般来说,用于给托管在海外的服务器提供安全访问通道并无明显性能问题,但对于国内的服务器则可能有较大延迟。这一情况下,可以考虑采用 n2n 等点对点方案代替,或者采用其他加速方案,但请考虑可能带来的合规问题。

配置 Cloudflare Teams

正如其名,如果想要使用这一服务,你需要有一个解析托管在 Cloudflare 的域名;幸运的是,我手头的两个域名都恰好托管在 Cloudflare 上。如果你的域名仍然在使用其他解析服务,需要先切换到使用 Cloudflare 再继续。

在 Cloudflare Dashboard 点击 域名 > 左侧边栏 Access,首先添加一种 One-Time Pin 以外的登录方法,我图省事就选择了 GitHub。用这个凭据即可认证受到 Teams 保护的服务,所以务必注意其安全性。

Cloudflare Teams 大致支持3类服务:

  • 基于 Cloudflare Edge Network 的网站代理,官方称之为 Application,通过 Cloudflare 的网络对访问者鉴权后,把流量转发给后端服务,一切都在浏览器中进行。
  • 基于 Cloudflare Warp 的 Warp Network,类似传统的虚拟专用网业务,需要用户运行单独的 Warp 应用程序。
  • 基于 DNS over HTTPS / DNS over TLS 的网络级代理,类似带有虚拟专用网功能的路由器。

而将应用程序连接到 Cloudflare Edge Network 又有下列方法

  • 采用 cloudflared 守护程序,主动建立到 Cloudflare Edge Network 的隧道(即 Argo Tunnel)。
  • 暴露在公网,但对传入连接进行 JWT 验证以确保其来自 Cloudflare。

理论上,使用 cloudflared 可以把任何防火墙后的服务暴露到公网,类似于让 Cloudflare 的网络替你做了端口转发、HTTPS 解密等一系列操作。

实例1:配置 Grafana

之前 Grafana 是通过一层反向代理暴露在公网的,由反向代理处理 HTTPS 流量;现在只需要把反向代理放在 Cloudflare 上即可。

具体做法是,创建一个 Argo Tunnel,然后设置 Tunnel 的 Ingress 服务为本地的反向代理目标端口(如 localhost:8080),最后在 Teams Dashboard 配置一个 Application.

参考文档,在服务器上安装 cloudflared 软件,配置一个 Tunnel:

$ sudo cloudflared tunnel login # 登录 Cloudflare 账户
$ sudo cloudflared tunnel create grafana # 创建 tunnel,请记录输出的UUID,此后记为 <隧道UUID>
$ sudo mkdir -p /root/.cloudflared
$ sudo vim "/root/.cloudflared/config.yml"

在上述 VIM 打开的文件中,如下配置 Grafana:

tunnel: <隧道UUID>
credentials-file: /root/.cloudflared/<隧道UUID>.json
ingress:
  - hostname: grafana.example.site # 你的服务域名,之后会用到
    service: http://localhost:8080 # 8080为本机Grafana端口
  - service: http_status:404 # 不符合上述要求的,返回 404

再到 Cloudflare Dashboard 中修改 grafana.example.site 的 DNS 记录为 CNAME,值为 <隧道UUID>.cfargotunnel.com。(也可以使用 cloudflared tunnel route dns <隧道UUID> <域名> 这一指令)

启动 Tunnel 服务:

$ sudo cloudflared service install
$ sudo systemctl enable --now cloudflared

最后在 Teams Dashboard 选择添加 Self-hosted Application,选择刚才的域名,并进行适当的权限设置即可。此时访问你指定的域名,则会出现如下窗口:

image-20220128003619979

在完成认证后,才可以进入 Grafana 的登录页,最大限度保证了应用程序的安全。

实例2:配置内网访问

现有一个 192.168.1.0/24 的局域网,需要允许通过 Warp Client 的方式进行访问。为此,我们在局域网内的一台设备安装 cloudflared,并且配置好 warp routing,即可使用户得以访问内网内的资源。

内网通常有自己的 DNS 服务器,为此,除了运行 Warp Client 外,还可能需要在联网的设备进行额外的设置。目前可以参考这份文档;之后也可以采用 Cloudflare 提供的 DNS 服务(尚未发布)。

首先,需要在 Teams Dashboard 进行 Split Tunnels 的配置;Settings > Network > Firewall > Split Tunnels 选择 Include IPs and Domains,并且点击 Manage 设置仅仅包括需要代理的内网 IP.

然后类似上文安装 cloudflared,但如下配置 config.yml

tunnel: <隧道UUID>
credentials-file: /root/.cloudflared/<隧道UUID>.json
warp-routing:
  enabled: true

再配置 cloudflared 的路由表:

$ sudo cloudflared tunnel route ip add <CIDR网段> <隧道UUID>

最后启动隧道即可。在自己的设备上安装启用 Cloudflare Warp,即可正常上网。

由于国内网络环境众所周知的原因,Cloudflare Warp 经常无法和其 API 通信完成注册设备;遇到此类情况请自行解决。

实例3:配置 SSH 远程登录

由于使用 Cloudflare 内嵌在浏览器的 SSH 客户端,SSH远程登录的情景和实例1类似,只是反向代理端口从 HTTP 服务变成了一个一般的 TCP 端口 (即 22)。默认配置下,完成 Cloudflare Teams 鉴权后还需要采用 SSH Key 进行鉴权,为了减少一次鉴权,可以考虑配置 Short-lived certificate,这样在连接后,即可通过 cloudflared 自动签发的证书完成鉴权。

首先修改实例1中的 config.yml 如下:

tunnel: <隧道UUID>
credentials-file: /root/.cloudflared/<隧道UUID>.json
ingress:
  - hostname: server.example.site # 你的服务域名
    service: ssh://localhost:22 #
  - service: http_status:404 # 不符合上述要求的,返回 404

并且在 Teams Dashboard 创建 Application 时,在 Cloudflared settings 内勾选 “enable automatic cloudflared authentication”,选择 Browser rendering 为 SSH.

接着,参考文档完成 ssh 配置即可。

文档中记录的步骤设置 SSH 为 PubkeyAuthentication 认证方式,并且通过设置 TrustedUserCAKeys,让 Cloudflare 作为 CA 来签发用户登录所用 Key。这样,即可把 Cloudflare Teams SSO 的认证凭据对接给 OpenSSH,从而允许用户成功登录。这里 sshd 对 CA Key 的检查是经典的公私钥体系操作:只需要用户出示的 Public key 由指定的 CA 签发即可。

其实,ssh 不仅支持以这种方式进行用户鉴权,还支持对服务器的公钥进行签名,从而实现可信的服务器鉴权。在签名服务器的 Host Key 后,需要配置客户端信任签发所用 CA,此后客户端连接服务器就不会遇到 Unknown Host 的提示了。因为服务端的真实性,已经由 CA 的签名所证明。

这一套机制非常像 TLS 中的服务端证书和客户端证书,不过有趣的是,大多数 TLS 流量只有服务端证书签名,而不需要客户端证书,而 SSH 的公钥体系中,常常是对客户端证书进行签名,服务端真实性则采用所谓 known_hosts 文件进行记录管理。

参考资料