跳转到内容

OpenSSH/Cookbook/基于主机的身份验证

100% developed
来自维基教科书,开放世界中的开放书籍

 

基于主机的身份验证允许主机代表该特定主机的所有或部分用户进行身份验证。这些帐户可以是系统上的所有帐户,也可以是 Match 指令指定的子集。这种类型的身份验证对于管理计算集群和其他相当同质的机器池很有用。

总共,服务器上必须准备三个文件才能进行基于主机的身份验证。在客户端上,只需要修改两个文件,但主机本身必须分配 SSH 主机密钥。以下内容设置了从一个系统到另一个系统的一个方向的基于主机的身份验证。为了双向身份验证,请重复执行此过程两次,但颠倒系统的角色。

基于主机的身份验证的客户端配置

[编辑 | 编辑源代码]

在客户端或源主机上,必须配置两个文件,此外还需要至少存在一个主机密钥

/etc/ssh/ssh_known_hosts - 已知主机的公钥的全局文件
/etc/ssh/ssh_config - 允许客户端请求基于主机的身份验证

然后,如果客户端的主机密钥不存在,则必须创建它们

/etc/ssh/ssh_host_ecdsa_key
/etc/ssh/ssh_host_ed25519_key
/etc/ssh/ssh_host_rsa_key

这三个步骤需要在每个将连接到指定主机的客户端系统上完成。设置完成后,登录到一个系统上的帐户将能够连接到另一个系统,而无需进一步的交互式身份验证。

请注意,在某些环境中,基于主机的身份验证可能不被认为足以防止未经授权的访问,因为它主要基于主机之间进行。

1. 使用服务器的公钥填充客户端

[编辑 | 编辑源代码]

远程服务器的公钥必须存储在客户端系统上的全局客户端配置文件 /etc/ssh/ssh_known_hosts 中。一种从服务器获取公钥的方法是使用 ssh-keyscan(1) 获取并保存它们。

$ ssh-keyscan server.example.com | tee -a /etc/ssh/ssh_known_hosts

在尝试任何操作之前,请务必备份 /etc/ssh/ssh_known_hosts(如果存在)。另一种方法是将服务器的公钥添加到相关帐户中的 ~/.ssh/known_hosts 文件。但这更麻烦。

无论以何种方式获取和验证,那里列出的公钥显然必须与服务器上的私有主机密钥相对应。可以使用三种类型中的任何一种或全部三种类型:RSA、ECDSA 或 Ed25519。DSA 不应再使用。有关公钥验证,请参阅关于 如何通过指纹验证主机密钥 的部分,以了解一些相关方法。

2. 系统范围的客户端配置

[编辑 | 编辑源代码]

必须进行两个更改。首先,客户端的配置必须在连接到指定系统时请求基于主机的身份验证。其次,客户端配置文件必须设置为启用 ssh-keysign(8)。它是一个辅助应用程序,用于访问本地主机密钥并生成基于主机的身份验证所需的数字签名。这两个更改都可以在 /etc/ssh/ssh_config 中全局完成。

以下是在客户端上尝试对所有机器进行基于主机的身份验证的 /etc/ssh/ssh_config 的摘录。 ssh_config(5) 中的 Host 指令可用于进一步限制对特定服务器或服务器组的访问。

Host *.pool.example.org
	HostbasedAuthentication yes
	EnableSSHKeysign yes

在某些发行版中,例如 Red Hat Enterprise Linux 8,EnableSSHKeysign 指令可能需要放在通用部分中才能生效。在这种情况下,请执行以下操作

Host *.pool.example.org
	HostbasedAuthentication yes

Host *
	EnableSSHKeysign yes

可以根据需要添加其他配置指令。例如,以下是应用于同一池的两个附加设置

Host *.pool.example.org
	HostbasedAuthentication yes
	EnableSSHKeysign yes
	ServerAliveCountMax 3
	ServerAliveInterval 60

如果客户端主机的 home 目录是与其他机器共享的目录,例如使用 NFS 或 AFS,那么查看 NoHostAuthenticationForLocalhost 指令可能也很有用。

作为一些琐事,程序 ssh-keysign(8) 本身必须是 SUID root。但 SUID 可能是安装时设置的,不需要在那里进行任何更改。

3. 设置客户端系统自己的主机密钥

[编辑 | 编辑源代码]

程序 ssh-keysign(8) 需要读取客户端系统在 /etc/ssh/ 中的私有主机密钥。安装 OpenSSH 服务器将创建这些密钥,但如果客户端系统上尚未安装 OpenSSH 服务器,则不需要安装。仅拥有密钥就足够了。如果客户端系统上的私有主机密钥不存在,则需要使用 ssh-keygen(1) 手动添加它们,然后基于主机的身份验证才能正常工作。

$ ssh-keygen -A

使用 -A 选项时,默认路径为 /etc/ssh/

请注意,尽管客户端系统不需要实际运行 SSH 服务器才能使用基于主机的身份验证来连接到另一个系统,但完全可以安装 SSH 服务器,然后禁用或卸载客户端服务器上的 SSH 服务器,以此来获取主机密钥。

基于主机的身份验证的服务器端配置

[编辑 | 编辑源代码]

必须修改服务器或目标主机上的三个文件才能启用和允许基于主机的身份验证

/etc/shosts.equiv 与旧的 rhosts.equiv 语法相同
/etc/ssh/ssh_known_hosts 保存来自客户端的主机公钥
/etc/ssh/sshd_config 打开基于主机的身份验证

shosts.equiv 的确切位置可能因操作系统和发行版而异。

1. 在服务器上注册允许的客户端系统

[编辑 | 编辑源代码]

shosts.equiv 文件必须包含允许尝试基于主机的身份验证的客户端系统的列表。该文件可以包含主机名、IP 地址或网络组。最好保持此文件简单,并仅面向主机列表,无论是通过名称还是 IP 号码。它只提供第一个切入点。对于微调,请改用 sshd_config(5) 来为特定用户和组设置或撤销访问权限。

重要的是要注意,如果您使用的是 shosts.equiv 文件,则客户端和服务器系统上的用户名必须匹配。例如,要连接到 [email protected],客户端上的用户名也必须是 bob。如果您需要用户 'alice' 连接到 [email protected],您需要在 'bob' 的 .shosts 文件中指定这一点,而不是在全局 shosts.equiv 文件中。

client1.example.org
192.0.2.102
client8.example.org -bull
@statcluster

此文件位于 OpenBSD 中的 /etc/ 目录中,本手册尽可能地使用该目录作为参考系统。在其他系统上,该文件可能位于 /etc/ssh/ 目录中。无论哪种方式,shosts.equiv 文件都会标识允许尝试身份验证的地址。请查看您正在使用的实际系统的 sshd(1) 手册页,以确保您知道正确的位置。

寻找 hosts.equiv(5) 手册页,以了解更多关于 .shosts.equiv(以及 .shosts.)的信息,但请注意,它的使用早已过时,大多数系统甚至没有该手册页。

旧时代的遗留物

[edit | edit source]

非常旧的系统上可能存在两个遗留文件。 如果从shosts.equiv中引用,则可以对其进行可选修改。netgroup文件中的每一行都包含一个网络组名称,后跟网络组成员的列表,具体来说是主机、用户和域。

/etc/netgroup - 默认网络组列表
/etc/netgroup.db - 网络组数据库,从网络组构建

但是,这些大多数是旧的rhosts的遗留问题,应该避免。

另一个遗留问题是在每个帐户的主目录中使用.shosts。 这将是.shosts.equiv的本地等效项。 通过这种方式,单个用户可以拥有包含受信任的远程机器列表或用户-机器对的本地.shosts,这些机器或用户-机器对被允许尝试基于主机的身份验证。

.shosts 不得被任何组或任何其他用户写入。 权限设置为 0644 应该就可以了。.shosts的使用和格式与.rhosts完全相同,但允许基于主机的身份验证,而不允许通过不安全的遗留工具rloginrsh登录。 该列表每主机一行。 第一列是强制性的,包含允许尝试基于主机的身份验证的主机名称或地址。

但是,全局.shosts.equiv比在每个主目录中都有.shosts更可取。

2. 在服务器上填充客户端的公钥

[edit | edit source]

服务器的shosts.equiv中列出的客户端系统还必须在其服务器上的/etc/ssh/ssh_known_hosts中包含其公钥,才能被确认。 每行需要三个数据字段。 第一个是主机名或 IP 地址或它们的逗号分隔列表,对应于shosts.equiv中的那些。 接下来是密钥类型,对于 RSA 密钥为 ssh-rsa,对于 Ed25519 密钥为 ssh-ed25519,对于 ECDSA 密钥为 ecdsa-sha2-nistp256。 第三个字段是公钥本身。 最后,可选地,可以是对密钥的注释。

desktop,192.0.2.102 ssh-rsa AAAAB3NzaC1yc2EAAAABIw ... qqU24CcgzmM=

就像客户端的第一步一样,从客户端收集公钥信息并将其发送到服务器有很多方法。 它可以使用 sftp(1) 复制,从~/.ssh/known_hosts 复制,或使用 ssh-keyscan(1) 获取。 尽管后两种方法只有在客户端系统也运行 SSH 服务器的情况下才有效。

$ ssh-keyscan -t rsa client.example.org | tee -a /etc/ssh/ssh_known_hosts

3. 全局服务器配置

[edit | edit source]

必须在服务器上更改的第三个文件是 sshd_config(5)。 它必须被告知通过设置HostbasedAuthentication指令允许基于主机的身份验证,无论是针对所有用户,还是仅针对某些用户,还是仅针对某些组。

HostbasedAuthentication yes

基于主机的身份验证可以限制为特定用户或组。 以下是从 sshd_config(5) 中允许任何属于 'cluster2' 组的用户让主机代表其进行身份验证的示例摘录

Match Group cluster2
        HostbasedAuthentication yes

可以使用HostbasedAcceptedAlgorithms(以前称为HostbasedAcceptedKeyTypes)和逗号分隔的可接受密钥算法列表来允许某些主机密钥类型。 必须列出所有要允许的密钥算法,因为未列出的算法将不被允许。 在白名单中可以使用模式。 以下示例允许 Ed25519 和 ECDSA 密钥,但不允许其他密钥,例如 RSA 和 DSA。

HostbasedAuthentication yes
HostbasedAcceptedAlgorithms ssh-ed25519*,ecdsa-sha2*

主机名和其他 DNS 事项

[edit | edit source]

如果可能,为客户端创建完整的 DNS 条目,包括允许反向查找。 如果客户端机器未在 DNS 中列出,则服务器可能无法识别它。 在这种情况下,您可能需要告诉 sshd(8) 不要对连接的主机执行 DNS 反向查找。 这在非正式的 LAN 上可能是一个问题,其中主机有地址但没有注册主机名。 以下是从/etc/ssh/sshd_config 中的示例,使用HostbasedUsesNameFromPacketOnly指令来解决客户端缺少 DNS 记录的问题。

HostbasedAuthentication yes
HostbasedUsesNameFromPacketOnly yes

除非正常方法失败,否则不要添加此指令。 否则它会干扰并阻止身份验证。

有时,尝试连接的主机被识别为目标主机上的其他东西,而不是预期的东西。 这也会阻止身份验证。 因此,请确保配置文件与主机实际调用的内容相匹配。

调试

[edit | edit source]

配置应该非常直观,只需在服务器上的三个文件和客户端上的两个文件上进行少量更改即可管理。 如果遇到困难,请准备以调试级别 1 (-d) 到 3 (-ddd) 运行 sshd(8) 独立程序,并在调试级别 3 (-vvv) 运行 ssh(1) 几次,看看您错过了什么。 错误必须按正确的顺序清除,因此请一次一步进行。

如果服务器生成消息“debug3: auth_rhosts2_raw: no hosts access file exists”,则shosts.equiv文件可能位于错误的位置或丢失,并且该帐户中没有备用的~/.shosts文件。

如果服务器无法找到客户端的密钥,尽管它在known_hosts中,并且如果客户端的主机名不在常规 DNS 中,则可能需要添加指令HostbasedUsesNameFromPacketOnly。 这将使用客户端本身提供的名称,而不是执行 DNS 查找。

以下是从主机 192.0.2.102(也称为 desktop1)上的用户 'fred' 使用 Ed25519 密钥成功进行基于主机的身份验证的示例摘录。 服务器首先尝试查找 ECDSA 密钥,但没有找到它。

# /usr/sbin/sshd -ddd
debug2: load_server_config: filename /etc/ssh/sshd_config
...
debug3: /etc/ssh/sshd_config:111 setting HostbasedAuthentication yes
debug3: /etc/ssh/sshd_config:112 setting HostbasedUsesNameFromPacketOnly yes
...
debug1: sshd version OpenSSH_6.8, LibreSSL 2.1
...
debug1: userauth-request for user fred service ssh-connection method hostbased [preauth]
debug1: attempt 1 failures 0 [preauth]
debug2: input_userauth_request: try method hostbased [preauth]
debug1: userauth_hostbased: cuser fred chost desktop1. pkalg ecdsa-sha2-nistp256 slen 100 [preauth]
...
debug3: mm_answer_keyallowed: key_from_blob: 0x76eede00
debug2: hostbased_key_allowed: chost desktop1. resolvedname 192.0.2.102 ipaddr 192.0.2.102
debug2: stripping trailing dot from chost desktop1.
debug2: auth_rhosts2: clientuser fred hostname desktop1 ipaddr desktop1
debug1: temporarily_use_uid: 1000/1000 (e=0/0)
debug1: restore_uid: 0/0
debug1: fd 4 clearing O_NONBLOCK
debug2: hostbased_key_allowed: access allowed by auth_rhosts2
debug3: hostkeys_foreach: reading file "/etc/ssh/ssh_known_hosts"
debug3: record_hostkey: found key type ED25519 in file /etc/ssh/ssh_known_hosts:1
debug3: load_hostkeys: loaded 1 keys from desktop1
debug1: temporarily_use_uid: 1000/1000 (e=0/0)
debug3: hostkeys_foreach: reading file "/home/fred/.ssh/known_hosts"
debug1: restore_uid: 0/0
debug1: check_key_in_hostfiles: key for host desktop1 not found
Failed hostbased for fred from 192.0.2.102 port 10827 ssh2: ECDSA SHA256:CEXGTmrVgeY1qEiwFe2Yy3XqrWdjm98jKmX0LK5mlQg, client user "fred", client host "desktop1"
debug3: mm_answer_keyallowed: key 0x76eede00 is not allowed
debug3: mm_request_send entering: type 23
debug2: userauth_hostbased: authenticated 0 [preauth]
debug3: userauth_finish: failure partial=0 next methods="publickey,password,keyboard-interactive,hostbased" [preauth]
debug1: userauth-request for user fred service ssh-connection method hostbased [preauth]
debug1: attempt 2 failures 1 [preauth]
debug2: input_userauth_request: try method hostbased [preauth]
debug1: userauth_hostbased: cuser fred chost desktop1. pkalg ssh-ed25519 slen 83 [preauth]
debug3: mm_key_allowed entering [preauth]
debug3: mm_request_send entering: type 22 [preauth]
debug3: mm_key_allowed: waiting for MONITOR_ANS_KEYALLOWED [preauth]
debug3: mm_request_receive_expect entering: type 23 [preauth]
debug3: mm_request_receive entering [preauth]
debug3: mm_request_receive entering
debug3: monitor_read: checking request 22
debug3: mm_answer_keyallowed entering
debug3: mm_answer_keyallowed: key_from_blob: 0x7e499180
debug2: hostbased_key_allowed: chost desktop1. resolvedname 192.0.2.102 ipaddr 192.0.2.102
debug2: stripping trailing dot from chost desktop1.
debug2: auth_rhosts2: clientuser fred hostname desktop1 ipaddr desktop1
debug1: temporarily_use_uid: 1000/1000 (e=0/0)
debug1: restore_uid: 0/0
debug1: fd 4 clearing O_NONBLOCK
debug2: hostbased_key_allowed: access allowed by auth_rhosts2
debug3: hostkeys_foreach: reading file "/etc/ssh/ssh_known_hosts"
debug3: record_hostkey: found key type ED25519 in file /etc/ssh/ssh_known_hosts:1
debug3: load_hostkeys: loaded 1 keys from desktop1
debug1: temporarily_use_uid: 1000/1000 (e=0/0)
debug3: hostkeys_foreach: reading file "/home/fred/.ssh/known_hosts"
debug1: restore_uid: 0/0
debug1: check_key_in_hostfiles: key for desktop1 found at /etc/ssh/ssh_known_hosts:1
Accepted ED25519 public key SHA256:BDBRg/JZ36+PKYSQTJDsWNW9rAfmUQCgWcY7desk/+Q from fred@desktop1
debug3: mm_answer_keyallowed: key 0x7e499180 is allowed
debug3: mm_request_send entering: type 23
debug3: mm_key_verify entering [preauth]
debug3: mm_request_send entering: type 24 [preauth]
debug3: mm_key_verify: waiting for MONITOR_ANS_KEYVERIFY [preauth]
debug3: mm_request_receive_expect entering: type 25 [preauth]
debug3: mm_request_receive entering [preauth]
debug3: mm_request_receive entering
debug3: monitor_read: checking request 24
debug3: mm_answer_keyverify: key 0x7e49a700 signature verified
debug3: mm_request_send entering: type 25
Accepted hostbased for fred from 192.0.2.102 port 10827 ssh2: ED25519 SHA256:BDBRg/JZ36+PKYSQTJDsWNW9rAfmUQCgWcY7desk/+Q, client user "fred", client host "desktop1"
debug1: monitor_child_preauth: fred has been authenticated by privileged process
...

注意任何警告或错误消息,并仔细阅读它们。 如果输出太多,请记住服务器的-e选项,并使用它将调试输出保存到单独的文件中,然后在以后读取该文件。

由于此方法依赖于客户端和服务器,因此以更高的详细程度运行客户端有时也有帮助。

如果来自客户端的输出太多而无法处理,请记住-E选项,并将调试日志重定向到文件,然后在空闲时读取该文件。

 

华夏公益教科书