“独门独户”型个人服务器/集群的安全实践
个人维护的话,特点是精力欠缺,维护周期和时间得不到保证,尤其是周期,这个其实很致命。 很多事情可能每天能抽十分钟出来看看也许就避免了,但很多时候就是懒得看。
安全体系结构的设计
缩小暴露面积
a. 能缩进内网就缩内网,用frp、端口映射一类的转外网;
b. 能不开的端口就不开,能合并的合并。合并端口是为了降低管理难度;
c. 善用IP封锁,比如一些肯定是给自己用的东西,就把国外IP封掉。
孤立运行环境,是软件就有漏洞,避免出了漏洞一窝端
a.不要吝啬虚拟机,该装就装,不相关服务不要塞到一起,那点CPU跟数据比不值钱
b.能用
docker
就用凡事多考虑:要是这一层措施失效了咋办?
可以考虑开
Guacamole
这样的中心服务器,然后关掉面向其他方向的ssh
访问。但中心这台Guacamole
一定要保护好,TOTP啥的都给开了。
暴力/打枣型 ssh 攻击的防御
玩VPS的新手可能很难想象ssh
会遭到多少攻击。 基本上就是VPS开通之后的几个小时之内,就会有人开始不间断的扫端口,试密码。 自己看看日志,能重塑三观。
相关信息
网上教程往往说/var/log/secure
是ssh日志,但新版ubuntu
根本没这个文件。
等价的文件是/var/log/auth.log
文件。grep
一下"Failed"
就懂了。
或者用journalctl -u ssh --since yesterday
这一套查看。
应对方法,最简单的就是安装sshguard
,然后别忘了systemctl start sshguard
、systemctl status sshguard
一下确保启动成功。 接下来就不用管了,sshguard
会按失败次数不断延长封禁时间,基本上被封几次对方就不会再做无用功,即使有特别执着的,八个小时解封一次又能尝试多少密码?
至于改ssh端口号的措施,只有说有点用,作用不大,甚至不够称作“安全措施”,最多是一个“降低拦截日志密度避免心烦”的小技巧。
防火墙的配置策略
如果是阿里云啥的,就不用管了,本身就自带外部防火墙,明明白白。
如果是不带防火墙的小VPS,那就是需要自己配。 你去网上查防火墙,大部分都让学iptables
,这玩意儿好是好,但一时学不会也不要耽误开防火墙。
重要
新手玩防火墙,第一原则是不要把自己锁外面,尤其是当你的VPS不提供后备终端链接渠道的时候,别浪。
所以为啥不用ubuntu自带的ufw
呢?看看帮助三分钟就学会用法了。
重要
再强调一遍,ufw enable
之前,先把ssh
的端口加进去,ufw allow 22/tcp
.
具体使用就不多说了。说说备份。组织好规则之后肯定是想备份下来,但配置文件备份挺麻烦的。 有个简单的办法是ufw show added
,这个不仅方便检查状态,把显示的内容复制下来就正好是n条你执行过的开放端口命令。
小知识
ufw
添加443端口的时候,不要只添加tcp
,udp
也放开。新版的QUIC
协议是UDP
的。
基于 ip 的定向防御
这个特指caddy
,nginx
应该也有,但caddy
的自动https
实在是给的太多了.jpg
主要是把一些自用的服务,使用IP封禁的方式限制在国内访问,甚至国内少数几个省访问。
定制版caddy
需要特殊编译的caddy
,因为caddy
的插件是编译器加入的。具体需要的主要是caddy-maxmind-geolocation
插件。 此外还有geoipupdate
和caddy-geo-ip
两个插件,但这两个似乎是自动添加的。总之我没有直接使用。
教程里面很多编译caddy
的,但不见得非要这么麻烦,caddy
官网提供了在线自助编译的渠道,估计就是有人请求就编译一下存起来吧。 在这里搜索maxmind
,点选caddy-maxmind-geolocation
,再下载就行了。
当然,你可能想要系统原装caddy
的那一套服务文件,又不想替换破坏系统包,这个是可以做到的:
sudo dpkg-divert --divert /usr/bin/caddy.default --rename /usr/bin/caddy
sudo mv ./caddy /usr/bin/caddy.custom
sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.default 10
sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.custom 50
sudo systemctl restart caddy
Caddyfile 文件配置
假定我们已经配好了caddy
,获得了maxmind
的geoip
文件(见下一节),则可以用如下命令,加在Caddyfile
中实际响应命令的语句之前,拒绝国外IP的连接。
@cnFilter {
maxmind_geolocation {
db_path "/caddyGeo/Country.mmdb"
deny_countries CN
}
}
respond @cnFilter 502 {
close
}
不要怀疑上面的deny_countries CN
这一句,这纯粹是deny_countries
这个命令的名字设计的不好。 @cnFilter
的作用是在IP不属于中国的时候命中,然后给他返回个502,为啥不是403呢?这叫虚虚实实,说不定他以为后台已经完蛋了就放弃了。
如果有多个站点需要屏蔽,可以用代码片段的方法重用,在Caddyfile
开头这样写:
(cnFilterBlocker) {
@cnFilter {
maxmind_geolocation {
db_path "/caddyGeo/Country.mmdb"
deny_countries CN
}
}
respond @cnFilter 502 {
close
}
}
然后在每一个站点的关键相应命令之前写:import cnFilterBlocker
就行了。
geoip 文件的获取和更新
很显然,上面的一切都依赖于那个/caddyGeo/Country.mmdb
,而这个文件显然是需要自动更新的。
前面说caddy自带一个geoipupdate
插件,但折腾起来比较麻烦,还要申请maxmind
的API账号。
还有一个简单粗暴的办法,就是单独写个脚本下载和更新:
#!/bin/bash
# 下载文件到临时文件
wget -O /caddyGeo/temp.mmdb https://cdn.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/Country.mmdb
# 验证临时文件大小
filesize=$(stat -c%s /caddyGeo/temp.mmdb)
if [[ $filesize -gt 50000 ]]; then
# 删除原文件并将临时文件替换为真正的文件
rm -f /caddyGeo/Country.mmdb
mv /caddyGeo/temp.mmdb /caddyGeo/Country.mmdb
# 重启caddy服务
systemctl restart caddy
echo "Caddy服务已重启"
else
echo "下载的文件大小小于等于50K,无法通过验证"
rm -f /caddyGeo/temp.mmdb
fi
因为是国内下国外,要十分注意下载失败、中断的处理。
然后,加个cron
命令,半夜三点四点执行一下这个脚本,就完事儿了。
0 3 * * * /bin/bash /caddyGeo/update.bash
0 4 * * * /bin/bash /caddyGeo/update.bash
记得隔两天后起码回来检查一次,看文件是不是更新了。
自编 ssh 哨兵
最后,其实还有一个阴招可以用:万一上面的措施都被搞了,起码我们要即使知道有异常登陆出现,而不是等到服务中断、或者下次有时间ssh登陆并且有心情查看登录日志的时候。
重要
这个方法其实漏洞颇多,主打一个出其不意,聊胜于无。
在/etc/ssh
目录新建一个/etc/ssh/sshrc
脚本,这个脚本的内容将在任意用户登陆,且用户目录下没有sshrc
文件的情况下被执行。 内容很自由,比如说在检测到ssh登陆的时候给自己发个邮件。
经测试,这个对ssh
和scp
都有效,但对于隧道链接无效。建立隧道的时候不会执行sshrc
.
当然通过本机发邮件一般都会被商业邮箱拉黑。最好是写个python脚本拿商业邮箱(比如QQ邮箱就不错)的SMTP服务来发。 坏处是慢一点。有空自己折腾一个域名邮箱当然是最好的了。
# 应该都是内置库
import os
import socket
import smtplib
import base64
from email.mime.text import MIMEText
from email.header import Header
from datetime import datetime
# 邮件参数
username = os.environ['USER']
hostname = socket.gethostname()
ip = os.environ['SSH_CONNECTION'].split(' ')[0]
# 获取当前时间
now = datetime.now()
current_time = now.strftime("%Y-%m-%d %H:%M:%S")
# 构建邮件内容
subject = f'SSH {hostname} @ {current_time}'
message = f'User {username} has logged into {hostname} from {ip} at {current_time}.'
# 发送邮件
msg = MIMEText(message, 'plain', 'utf-8')
msg['From'] = Header("xxxx")
msg['To'] = Header("xxxx")
msg['Subject'] = Header(subject)
smtp = smtplib.SMTP("xxxx", "xxxx")
smtp.ehlo()
smtp.starttls()
smtp.ehlo()
smtp.login("xxxx", "xxxx")
smtp.sendmail("xxxx", "xxxx", msg.as_string())
smtp.quit()
另外,如果有多个机器,还有两条很有用的命令,让上面生成的邮件变得容易看:
永久调整时区为中国:sudo timedatectl set-timezone Asia/Shanghai
更改主机名:sudo hostnamectl set-hostname xxxx