1.3 安全组:从全开到最小权限的血泪课

这一节是我在云上交的第一笔学费。不是钱,是凌晨三点被报警电话吵醒的代价。如果你只想从本篇带走一句话,那就是:新买的云服务器,安全组默认是全开的,买完第一件事就是改它。

一、事故:一个开放在公网的 3306 端口

事情出在项目上云第二周。当时我正准点睡觉,手机突然震个不停——是我自己设的服务器监控告警。打开一看,CPU 占用 100%,数据库连接数异常。爬起来登服务器,入站连接刷屏:

$ netstat -an | grep 3306 | wc -l
2412

两千多个外部连接挂在我的 MySQL 上,全是陌生 IP。显然,我的数据库端口直接暴露在公网上,被扫到了。那阵子我图省事,在服务器上自己装了 MySQL,安全组是购买时的默认状态——入站规则全放行。扫描器一发现这台机器开着 3306,就开始拿常见密码字典跑 root 账号。

我当时的 MySQL root 密码……不说了,弱得像没设。他们还没跑破密码,光连接数就把我服务器资源耗光了。处理办法很笨:先改安全组只留 22 和 443,再封掉这些连接,前后折腾到天亮。

那一晚我一共封了两千多个恶意 IP,写了条定时任务自动分析日志封 IP,还给报警策略加了连接数阈值。天亮之后我喝着咖啡想:这些善后手段,其实都不如一开始就把安全组改对来得干净。

整个事件的时间线整理出来,挺值得回看:

时间 事件 我当时的反应
凌晨 3:02 告警短信到达 还在睡,手机静音没听见
凌晨 3:40 用户群里有人问站点异常 爬起来看监控
凌晨 3:55 确认是 MySQL 被外部连接占满 手忙脚乱翻安全组
凌晨 4:10 安全组改为最小权限,连接数回落 长舒一口气
凌晨 4:30 写定时任务封禁恶意 IP,报警策略加阈值 决定把规矩定下来

如果安全组一开始就是最小权限,这事根本不会发生——扫描器连 3306 都探测不到,自然不会引来后面的连接洪流。故障的根因不是我运气差,是我自己给攻击者留了门。

二、安全组是什么:云上的虚拟防火墙

吃了亏我才认真去读文档。安全组是 CVM 的虚拟防火墙,对实例的入站和出站流量做过滤,规则有方向、协议、端口、来源这几个要素,控制台里点点就能配,不用登服务器改 iptables。

有几个关键特性必须知道:

  • 安全组是有状态的:放行了一个入站请求,它的响应流量会自动放行,不用再单独给出站开规则。第一次配的人常犯的错是给响应流量也加规则,其实没必要。
  • 规则有优先级:从上往下匹配,先命中先生效,自定义规则优先于默认规则。
  • 可以关联多台实例:一组规则管一批机器,适合规模起来之后统一管理。
  • 修改即时生效:规则保存后几乎立刻对现有连接和新连接生效,改错了也会立刻体现,所以生产环境改动要谨慎。

举一个有状态的实际例子:用户通过 443 端口请求你的网页,安全组放行了这个入站包,服务器返回的响应包(目标端口是用户那边的高位随机端口)不会被出站规则拦下,整个过程自动完成。如果安全组是无状态的,你得手动给所有可能的响应端口开规则,那这功能就没法用了。理解了有状态这一点,出站规则默认全开就很好懂——反正响应流量本来就要走。

我对安全组的理解前后经过三个阶段:刚买机器时不知道它存在,出事后知道它是全开的,出事之后明白它应该是最小权限的。完整的概念说明见云服务器概述文档入口里的安全组章节。

三、从全开到最小权限:我的规则清单

改完之后的规则表是这样的,你可以直接照抄:

协议/端口 来源 策略 说明
TCP 22 我家办公 IP 允许 SSH 只放自己的 IP
TCP 443 0.0.0.0/0 允许 HTTPS 对所有用户开放
TCP 80 0.0.0.0/0 允许 仅用于跳转到 443
TCP 3306 内网网段 允许 数据库只走内网
其他全部 任意 拒绝 兜底

这套规则背后的思路就三条:业务端口只开用户真正需要的(80 和 443);管理端口锁死来源(SSH 只放自己的 IP);内部服务一律不暴露公网。每一条规则都要能回答:这个端口为什么必须开?开给谁?答不上来就删掉。

后来数据库挪到了云数据库上,3306 这条规则直接删了。云数据库的网络隔离做得更彻底,根本不暴露公网,这是第二篇的内容,这里只放结论:数据库能用托管服务就别自己往公网上开。

22 端口限制来源 IP 有个问题:家庭宽带 IP 是会变的。我的办法是办了个动态 DNS,IP 变了脚本更新安全组规则;嫌麻烦也可以 TCP 22 只放公司 IP 加备用方案,或者干脆用 VPN/堡垒机。再不济,退一步把 SSH 改成只允许密钥登录、关掉密码认证,被扫也不怕爆破。我没有一上来做堡垒机这种重活,就是限制来源 IP 加密钥登录,对个人项目来说足够了。

四、系统层再上一道锁

安全组是云上的第一道门,服务器系统内部还可以再配一层,我两道都做:

层级 工具 作用
云网络层 安全组 挡住绝大多数扫描
系统层 ufw 或 firewalld 防止应用误开端口
应用层 应用自身配置 绑定内网地址,不监听 0.0.0.0

第三条容易被忽略。有些应用启动时默认监听所有地址(0.0.0.0),以为安全组挡着就没事,万一哪天规则误改,底裤就没了。我的做法是让服务只监听内网或 127.0.0.1,外面有 Nginx 反向代理进来,应用端口压根不出门。

另外出站规则也别完全不管。默认出站一般全开,对多数业务没影响,但如果你的服务器被种了马,它往外发包就没人拦。要求高的场景可以把出站也限制成只放行必要的目标端口,至少挡住非常规端口的对外连接。

五、多台机器时的安全组管理

后来我的服务器不止一台,安全组管理的坑又来了。一开始每台机器各配各的规则,改一次要登好几台。后来我把同类的机器归到一个安全组里——比如 Web 服务器一组、后台任务机一组——一组规则管一批机器,改一次就够了。安全组是可以关联多个实例的,云数据库也有自己的安全组体系,数据库实例只放行 Web 服务器所在安全组的内网访问,比一条条写 IP 网段干净得多。

服务器数量多起来之后,我还给自己定了个规矩:每次改动安全组,先在测试机上演练一遍。生产环境的规则改动一旦写错,可能把正常用户也挡在门外,这种事我见过别人出过,把自己网站关在外面半小时,排查半天才发现是规则里源 IP 写错了网段。

六、事后检查清单

经过那次事故,我给自己定了规矩,每次开新机器或改网络都过一遍:

  • [ ] 安全组没有 ALL-ALL 入站规则
  • [ ] SSH 只允许密钥登录,密码认证关闭
  • [ ] 管理端口(22 等)限制来源 IP
  • [ ] 业务端口只开必要的(80/443)
  • [ ] 数据库、缓存等内部服务不暴露公网
  • [ ] 系统防火墙与安全组规则保持一致

这份清单我存在手机备忘录里,每加一台机器必过一遍,花不了三分钟。比起凌晨爬起来处理事故,三分钟的仪式感便宜得不像话。后来我把这套规矩也讲给了身边几个上云的朋友,他们中最起码有两个人,是在照着清单走完之后才发现自己机器的 22 端口对全网放着。

至于为什么那台机器的 MySQL 没有第一时间发现被扫——因为我当时根本没配监控和告警,全靠用户投诉。装完监控和告警之后,这类事我会比用户先知道。告警的完整配置方法在第五篇云监控那一节,这里先留个印象:监控不是可选项。

七、面板用户的提醒

如果你跟我一样用 1Panel 管理服务器,面板本身会在系统层开放面板端口。装完面板第一件事是改默认端口、开二次验证,别让管理面板亮在公网上。相关的注意事项可以看1Panel 常见问题,面板的安装和基础用法见1Panel 运维面板实战。

安全这件事,代价最小的时候就是买完机器的那一刻。等你被扫描器盯上,再改就来不及了。公网上从来不缺自动扫描器,它们日夜不停地拂过每一个 IP,寻找敞开的端口——区别只是,有的人第二天就改好了规则,有的人要等一次事故才学会这一课。希望你是前者。

延伸阅读