OpenSSH/食谱/负载均衡
可以通过指定三个冒号分隔的值 start:rate:full 来启用随机早期丢弃。当未经身份验证的连接数达到 start 指定的值后,sshd(8) 将以 rate 指定的百分比拒绝新连接。随着 full 指定的限制接近,拒绝连接的比例线性增加,直到达到 100%。此时,所有新的连接尝试都会被拒绝,直到连接积压减少。
MaxStartups 10:30:100
例如,如果在 sshd_config(5) 中给出 MaxStartups 5:30:90,那么从 5 个新的连接等待身份验证开始,服务器将开始丢弃 30% 的新连接。当连接积压增加到 90 个待处理的未经身份验证的连接时,将丢弃 100%。
在默认设置中,full 的值已增加到 100 个待处理的连接,以使其更难屈服于攻击或负载过重导致的拒绝服务。因此,新的默认值为 10:30:100。
或者,如果传入连接不是通过数据包过滤器或其他技巧(如循环 DNS)在网络级别管理的,则可以在 SSH 服务器本身限制它。将 MaxStartups 设置为整数会将 SSH 守护进程的最大并发未经身份验证连接数设置为硬性限制。
MaxStartups 10
其他连接将被丢弃,直到身份验证成功或 LoginGraceTime 针对另一个连接过期。旧的默认值为 10。
在 SSH 会话期间可以跟踪两个连接,即网络的 TCP 连接和在其上运行的加密 SSH 会话。一些隧道和 VPN 可能并不总是处于活动状态,因此存在会话超时甚至路由器或防火墙上的 TCP 会话超时的风险。网络连接可以使用 TCPKeepAlive 跟踪,但这不能准确地反映实际 SSH 连接的状态。但是,它是实际网络状态的有用指示器。客户端或服务器可以通过使用心跳来保持加密连接处于活动状态来抵消这种情况。
在客户端,如果全局客户端配置尚未设置,则个人可以使用 ServerAliveInterval 选择服务器活动心跳的间隔(以秒为单位),并使用 ServerAliveCountMax 设置相应的最大允许错过客户端消息数,在超过该限制之前,加密 SSH 会话将被视为已关闭。
ServerAliveInterval 15
ServerAliveCountMax 4
在服务器端,ClientAliveInterval 设置客户端活动心跳之间的间隔(以秒为单位)。ClientAliveCountMax 设置允许错过的最大客户端消息数,在超过该限制之前,加密 SSH 会话将被视为已关闭。如果在此期间没有发送或接收其他数据,sshd(8) 将通过加密通道发送一条消息以请求来自客户端的响应。如果 sshd_config(5) 将 ClientAliveInterval 设置为 15,并将 ClientAliveCountMax 设置为 4,则无响应的 SSH 客户端将在大约 60 (= 15 x 4) 秒后断开连接。
ClientAliveInterval 15
ClientAliveCountMax 4
如果还使用了基于时间的 RekeyLimit,但时间限制小于 ClientAliveInterval 心跳,那么较短的重新密钥限制将用于心跳间隔。
这与服务器原理基本相同。同样,这在 ~/.ssh/config 中设置,可以全局应用于该帐户的所有连接,也可以使用 Host 或 Match 块选择性地应用于特定连接。
如果服务器不再在空闲 SSH 帐户达到 ClientAliveInterval 选项配置的超时时断开其连接,那么解决方法是将 shell 的 TMOUT 变量设置为所需的超时值。当 TMOUT 设置时,它指定 shell 在关闭 shell 并关闭 SSH 会话之前等待输入行的秒数。请注意,这意味着也必须按下 Enter 键,因为其他输入不足以防止超时,并且必须实际输入行才能重置计时器。
在服务器上,检查 SSH_CONNECTION 变量是否存在,该变量通常为空,除非当前处于 SSH 会话中,以区分 SSH 会话和本地 shell。如果允许该帐户更改超时时间,那么以下内容可以在该帐户自己的配置文件中,例如 ~/.profile,
if [ "$SSH_CONNECTION" != "" ]; then
# 10 minutes
TMOUT=600
export TMOUT
fi
如果该帐户不能更改此设置,那么它必须在全局配置文件中,并且必须设置为只读,例如在 /etc/profile.d/ 下的某个位置,
if [ "$SSH_CONNECTION" != "" ]; then
# 10 minutes
TMOUT=600
readonly TMOUT
export TMOUT
fi
以上两个示例都是针对 Bourne shell、其派生版本以及可能的一些其他 shell。一些 shell 可能有其他选项可用,例如在 tcsh(1) 中找到的实际 autologout 变量。
从 6.7 开始,OpenSSH 的 sshd(8) 不再支持 TCP 包装器,也称为 tcpd(8)。因此,此小节仅适用于 6.6 及更早版本。可以使用 tcpd(8) 的其他选项包括数据包过滤器,如 PF [1]、ipf、NFTables 甚至旧的 IPTables。特别是,OpenSSH 服务器的 Match 指令支持按 CIDR 地址进行过滤。使用这些,并记住短语“等效安全控制”,这可以消除安全审计人员可能在其工作表上从 1990 年代遗留的“tcpwrappers”复选框造成的麻烦。
tcpd(8) 程序是用于传入对互联网服务的请求的访问控制实用程序。它用于将一个对一映射到可执行文件的服务,例如 sshd(8),并且这些服务已编译为与其交互。它首先检查白名单(/etc/hosts.allow),然后检查黑名单(/etc/hosts.deny)以批准或拒绝访问。与任何给定连接尝试匹配的第一个模式将被使用。/etc/hosts.deny 中的默认行为是阻止访问,如果 /etc/hosts.allow 中没有匹配规则。
sshd: ALL
除了访问控制之外,还可以将 tcpd(8) 设置为在触发规则时使用 twist 或 spawn 运行脚本。spawn 以 tcpd(8) 的子进程身份启动另一个程序。来自 /etc/hosts.allow
sshd: .example.org : allow
sshd: .example.com : spawn \
/bin/date -u +"%%F %%T UTC from %h" >> /var/log/sshd.log : allow
变量 %h 展开为连接客户端的主机名或 ip 号码。hosts_access(5) 的手册页包含对可用变量的完整描述。由于示例中的程序 date 使用相同的符号来表示变量,因此必须对转义字符 (%) 进行转义 (%%),这样它就不会被 tcpd(8) 忽略,并能正确地传递给 date。
twist 使用另一个程序替换请求的服务。它有时用于蜜罐,但实际上可以用于任何目的。来自 /etc/hosts.deny
sshd: .example.org : deny
sshd: ALL : twist /bin/echo "Sorry, fresh out." : deny
使用 TCP 包装器时,会首先搜索白名单 /etc/hosts.allow,然后搜索黑名单 /etc/hosts.deny。第一个匹配项将被应用。如果未找到任何适用的规则,则默认情况下授予访问权限。不应再使用它,而应使用更好的替代方案。另请参阅 sshd_config(5) 中关于 CIDR 地址的 Match 指令,或者 AllowUsers 和 DenyUsers 指令。
以前可以使用 TCP Wrappers,只需将 sshd(8) 设置为仅监听本地地址,不接受任何外部连接。这是一种方法。要使用 TCP Wrappers 执行此操作,请在 /etc/hosts.deny 中添加一行以阻止所有内容
sshd: ALL
然后在 /etc/hosts.allow 中添加一个例外,通过使用 CIDR 表示法指定 IP 范围或使用域名来指定例外
sshd: 192.0.32.0/20
相同的方法可用于将访问限制为仅 localhost (127.0.0.0/8),方法是将一行添加到 /etc/hosts.allow
sshd: 127.0.0.0/8
同样,最佳实践是从任何地方阻止,然后打开例外。请记住,如果使用域名而不是 IP 范围,则 DNS 条目必须按顺序排列,并且 DNS 本身必须可访问。但是,上述方法仅具有历史意义。相同类型的限制最好通过相应地设置 sshd_config(5) 来完成,而不是使用 TCP Wrappers,而是在 OpenSSH 服务器中使用 Match 指令,或在操作系统本身中使用数据包过滤器。
扩展互联网服务守护进程,xinetd(8),可以提供多种类型的访问控制。其中包括但不限于远程主机的名称、地址或网络,以及一天中的时间。它还可以限制每个服务的服务数量,以及在负载超过一定限制时停止服务。
超级服务器监听传入请求并在需要时启动 sshd(8),因此有必要首先停止 sshd(8) 作为独立守护进程运行。这可能意味着修改 System V init 脚本或 Upstart 配置文件。然后为 SSH 服务创建一个 xinetd(8) 配置文件。它可能位于 /etc/xinetd.d/ssh 中。参数 -i 非常重要,因为它告诉 sshd(8) 它正在从 xinetd(8) 运行。
service ssh
{
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
per_source = UNLIMITED
log_on_failure = USERID HOST
access_times = 08:00-15:25
banner = /etc/banner.inetd.connection.txt
banner_success = /etc/banner.inetd.welcome.txt
banner_fail = /etc/banner.inetd.takeahike.txt
# log_on_success = PID HOST DURATION TRAFFIC EXIT
# instances = 10
# nice = 10
# bind = 192.168.0.100
# only_from = 192.168.0.0
# no_access = 192.168.54.0
# no_access += 192.168.33.0
}
最后,通过向 xinetd(8) 发送 SIGHUP 来重新加载配置。
- ↑ Peter N M Hansteen (2011). "使用 PF 进行防火墙".