SSH是一个非常强大的自动化工具。许多程序可以使用SSH作为传输,依靠已知的安全软件,而不是试图实现自己的网络安全。大多数网络编排工具,如Ansible和Puppet,都是用SSH;破坏SSH配置意味着你无法使用它们。
然而,这种灵活性也可能导致安全问题。自动化流程不应该访问任何东西,除了执行任务所需的最低限度。幸运的是,你可以限制特定用户可以通过SSH、authorized_keys文件甚至sshd本身运行的命令。此外,你可以在用户登录时自动运行命令。我们将从该功能开始,然后继续限制用户。
SSH服务器在用户启动新会话时检查要运行的命令。这主要是为了配置使账户在登录前可用所需的服务,例如挂载文件系统和分配X显示,但你可以根据需要使用它。
登录时,sshd检查shell脚本$HOME/.ssh/rc。如果它存在,就运行它。如果不存在,sshd会检查/ssh/sshrc上的脚本并运行该脚本。无论哪种方式,脚本都由登录的账户运行。如果每次用户登录时都需要执行任务,请考虑此功能。
该脚本必须是有效的shell脚本,并文件顶部带有#!/bin/sh,并且它必须是可执行的(一些Linux发行版即使不符合这些要求也会执行此命令)。SSH守护进程向脚本传递一个参数,即X11 cookie。使用现代X软件,你几乎可以忽略它。
sshd_config关键字PermitUserRC打开和关闭脚本检查。虽然它默认为yes,但可以通过将其设置为no来禁用该脚本。
虽然用户的authorized_keys指定了可用于身份验证的密钥对,但你也可以使用它来限制使用该密钥登录的用户可能运行的命令。一个账户可能有一个用于交互式使用的密钥对,还有一个用于自动任务的密钥对。配置需要了解authorized_keys文件格式。
最小authorized_keys条目有三个部分:密钥类型、表示公钥的几百个字符和注释字段。所有条目都在同一行上,无论它有多长。像以下示例:
xxxxxxxxxx
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA… wE2Ime8Rs/Q== moose-20160525
这是一个RSA密钥,条目开头显示了ssh-rsa。公钥从AAA开始,到8Rs/Q==结束。大多数公钥条目都以==结尾,但也有例外。末尾的注释会向主机提供此密钥创建的日期和时间。
你可以在条目的开头添加其他关键字和如何使用此密钥的说明。服务器在用户权限范围内遵守这些指令。在ssh手册页中有authorized_keys关键字的完整列表。以下是最常用的关键字:
每当有人使用此密钥登录时,运行指定的命令。SSH忽略用户提供的任何命令,而选择authorized_keys指定的命令。你可以将其用于自动化过程,例如配置VPN或运行rsync。
xxxxxxxxxx
command="sudo ifconfig tun0 inet 192.0.2.2/30" ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
一个有趣的特性是,SSH保留了客户端在环境变量$SSH_ORIGINAL_COMMAND中请求的任何命令。你可以让authorized_keys运行一个脚本,检查此环境变量并采取适当的行动。
当使用此密钥登录时,会设置一个环境变量。你可以使用任意数量的环境语句。
xxxxxxxxxx
environment="automated=1" ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
默认情况下,sshd不允许设置环境变量。系统管理员必须在sshd_config中将PermitUserEnvironment设置为yes,以便用户设置环境变量。
只有当客户端地址或反向DNS与给定模式匹配时,此密钥才能用于身份验证。我们在第二章讨论了模式。我经常用它来限制自动化流程。即使入侵者窃取了私人用户密钥,他也无法从我允许的主机之外的任何主机访问SSH服务器。
xxxxxxxxxx
from="198.51.100.0/29" ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
只有IP范围为192.51.100.0到192.51.100.7的主机才能使用此密钥登录SSH服务器。
如果UserDNS设置为yes,则只能在模式中使用主机名。请记住,入侵者经常可以伪造他们的反向DNS条目,因此最好在sshd中禁用DNS并坚持使用IP地址。
使用此密钥将禁用SSH代理转发:
xxxxxxxxxx
no-agent-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
禁用X转发:
xxxxxxxxxx
no-x11-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
使用此密钥进行身份验证的会话将不会被授予伪终端(pseudo-terminal)。许多在自动化下运行的程序不需要终端。
xxxxxxxxxx
no-pty ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
这将禁用sshd的登录脚本检查,如本章开头的“登录时运行命令”中所述:
xxxxxxxxxx
no-user-rc ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
permitopen关键字限制本地端口转发,使其只能连接到本地计算机上的给定主机名或IP地址和端口。如果服务器不允许本地端口转发,则此操作无效。
xxxxxxxxxx
permitopen="localhost:25" ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
此示例允许端口转发连接到127.0.0.1上的端口25,但仅此而已。
你可以将permitopen设置为none以禁止所有端口转发。
为SSH隧道使用特定的隧道设备号(见第十三章)。
xxxxxxxxxx
tunnel="3" ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
默认情况下,任何未被拒绝的内容都是允许的。restrict关键字反转了这一点,除非你特别允许,否则会组织所有内容。你可以使用关键字agent-forwarding、port-forwarding、pty、user-rc和X11-forwarding来重新打开这些功能。
与OpenSSH中几乎所有内容一样,你可以在一个条目中使用多个关键字。用逗号分隔关键字:
xxxxxxxxxx
restrict,command="/usr/local/scripts/backup.sh" ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
我们很多人都想使用SSH作为其他程序的安全传输。也许你有一个自定义的监视器程序,或者一个在rsync上运行的备份进程。此类客户不应该拥有硬编码的用户名和密码;除了不安全之外,它既不可维护也不可扩展。一种解决方案是使用没有密码的身份验证密钥。通过严格限制该密钥的使用方式以及可以对该密钥采取的操作,可以最大限度地减少入侵者造成的伤害。
注意,潜在的损害只是被最小化,而不是被消除。在错误时间允许rsync备份可能会损坏现有的良好备份或使网络饱和。在错误时间启动VPN可能会造成严重破坏。然而,在大多数环境中,这些比复制或销毁你所有专有数据的人破坏性更小、更明显。
首先,你需要一个适合程序使用的用户密钥,然后,需要适当的authorized_keys限制。
自动流程无法键入密码短语。任何需要SSH访问另一台主机的计划或其他自动化任务都需要一个没有密码短语的密钥。生成此密钥,就像生成主机密钥一样:
xxxxxxxxxx
$ ssh-keygen -f filename -N ''
这将创建两个文件,一个具有你选择的文件名,另一个具有相同的名称但附加了.pub。这里,我创建了一个名为task-key的密钥:
xxxxxxxxxx
$ ssh-keygen -f task-key -N ''
我最终得到了文件task-key和task-key.pub。.pub文件是公钥。
在SSH服务器上为此自动任务创建一个账户,或选择一个现有账户。主机的SSH服务器必须允许登录该账户。将.pub文件添加到该账户的authorized_keys中。
客户端机器现在应该能够使用密钥登录到SSH服务器。记得使用ssh的-i参数指定备用密钥文件。此处,我使用此密钥登录到机器sloth:
xxxxxxxxxx
$ ssh -i task-key sloth
如果你成功登录到服务器,则密钥已经正确安装。现在我们把它锁起来。
最佳实践禁止用户执行任务所需的所有访问。你的自动化流程需要端口还是X转发?关掉它们。它需要一个特殊的环境吗?可能不会,因为你可以在用户账户中更容易地建立该环境。你的自动化作业在一台机器上运行,因此你可以限制密钥,使其只能在这台机器上使用。你可能会得到这样的authorized_keys条目:
xxxxxxxxxx
restrict,command="dump /home > /backups/`date +s`.dump",from="192.0.2.8" ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAA…
这样配置密钥可以减少灾难的范围。备份脚本不会意外覆盖你的根分区。入侵者只能运行你的备份脚本。这并不好,但总比入侵者窃取你的数据并删除你的日志文件好。
限制密钥的一个挑战是了解程序实际需要什么命令,以及你认为它需要什么。sshd的调试模式可以帮助你解决这个问题。让你的客户端在调试模式下对sshd运行其命令,并研究输出。你将看到客户端运行的所有命令。这也将使你能够比其他方式更进一步地锁定密钥——如果你知道rsync将在你的服务器上使用的确切标志,你可以将其作为限制。
有时编写的脚本似乎可以在命令行中工作,但在计划时失败了,每次我的脚本都是从我的SSH代理获取身份验证信息,而不是使用我为任务创建的密钥。IdentitiesOnly关键字告诉ssh只使用命令行中指定的标识,而不使用代理。在脚本的SSH命令中设置 -o IdentitiesOnly=yes。
自动脚本不应该提示用户输入。你不希望脚本挂起并等待密码提示。BatchMode关键字禁用密码和密码提示。通过BatchMode设置为yes,脚本的SSH部分将立即崩溃并死亡,而不是永远无意义地徘徊。
也许你不想使用authorized_keys来限制访问,或者你可能要额外的保护。你可以使用ForceCommand sshd_config关键字来限制账户可以运行的内容。
ForceCommand接受一个参数,即要运行的命令。它在用户的常规权限下运行,忽略客户端请求的任何命令。与在authorized_keys中定义命令非常相似,ForceCommand在$SSH_ORIGINAL_COMMAND环境变量中保留了请求的命令。
ForceCommand最好在Match语句中使用。
不要使用PermitRootLogin关键字以root身份登录。以root身份登录自动化违反了许多基本的安全原则。
一些环境可以安全地支持root登录。有些人以支持审计的方式使用root登录。然而,如果你读这本书是为了了解SSH,那你的环境还远没有为此做好准备
如果你的自动化流程需要特权访问,请使用sudo。sudo运行无特权用户以提升的权限运行特定的命令,适用于每个类Unix系统。可以参阅《Sudo Mastery》。sudo比大多数人想象的要灵活得多,也更危险。