使用动态 IP 地址限制对 SSH 的访问

使用动态 IP 地址限制对 SSH 的访问

我已经禁用PasswordAuthenticationSSH,并使用公钥-私钥登录。但是,许多指南(特别是这个)我看到建议只允许从我的特定 IP 地址进行 SSH 访问。

/etc/ssh/sshd_config:
    AllowUsers deploy@(your-ip) deploy@(another-ip-if-any)

ufw allow from {your-ip} to any port 22
  • sshd_config通过 IP 地址进行限制并添加防火墙规则有什么意义吗?

  • 假设我有一个动态 IP 地址,那么这些规则似乎毫无用处,因为我必须使用托管提供商的控制台终端,以 root 身份登录才能允许从我的家庭 IP 进行访问。在这种情况下:建议的路线是什么?

1:除非我首先使用托管服务提供商终端,否则禁止所有通过 SSH 登录,以根身份登录以允许从我的家庭 IP 访问 SSH?

2:保持SSH端口开放,并信任公私密钥进行登录。

3:还有其他建议吗?

答案1

好吧,我做了很多挖掘,但一无所获。因此,我为我的家用电脑设置了动态 DNS 服务,并创建了以下 bash 脚本,希望它对其他人有用。

指示:

  • 这个脚本不是特定于 SSH,是一种允许动态主机使用 ufw 永久访问目标端口的方法 - 不管该端口上侦听的服务是什么。因此,使用端口 22(默认情况下)将允许从动态主机永久访问 SSH。同样,您可以使用此脚本确保从动态主机永久访问 FTP(端口 21)之类的东西。

  • 只需修改文件顶部的RemoteHostName和变量即可。显然,是动态 DNS 提供商提供的主机名,是我们允许访问的端口。PortNumberRemoteHostNamePortNumber

  • 如果解析的 IP 已经RemoteHostName存在于可以访问目标端口的 ufw 中,则脚本退出,因为没有什么可更改的。

  • 如果不存在,则删除 ufw 中所有具有目标端口号的条目,然后使用更新后的 IP 地址和目标端口添加新条目。简而言之,执行不是尝试对脚本中涉及端口的其他规则使用 ufw,因为脚本将遍历它们。

  • 如果该脚本由 cron 作业或类似程序调用,那么理论上它应该始终保持 ufw 中的 IP 地址更新。

  • 该脚本输出以下内容:

    1) Standard output: Could not resolve the IP address of the RemoveHostName XYZ
    2) Standard output: No change necessary as the IP address XYZ already has access to port XYZ.
    3) Standard output: Deleting old ufw rule: XYZ
    4) Standard output: Adding new ufw rule: XYZ
    5) Error output: Could not resolve the IP address of the RemoveHostName XYZ
    
  • 但请注意,我是不是非常熟悉 bash 脚本,但对正则表达式却不太熟悉。所以如果你使用这个脚本,请帮自己一个忙(也帮我一个忙!),仔细检查一下。如果你发现任何错误,请发表评论或新的答案。

脚本:

#!/bin/bash


##############################################################
#Variables
##############################################################    
RemoteHostName=someremotehost.dyndnsorsomething.com
PortNumber="22"


##############################################################
#Check if the dynamic IP address exists within ufw.
##############################################################
DynamicIPAddress=$(host $RemoteHostName | awk '/has address/ { print $4 }')
if [[ $DynamicIPAddress = "" ]]
then
    echo $(date -u -Iseconds): Could not resolve the IP address of the RemoveHostName $RemoteHostName >&2
    exit
fi

UFWRules=$(ufw status numbered)
REGEX=" $PortNumber (.*)ALLOW IN(.*)$DynamicIPAddress$"
while read -r line; do
    if [[ $line =~ $REGEX ]]
    then
        echo $(date -u -Iseconds): No change necessary as the IP address $DynamicIPAddress already has access to port $PortNumber.
        exit
    fi
done <<< "$UFWRules"


##############################################################
#Remove all entries with the given port number.
##############################################################
while true
do

    UFWRules=$(ufw status numbered)
    REGEX="(.*) $PortNumber (.*)ALLOW IN(.*)"
    Matched="No"
    while read -r line; do
        if [[ $line =~ $REGEX ]]
        then
            echo $(date -u -Iseconds): Deleting old ufw rule: $line
            RuleNumber=$(echo $line | (cut -d "[" -f2 | cut -d "]" -f1))
            ufw --force delete $RuleNumber
            Matched="Yes"
            break
        fi
    done <<< "$UFWRules"

    if [ $Matched = "No" ]
    then
        break
    fi

done


##############################################################
#Add in access from the dynamic IP address.
##############################################################
echo $(date -u -Iseconds): Adding new ufw rule: "ufw allow from $DynamicIPAddress to any port $PortNumber".
ufw allow from $DynamicIPAddress to any port $PortNumber

答案2

如果您在同一台主机上运行 Web 服务器,这意味着可能也安装了 PHP,那么您可以安装我编写、使用并积极维护的工具:

https://github.com/cubiclesoft/web-knocker-firewall-service/

需要 SSH 访问的客户端在其所选操作系统上将客户端部分作为系统服务运行。客户端使用不频繁的加密数据包保持服务器的 SSH 端口和任何其他受保护端口(例如 POP3/IMAP)打开。这是一个非常不错的即发即弃解决方案。我个人使用该软件时没有遇到任何问题,但您可能希望在将其部署到生产环境之前在 DigitalOcean droplet 或类似服务上对其进行测试。除非发送续订数据包(客户端默认每 10 分钟尝试续订一次),否则端口将保持打开状态长达 30 分钟(默认),这允许 IP 地址在最短的停机时间内更改。当为 IP 地址打开端口时,可以选择向配置为接收此类通知的任何人发送电子邮件通知。

目标是通过关闭敏感端口来大幅限制攻击面,因为对于动态 IP 上的客户端,没有加密密钥的用户无法设置复杂的 VPN。即使攻击者最终使用相同的未锁定 IP 地址(例如咖啡店 WiFi 上的笔记本电脑),他们仍然必须通过 SSH,但至少俄罗斯、中国和朝鲜无法看到该端口。

顺便说一句,我不使用 ufw。它用一堆不必要的链污染了 iptables,使 iptables 规则变得比需要的复杂得多,而且规则越多,通过 iptables 处理数据包的速度就越慢。不久前,我写了一篇关于编写优雅 iptables 规则的文章:

http://cubicspot.blogspot.com/2016/06/elegant-iptables-rules-for-your-linux.html

实际上,直接使用 iptables 要容易得多,而且当您可以复制粘贴好的干净规则集时,它比使用 ufw 更省事。另外,您永远不知道何时会卡在 CentOS 机器上。

答案3

我对发表于@R4D4 的回答(并由@muru 编辑),因为我在 UFW 规则中有一行允许从本地网络进行 SSH 访问。

我还想在规则中包含协议 TCP,因为 SSH 使用 TCP。我希望在评论中看到上次更新/恢复 UFW 规则的时间。也许,这可以通过不同的方式实现,如果可以的话请告诉我,但它似乎有效:

#!/bin/bash


##############################################################
#Variables
##############################################################
RemoteHostName=mydomain.com
PortNumber="22"


##############################################################
#Check if the dynamic IP address exists within ufw.
##############################################################
DynamicIPAddress=$(host $RemoteHostName | awk '/has address/ { print $4 }')
if [[ $DynamicIPAddress = "" ]]
then
    echo $(date -u -Iseconds): Could not resolve the IP address of the RemoveHostName $RemoteHostName >&2
    exit
fi

UFWRules=$(ufw status numbered)
REGEX=" $PortNumber (.*)ALLOW IN(.*)$DynamicIPAddress$"
while read -r line; do
    if [[ $line =~ $REGEX ]]
    then
        echo $(date -u -Iseconds): No change necessary as the IP address $DynamicIPAddress already has access to port $PortNumber.
        exit
    fi
done <<< "$UFWRules"


##############################################################
#Remove all entries with the given port number.
##############################################################
while true
do

    UFWRules=$(ufw status numbered)
    REGEX="(.*) $PortNumber\/tcp (.*)ALLOW IN(.*)"
    Matched="No"
    while read -r line; do
        if [[ $line =~ $REGEX ]]
        then
            echo $(date -u -Iseconds): Deleting old ufw rule: $line
            RuleNumber=$(echo $line | (cut -d "[" -f2 | cut -d "]" -f1))
            ufw --force delete $RuleNumber
            Matched="Yes"
            break
        fi
    done <<< "$UFWRules"

    if [ $Matched = "No" ]
    then
        break
    fi

done


##############################################################
#Add in access from the dynamic IP address.
##############################################################
echo $(date -u -Iseconds): Adding new ufw rule: "ufw allow from $DynamicIPAddress to any port $PortNumber proto tcp".
ufw allow from $DynamicIPAddress to any port $PortNumber proto tcp  comment "the rule for this IP was added by the bash script automatically on: $(date)"

##############################################################
#Restore access from IPs of my local network 
##############################################################
echo $(date -u -Iseconds): Restoring the ufw rule: "ufw allow from 192.168.192.0/24 to any port 22 proto tcp".
ufw allow from 192.168.192.0/24 to any port 22 proto tcp comment "restored by the bash script access to the local network on: $(date)" 
ufw reload

答案4

R4D4 的出色脚本 - 但我也做了一些改进:

应该可以从多个动态 IP 地址进行访问。因此,我修改了以下内容:

  • 创建防火墙规则时,实际主机名将作为注释输入。
  • 当尝试删除防火墙规则时,每个脚本执行仅处理为实际主机名创建的规则。

因此,我现在可以根据需要对尽可能多的动态主机名执行脚本:

./restrictLogin.sh host1.domain.tld
./restrictLogin.sh host2.domain.tld
./restrictLogin.sh something.totally.different.org
./restrictLogin.sh yet.anotherone.com

这也允许您创建单独的规则(针对静态 IP),而无需被脚本清除。

因此,脚本输出也与原始示例略有不同。示例ufw status numbered

     To                      Action      From
     --                      ------      ----
[ 6] 22                      ALLOW IN    188.xx.xx.xx  #host1.domain.tld
[ 7] 22                      ALLOW IN    178.xx.xx.xx  #host2.domain.tld
[ 8] 22                      ALLOW IN    80.xx.xx.xx   #host3.domain.tld

版本:

#!/bin/bash


##############################################################
#Variables
##############################################################    
RemoteHostName=$1
PortNumber="22"

##############################################################
#Check if the dynamic IP address exists within ufw.
##############################################################
DynamicIPAddress=$(host $RemoteHostName | awk '/has address/ { print $4 }')
if [[ $DynamicIPAddress = "" ]]
then
    echo $(date -u -Iseconds): Could not resolve the IP address of the RemoveHostName $RemoteHostName >&2
    exit
fi

UFWRules=$(ufw status numbered)
REGEX=" $PortNumber (.*)ALLOW IN(.*)$DynamicIPAddress(.*)#\s*$RemoteHostName$"
while read -r line; do
    if [[ $line =~ $REGEX ]]
    then
        echo $(date -u -Iseconds): No change necessary as the IP address $DynamicIPAddress / $RemoteHostName already has access to port $PortNumber.
        exit
    fi
done <<< "$UFWRules"


##########################################################################
#Remove all entries with the given port number and RemoteHostName comment.
###########################################################################
while true
do

    UFWRules=$(ufw status numbered)
    REGEX="(.*) $PortNumber (.*)ALLOW IN(.*)#\s*$RemoteHostName"
    Matched="No"
    while read -r line; do
        if [[ $line =~ $REGEX ]]
        then
            echo $(date -u -Iseconds): Deleting old ufw rule: $line
            RuleNumber=$(echo $line | (cut -d "[" -f2 | cut -d "]" -f1))
            ufw --force delete $RuleNumber
            Matched="Yes"
            break
        fi
    done <<< "$UFWRules"

    if [ $Matched = "No" ]
    then
        break
    fi

done


##############################################################
#Add in access from the dynamic IP address.
##############################################################
echo $(date -u -Iseconds): Adding new ufw rule: "ufw allow from $DynamicIPAddress to any port $PortNumber comment '$RemoteHostName'".
ufw allow from $DynamicIPAddress to any port $PortNumber comment $RemoteHostName

相关内容