签名的 SSH 证书
就设置复杂性和平台无关性而言,签名的 SSH 证书是最简单和最强大的。通过利用 Vault 强大的 CA 能力和 OpenSSH 内置的功能,客户端可以使用自己的本地 SSH 密钥通过 SSH 连接到目标主机。
本节中的“客户端”指的是执行 SSH 操作的人或者机器。“服务端”指的是目标机器。客户端也可以看作“用户”。
本节仅演示如何快速使用该机密引擎。要获取所有可用路径的详细文档,请使用 vault path-help
命令,命令后方输入机密引擎的挂载地点。
客户端密钥签名
在客户端可以请求对他们的 SSH 密钥进行签名之前,必须配置 Vault SSH 机密引擎。通常由 Vault 管理员或安全团队执行这些步骤。也可以使用 Chef、Puppet、Ansible 或 Salt 等配置管理工具来自动化这些操作。
签名密钥 & 配置角色
以下步骤由 Vault 管理员、安全团队或配置管理工具预先执行。
- 挂载机密引擎。就像 Vault 中所有的机密引擎一样,SSH 机密引擎在使用前必须先挂载:
$ vault secrets enable -path=ssh-client-signer ssh
Successfully mounted 'ssh' at 'ssh-client-signer'!
该命令在路径 ssh-client-signer
上启用了 SSH 机密引擎。可以通过使用不同的 -path
参数多次挂载同一个机密引擎。这里的名字 ssh-client-signer
并没有什么特殊的——它可以是任何名字,但本节假设挂载点是 ssh-client-signer
。
- 透过
/config/ca
端点给 Vault 配置一个 CA ,用以对客户端密钥进行签名。如果我们没有内部 CA,Vault 会为我们生成一个密钥对(keypair)。
$ vault write ssh-client-signer/config/ca generate_signing_key=true
Key Value
--- -----
public_key ssh-rsa AAAAB3NzaC1yc2EA...
如果我们已经有了密钥对,请将公钥和私钥部分指定为有效载荷的一部分:
$ vault write ssh-client-signer/config/ca \
private_key="..." \
public_key="..."
无论是生成的还是上传的,用以对客户端进行签名的公钥都可以通过 API 的 /public_key
端点访问到。
- 将公钥添加到所有目标主机的 SSH 配置中。该进程可以手工完成,或是借助于配置管理工具。该公钥可以通过 API 得到,不需要经过身份验证:
$ curl -o /etc/ssh/trusted-user-ca-keys.pem http://127.0.0.1:8200/v1/ssh-client-signer/public_key
$ vault read -field=public_key ssh-client-signer/config/ca > /etc/ssh/trusted-user-ca-keys.pem
将存有公钥的文件路径保存在 SSH 配置文件中的 TrustedUserCAKeys
配置项上:
# /etc/ssh/sshd_config
# ...
TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem
重启 SSH 服务以加载变更。
- 创建用于签署客户端密钥的命名 Vault 角色。
重要提示:在 Vault 1.9 之前,如果角色没有配置 "allowed_extensions"
或是配置为空白,Vault 会采用允许的默认值:分配给该角色的任何用户都可以指定任意扩展值作为对 Vault 服务器的证书请求的一部分。这可能会对依赖 extensions
字段来获取安全关键信息的第三方系统产生重大影响。在这些情况下,请考虑使用模板来指定默认扩展,并在字段为空或未设置时将 allowed_extensions
显式设置为任意非空字符串。
由于一些 SSH 证书功能实现的方式,选项以字典的方式传递。下面的例子中为证书添加了 permit-pty
扩展,并允许用户在请求证书时为 permit-pty
和 permit-port-forwarding
自行设置值:
$ vault write ssh-client-signer/roles/my-role -<<"EOH"
{
"algorithm_signer": "rsa-sha2-256",
"allow_user_certificates": true,
"allowed_users": "*",
"allowed_extensions": "permit-pty,permit-port-forwarding",
"default_extensions": [
{
"permit-pty": ""
}
],
"key_type": "ca",
"default_user": "ubuntu",
"ttl": "30m0s"
}
EOH
客户端 SSH 身份验证
希望通过身份验证连接到由 Vault 管理的机器的客户端(用户)需要执行以下步骤。这些命令通常从过年客户端运行的本地工作站运行:
- 定位或生成 SSH 公钥。通常公钥在
~/.ssh/id_rsa.pub
。如果我们没有 SSH 密钥对,可以生成一个:
$ ssh-keygen -t rsa -C "user@example.com"
- 请求 Vault 签名公钥。该文件通常以
.pub
为后缀名,内容的开头为ssh-rsa ...
。
$ vault write ssh-client-signer/sign/my-role \
public_key=@$HOME/.ssh/id_rsa.pub
Key Value
--- -----
serial_number c73f26d2340276aa
signed_key ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1...
返回结果包含了序列号以及签名后的密钥。该密钥也是一个公钥。
要自定义签名参数,请使用 JSON 载荷:
$ vault write ssh-client-signer/sign/my-role -<<"EOH"
{
"public_key": "ssh-rsa AAA...",
"valid_principals": "my-user",
"key_id": "custom-prefix",
"extensions": {
"permit-pty": "",
"permit-port-forwarding": ""
}
}
EOH
- 将经过签名的公钥保存到磁盘上。可以根据需要限制权限:
$ vault write -field=signed_key ssh-client-signer/sign/my-role \
public_key=@$HOME/.ssh/id_rsa.pub > signed-cert.pub
如果我们将证书直接保存在 SSH 密钥旁边,在文件名后添加 -cert.pub
后缀(!/.ssh/id_rsa-cert.pub
)。如果按照这种规范命名,OpenSSH 会自动在进行身份验证时使用经签名的公钥。
- (可选)查看经签名的密钥的已启用的扩展(extnsions)、主体(principals)以及元数据:
$ ssh-keygen -Lf ~/.ssh/signed-cert.pub
- 使用经签名的密钥 SSH 连接至主机。我们需要同时提供从 Vault 获取的经签名的公钥以及相对应的私钥作为身份验证信息来发起 SSH 调用:
$ ssh -i signed-cert.pub -i ~/.ssh/id_rsa username@10.0.23.5
对主机密钥签名
为了增加额外的安全性,我们建议启用主机密钥签名。与客户端密钥签名结合使用可以提供额外的完整性层。启用后,SSH 代理将在尝试 SSH 之前验证目标主机是否有效且受信任。这将减少用户通过 SSH 意外连接到非托管或恶意机器的可能性。
签名密钥配置
- 挂载机密引擎。为了最大程度的安全,请挂载到与签名客户端时不同的路径上:
$ vault secrets enable -path=ssh-host-signer ssh
Successfully mounted 'ssh' at 'ssh-host-signer'!
- 通过 Vault 的
/config/ca
端点配置一个对主机密钥进行签名用的 CA 证书。如果我们没有内部的 CA,Vault 可以为我们生成一个密钥对。
$ vault write ssh-host-signer/config/ca generate_signing_key=true
Key Value
--- -----
public_key ssh-rsa AAAAB3NzaC1yc2EA...
如果我们已经有了密钥对,请将公钥和私钥部分指定为有效载荷的一部分:
$ vault write ssh-host-signer/config/ca \
private_key="..." \
public_key="..."
无论是生成的还是上传的,用以对主机进行签名的公钥都可以通过 API 的 /public_key
端点访问到。
- 延展主机密钥证书的 TTL:
$ vault secrets tune -max-lease-ttl=87600h ssh-host-signer
- 创建用于对主机密钥进行签名的角色。一定要填写 allowed domains 列表,或设置
allow_bare_domains
,或同事设置两者:
$ vault write ssh-host-signer/roles/hostrole \
key_type=ca \
ttl=87600h \
allow_host_certificates=true \
allowed_domains="localdomain,example.com" \
allow_subdomains=true
- 对主机的 SSH 公钥进行签名:
$ vault write ssh-host-signer/sign/hostrole \
cert_type=host \
public_key=@/etc/ssh/ssh_host_rsa_key.pub
Key Value
--- -----
serial_number 3746eb17371540d9
signed_key ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1y...
- 将返回的经签名的证书保存到磁盘:
$ vault write -field=signed_key ssh-host-signer/sign/hostrole \
cert_type=host \
public_key=@/etc/ssh/ssh_host_rsa_key.pub > /etc/ssh/ssh_host_rsa_key-cert.pub
将证书文件的权限设置为 0640
:
$ chmod 0640 /etc/ssh/ssh_host_rsa_key-cert.pub
将主机密钥及证书添加到 SSH 配置文件中:
# /etc/ssh/sshd_config
# ...
# For client keys
TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem
# For host keys
HostKey /etc/ssh/ssh_host_rsa_key
HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub
重启 SSH 服务以加载变更。
客户端对主机进行验证
- 获取主机使用的 CA 公钥来验证目标机器的签名:
$ curl http://127.0.0.1:8200/v1/ssh-host-signer/public_key
$ vault read -field=public_key ssh-host-signer/config/ca
- 将返回的公钥添加到
known_hosts
文件的 authority 中:
# ~/.ssh/known_hosts
@cert-authority *.example.com ssh-rsa AAAAB3NzaC1yc2EAAA...
- 像往常一样 SSH 连接到目标机器。
故障排查
当开始配置此类密钥签名时,开启 SSH 登录的 VERBOSE
级别日志可以帮助在日志中定位各种错误:
# /etc/ssh/sshd_config
# ...
LogLevel VERBOSE
重启 SSH 服务以加载变更。
默认情况下,SSH 将日志记录到 /var/log/auth.log
,但还有其他许多程序也会将日志记录于此。如果要把 SSH 的日志提取出来,可以这样做:
$ tail -f /var/log/auth.log | grep --line-buffered "sshd"
如果我们无法连接到主机,服务端的 SSH 日志可能可以提供指引和洞见。
name is not a listed principal
如果 auth.log
显示一下信息:
# /var/log/auth.log
key_cert_check_authority: invalid certificate
Certificate invalid: name is not a listed principal
该证书不允许将用户名作为列出的主体对系统进行身份验证。这很可能是由于 OpenSSH 错误(有关更多信息,请参阅已知问题)。此错误导致 allowed_users
选项值如果为“*”将不起作用。以下是解决此问题的方法:
- 设置角色中的
default_user
。如果我们只使用同一个用户进行身份验证,可以将角色的default_user
设置为 SSH 到目标机器时使用的用户名。
$ vault write ssh/roles/my-role -<<"EOH"
{
"default_user": "YOUR_USER",
// ...
}
EOH
- 在签名过程总设置
valid_principals
。如果有多个用户需要通过 Vault 进行 SSH 身份验证,在密钥签名期间设置合法的主体列表,其中要包括当前用户名:
$ vault write ssh-client-signer/sign/my-role -<<"EOH"
{
"valid_principals": "my-user"
// ...
}
EOH
登录后没有提示(prompt)
如果我们在通过登录到目标机器后没有看到提示,可能是因为经签名的证书没有配置 permit-pty
扩展。有两种方法为证书添加该扩展:
- 在创建角色时添加:
$ vault write ssh-client-signer/roles/my-role -<<"EOH"
{
"default_extensions": [
{
"permit-pty": ""
}
]
// ...
}
EOH
- 在签名时添加:
$ vault write ssh-client-signer/sign/my-role -<<"EOH"
{
"extensions": {
"permit-pty": ""
}
// ...
}
EOH
没有端口转发
如果从客户端到主机的端口转发不起作用,经签名的证书可能没有配置 permit-port-forwarding
扩展。可以参考上面没有提示部分的流程,在创建角色时添加该扩展:
{
"default_extensions": [
{
"permit-port-forwarding": ""
}
]
}
没有 X11 转发
如果从客户端到主机的 X11 转发不起作用,经签名的证书可能没有配置 permit-X11-forwarding
扩展。可以参考上面没有提示部分的流程,在创建角色时添加该扩展:
{
"default_extensions": [
{
"permit-X11-forwarding": ""
}
]
}
没有代理(agent)转发
如果从客户端到主机的代理转发不起作用,经签名的证书可能没有配置 permit-agent-forwarding
扩展。可以参考上面没有提示部分的流程,在创建角色时添加该扩展:
{
"default_extensions": [
{
"permit-agent-forwarding": ""
}
]
}
已知的问题
- 在启用了 SELinux 的系统上,我们可能需要调整相关类型使得 SSH 守护进程可以读取到它。流入,将经签名的主机证书设置成
sshd_key_t
类型。 - 在某些版本的 SSH 上我们可能会看到以下错误:
no separate private key for certificate
这是一个 OpenSSH 7.2 版本引入的 bug,在 7.5 被修复了。请阅读 OpenSSH bug 2617获取详细信息。
- 在某些版本的 SSH 上,我们可能会在目标主机上看到以下错误:
userauth_pubkey: certificate signature algorithm ssh-rsa: signature algorithm not supported [preauth]
可以通过向 etc/ssh/sshd_config
追加以下内容来修复该错误:
CASignatureAlgorithms ^ssh-rsa
从 OpenSSH 8.2开始不再支持 ssh-rsa 算法。