今日总结 – 在 SPIRE 中用 SSH 证实节点身份

前面关于 SPIRE 的内容中,介绍了使用 JOIN Token 证实节点身份的方法。这种方法比较简易,但是完全依赖 SPIRE Server/Agent 的“内循环”,并不利于外部管理,同时每次节点更新,都要照本宣科的重来一遍。对于动态集群来说,这种方式并不理想,SPIRE 包含了面向 OpenStack、几大公有云以及 TPM 等的花钱证实节点身份的方案;除了这些之外,还有个经济型的证实方法——使用 SSH。

我们一般使用的免密登录 SSH 方案通常是点对点的,总结来说就是服务器和客户端各自有各自的公私钥,互相进行信任操作:

  1. SSHD 会自动生成服务器端的公私钥
  2. 客户端通常使用 ssh-keygen 命令生成自己的公私钥
  3. 客户端将服务器端的证书脚印加到自己的 know_hosts 文件里面,代表信任该地址和证书的组合
  4. 服务器将客户端的公钥加到服务侧特定用户的 authorized_keys 文件之中,代表认可以该密钥作为特定用户的身份证明。

不难看出,这个过程实际上是跟前面的 JOIN Token 方式是对等的,并不会提升节点证实过程的可管理性。因此 SPIRE 的 SSH 插件要求使用基于 CA 的 SSH 方法。

用 CA 进行 SSH 认证

这种方式比上面的点对点认证方式稍微复杂一些。主要区别在于:

  1. 主机身份和用户身份都用 CA 进行签署
  2. 同样地,主机和用户身份的互信,也是通过对 CA 的信任完成

大概要完成几个工作:

  1. 创建节点 CA 证书,SSH 客户端信任该 CA 证书
  2. 用节点 CA 签发主机证书,并将服务端证书记录在 SSHD 的配置文件中。
  3. 创建客户端 CA 证书,SSH 服务端信任该 CA 证书
  4. 使用客户端 CA 签发用户证书,以此作为登录凭据。

例如 ChatGPT 告诉我的步骤是这样的:

几个关键的命令:

生成并配置 HostKey

下面的命令可以用于 SSHD 初始化,利用 CA 生成 HostKey:

ssh-keygen -s /etc/ssh/ca \
     -I "$(hostname --fqdn) host key" \
     -n "$(hostname),$(hostname --fqdn),$(hostname -I|tr ' ' ',')" \
     -V -5m:+3650d \
     -h \
     /etc/ssh/ssh_host_rsa_key.pub \
     /etc/ssh/ssh_host_dsa_key.pub \
     /etc/ssh/ssh_host_ecdsa_key.pub

查看一下生成的证书内容:

$ ssh-keygen -L -f ssh_host_rsa_key-cert.pub
ssh_host_rsa_key-cert.pub:
        Type: ssh-rsa-cert-v01@openssh.com host certificate
        Public key: RSA-CERT SHA256:[...]
        Signing CA: RSA SHA256:[...] (using rsa-sha2-512)
        Key ID: "ssh"
        Serial: 0
        Valid: from 2022-12-16T08:12:02 to 2032-12-13T08:17:02
        Principals:
                ssh
                ssh
                10.211.55.9
                fdb2:2c26:f4e4:0:21c:42ff:fe2a:18c4
        Critical Options: (none)
        Extensions: (none)

配置 HostKey

生成主机凭据之后,将证书和密钥信息加入 /etc/ssh/sshd_config

HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub

让客户端信任主机 CA

和前面提到的 FingerPrint 方式类似,把 CA 证书公钥加入到客户端的 ~/.ssh/know_hosts 之中,例如:

@cert-authority * ssh-rsa ...AAAAB3NzaC1yc2EAAAADAQABAAABgQCb... someone@ssh

生成客户端证书

和前面生成主机身份证书的情况类似,这次去掉了 -h 参数:

ssh-keygen -s /etc/ssh/ca \
    -I "$(whoami)@$(hostname --fqdn) user key" \
    -n "$(whoami)" \
    -V -5m:+3650d \
    ~/.ssh/id_rsa.pub

服务端信任客户端证书 CA

同样在 /etc/ssh/sshd_config 配置中加入 TrustedUserCAKeys,具体取值为用户 CA 的公钥文件名。

完成这些内容之后,如果使用新的身份证书登录成功,则代表前置任务完成。否则可以参考以下材料:

  • RHEL:
    Using OpenSSH Certificate Authentication
  • Using CA With SSH

SPIRE 配置

前面的 SSH 配置只是个铺垫。SPIRE 使用 SSHPOP 实现了 Server 和 Agent 侧的节点证实插件,两个插件需要协同工作,官网的说明非常简明扼要:

稍稍延展说明一下需要注意的要点:

  1. SPIRE Agent 所在的节点实际上是作为 SSH 的服务端
  2. SPIRE Agent 联系 SPIRE Server 之后,SPIRE Server 要通过 SSH 来访问 SSH 服务端来确认身份。

因此上面语焉不详的配置就比较清楚了:

  • SPIRE Server 的 cert_authorities 需要的是客户端证书内容,例如 ["ssh-rsa XXXX46IvQ+bDEXYvf8pM= someone@ssh"]
  • SPIRE Server 的 cert_authorities_path 指向节点 CA 公钥,例如 XXXX/ca.pub
  • SPIRE Agent 的 host_cert_path 指向主机证书文件,例如 XXXX_key-cert.pub
  • SPIRE Agent 的 host_key_path 指向密钥文件,例如 XXXX_key

配置完成之后,启动 SPIRE Server,获取并把 Trust Bundle 传递给 SPIRE Agent,启动 SPIRE Agent,可以看到生成了形如 "spiffe://spiffe.dom/spire/agent/sshpop/XXXX 的 SVID,说明这个证实过程已经成功完成。

后记

本以为这是个顺便完成的东西,结果从来没想过 SSH 还有个 CA 这样的玩意,卡了好些时间,轻敌了。

另,值此辞旧迎新之际,祝大家身体健康、事业稳定、学习进步、物资充足——最重要运气爆棚吧:)

正文完