前段时间给某内部业务系统做架构升级,这次和以往不太一样,安全和高可用第一次被放进了同一个方案里来设计。WAF防护层选的是雷池社区版,从一开始的单节点试水,到最终双节点高可用的落地,整个过程踩了不少坑,也沉淀了一些值得记录的配置细节。这篇分享就按照我当时落地的顺序来写:为什么选它、架构怎么摆、配置怎么做、故障怎么排。如果你正在评估自建WAF的可行性,或者已经装了雷池但还没把它接入高可用链路,这篇内容应该能帮你少走一些弯路。
1. 从一次访问中断说起:安全和高可用为什么不能分开设计
1.1 一个不算罕见的事故
这套系统上线三个月左右的时候,赶上过一次比较严重的故障。当时后端是两台服务器,其中一台因为硬件告警被离线维护,剩下那一台单点扛着所有流量。结果当天下午刚好有营销活动,访问量比平时高出两三倍,这台服务器的CPU直接被拉满,页面超时、接口大面积报错。排查到一半又发现入口层的防护规则没生效,日志里全是扫描和注入尝试,部分恶意请求竟然直接穿了进去。
最后服务虽然恢复了,但复盘会上大家基本达成一个共识:安全策略和高可用过去是分开管的,可它们实际互相影响。单点Web节点会挂,单点WAF节点同样会挂,而如果WAF挂了之后流量绕过检测或者直接中断,业务的可用性反而会因为多了一层防护而变得更差。这件事直接推动我们定下了接下来的改造目标:入口流量必须经过安全检测,检测节点不能成为新的单点,后端应用全部无状态化,数据库单独做冗余。
1.2 选型思考:为什么最后是雷池社区版
先说选型。WAF 这一层我们对比过几类方案。
自建反向代理加Lua脚本做检测,最灵活,但维护成本高。本质上还是靠正则匹配,面对混淆过的攻击容易漏,规则库得自己花时间维护,对一个小团队来说并不现实。商用WAF功能全,但授权费不低,而且策略大多托管在厂商的控制台上,碰到需要离线调试的场景会有点被动。云WAF接入最简单,但当时这套系统是混合网络环境,一部分流量没办法全部引流到云上,接入层统一性不好解决。
后来试了雷池社区版,几个特点比较贴合我们的情况。纯Docker部署,可以放进自己的网络环境里,数据留在自己手上。检测不是单纯跑正则,而是做语义分析,对一些变形攻击识别效果好一些。社区版把核心WAF功能开放得比较完整,没有明显缩水,这点对我们这种预算敏感的小团队是加分的。
但也要说清楚它的局限。社区版的多节点配置同步和统一管理能力不如商业版那么省心,两台WAF节点之间的配置一致性需要自己想办法维护,节点切换后的状态漂移问题也得自己在架构层面去兜底。这一点在后面故障演练部分会细说。
1.3 架构目标:把“挡得住”和“不停机”统一起来
这次改造的架构目标可以归纳成三点。
第一,持续可用。任何一个单点组件,包括WAF节点本身,都不能成为链路上的故障源。这就要求每一层都有冗余,并且在故障发生时能自动切换或者快速摘除。第二,攻击可挡。恶意流量进入后端之前必须经过安全检测,且检测失败时不能放行危险请求,也不能把正常请求全部拦死。第三,事后可查。攻击日志、访问日志、拦截记录都要能留存一段时间,方便出问题时回溯。
基于这三点,最终的架构形态其实很容易画出来:入口接入层做负载均衡,中间挂两台雷池WAF节点,WAF后面是Web应用集群,最底下是数据库主从。每一层都可以独立扩缩容,每一层也都做了冗余。后面所有配置工作都是围绕这条链路展开的。
2. 架构设计与部署准备
2.1 高可用Web架构的最终形态
先把我最后落地的拓扑写出来,方便后面配置部分对照理解。用户请求首先到入口接入层,入口用的是自建Nginx加Keepalived做VIP漂移。由于涉及两台Nginx机器,正常情况下一台对外提供服务,另一台处于热备状态,主节点挂了以后虚拟IP会自动漂移到备用节点。
流量从接入层继续向后转发,下一站是两台雷池WAF节点。这两台做的是主备模式,接入层的Nginx通过upstream把请求同时配置给两个WAF节点,默认权重相等。当其中一台WAF异常时,Nginx的健康检查机制会在几十秒内自动把它摘除,请求全部打到健康的节点上。
WAF节点再往后是Web应用集群,两台无状态应用服务器。这一层没有做复杂的一致性保障,要求应用本身不能保存用户会话状态,Session统一放到Redis集群。这样任意一台Web节点宕机,另外一台都能无缝承接全部流量。
数据库这层跑了MySQL主从复制,主库负责写入,从库负责读取,同步模式用了半同步,在性能和可靠性之间取了平衡。一旦主库异常,从库可以手动或者通过脚本提升为新的主库。
这条链路每一层都坏过,也都在故障演练中验证过替换路径。实际配置过程中的很多细节,恰恰都藏在每个组件的健康检查、超时时间、转发策略这些看似不起眼的参数里。
2.2 部署环境规划
部署前先规划了一套参考配置,这里整理出来给大家一个基准。
| 组件 | 数量 | 参考配置 | 数据盘建议 |
|---|---|---|---|
| 接入网关 | 2 | 2核4G | 不需要单独数据盘 |
| WAF节点 | 2 | 4核8G起步,生产建议8核16G | 日志盘100G,数据盘50G |
| Web应用节点 | 2 | 4核8G | 日志普通盘即可 |
| Redis节点 | 2 | 2核4G | 内存型,持久化关掉或开AOF |
| 数据节点 | 2 | 8核16G | 系统盘外单独挂数据盘,300G起步 |
经验之谈,WAF节点最容易忽略的是磁盘规划。雷池会积累攻击日志、访问日志、审计记录等,日志量大的时候一周就能写掉几十个G。如果日志盘和系统盘混用,根分区一旦写满,Docker容器很容易进入异常状态,丢日志是小,整个WAF节点无响应才是大麻烦。所以给日志单独挂一块盘,和数据盘分开,是相当重要的前置操作。
网络规划上也有一点要提醒:管理端口和数据端口不要混在同一张网卡上。实际部署时雷池控制台的管理端口只在内网放行,加上来源IP白名单,管理面和业务面彻底分开。
2.3 雷池社区版的安装与初始化
雷池社区版的安装本身并不复杂,官方提供了一键脚本,确认Docker环境没问题以后基本几步就能跑起来。这里有一个容易被新手忽略的点:如果部署环境在内网,外网镜像源不通,需要预先在能联网的机器上把镜像拉下来,再导出、导入到生产环境里。我们一开始没注意这点,导致安装脚本卡在拉镜像那一步,浪费了大概半小时。
控制台初始化的时候会要求设置管理员密码,建议使用密码管理器生成随机强密码,不要图省事用统一密码。初始化完成后,管理端口默认走HTTPS,需要给控制台访问加一层来源限制。我习惯把管理端口只开放给内部跳板机的IP,业务网段完全不让碰管理面。
再补充一点关于Docker部署的理解。很多人会问为什么WAF不用裸机安装或者虚拟机镜像。我的实际体会是,Docker部署带来的最大好处是迁移和备份极其方便。整机的雷池数据目录可以直接打包带走,新节点起来之后把数据目录恢复进去,配置和后台上传的证书都能自动回来,这在双节点高可用架构里是做故障重建的基础能力。
安装完先不要急着对接业务。建议在控制台里熟悉一遍功能布局,把基础的防护模块浏览一遍,对雷池的日志结构有个印象,再进入核心链路配置。
3. 核心链路配置:把流量安全送到后端
3.1 接入层的负载均衡与健康检查
接入层是整套架构的入口,同时也是第一道可用性防线。我们用Nginx加Keepalived的方式来做。Keepalived负责VIP漂移,Nginx负责流量转发。正常情况下一台Nginx节点持有VIP对外提供服务,另一台备用。如果主节点宕机,Keepalived检测到服务异常后,会让虚拟IP漂移到备用节点,整个过程通常在几秒内完成。
Nginx的upstream配置直接指向两台WAF节点,关键参数是max_fails和fail_timeout。下面给一份我当时使用的参考配置:
upstream waf_cluster { server 10.0.1.11:80 max_fails=2 fail_timeout=30s; server 10.0.1.12:80 max_fails=2 fail_timeout=30s; } server { listen 80; server_name app.example.com; location / { proxy_pass http://waf_cluster; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }这里max_fails和fail_timeout的含义是,如果某个WAF节点在30秒内失败两次,Nginx会在接下来一段时间内主动把它摘除,不再往这个节点转发请求。这个参数决定了WAF宕机后接入层需要多久才能感知到并完成摘除,直接影响到故障切换的速度。
接入层自身的健康检查节点,建议加一个简单的TCP层面探测就够了,不需要做复杂的HTTP探测。因为我们真正需要检测的是下游WAF节点的状态,接入层只要保证VIP两边都是活着的就行。
3.2 WAF节点的上游配置与证书处理
雷池控制台里的站点管理,核心配置点是上游服务器。上游服务器填的就是后端Web应用节点的内网IP和端口。权重在初始配置时两端先平均分配,等验证了一段时间,再根据实际负载情况微调。
一个关键决策是SSL证书放在哪一层。我们的选择是放在WAF节点上,由WAF终止SSL,再以HTTP转发给后端Web节点。这样做的原因很直接:雷池的检测是针对明文HTTP内容做的,如果使用SSL透传,WAF看不到解密后的请求体,很多检测能力会大打折扣。对SQL注入、XSS这类攻击检测来说,看不到明文就等于失去了一半战斗力。
当然这也有代价。WAF如果不可用,接入层会尝试把请求发给另一台健康的WAF节点,但如果两台都异常,整个链路就没有证书可以提供服务了。所以证书放在WAF层,意味着对WAF可用性的要求更高。这也再次印证了WAF自身必须做冗余的必要性。
另外,证书到期问题在安全架构里特别容易被忽略。我们吃过这个亏,某次证书到期后,WAF所在的端口校验失败,用户访问直接弹出证书错误,排查了很久才发现是证书忘了更新。后来给证书做了统一到期提醒,到期前一个月开始倒计时检查,这个问题才彻底解决。
3.3 后端Web集群的配置细节
Web应用层我们做了两件事:无状态化和健康检查路径梳理。
无状态化这一步很多团队会拖到真正出问题之后才做。应用的会话状态如果存在本机内存里,那么负载均衡一旦把请求转发到另一台机器,用户的登录状态就会丢失。这不是WAF能解决的问题,必须靠应用改造。我们当时把Session统一迁移到了Redis,Web节点本身不再保存任何用户状态,这样任意一台Web节点下线,流量切换以后用户无感知。
健康检查是个容易被低估的坑点。最开始我们给健康检查配置了一个动态接口,它每次被调用都会查询一次数据库。结果WAF节点和接入层的健康检查请求叠加起来,每秒钟对后端的数据库产生了不小的压力。后来把健康检查路径换成了一个静态页面,或者一个轻量级的不落库接口,问题立刻缓解。
补充一个我后来形成的判断标准:健康检查的目的是确认服务进程还活着、网络链路还通着,而不是确认业务逻辑是否完全正常。把健康检查做得太重,反而会引入新的性能问题。
3.4 数据库层的冗余与容灾
数据库这层没有太多可以取巧的地方,就是老老实实做主从复制。主库负责写入,从库负责读取,同步方式选择了半同步复制。半同步和异步的区别在于,半同步要求某个从库确认收到binlog之后,主库的事务提交才算完成,这样能尽量降低主从切换时的数据丢失风险。
生产中遇到的教训是,应用连接串不能只写一个主库地址。如果主库挂了,应用还在连旧主库,那数据库层面的高可用就形同虚设。我们优化了连接配置,让应用优先连接可用的写节点,同时把读流量分摊到多台从库上。
备份策略也定了一个周期,每天凌晨全量备份,每两小时做一次binlog增量备份。备份文件和WAF日志一样,都在独立磁盘上,避免和数据盘争抢IO。
4. 安全策略配置实战:防护规则不是开了就不用管
4.1 基础防护与语义分析原理
雷池的检测引擎有两个特点值得展开说。传统WAF维护的是规则库,用一堆正则表达式去匹配请求里的攻击签名,遇到编码、注释、大小写混淆这些绕过手法时容易失手。雷池社区版的思路偏语义分析,把请求还原成结构化的语义,再判断这个结构本身是不是有攻击意图。生活化地讲,传统WAF像是逐字检查你说的每个词,语义分析则是看你整句话的逻辑结构有没有问题。后者在处理混淆攻击时天然更有优势。
但语义分析也不是万能的,它同样依赖一套特征模型。所以我的建议是,新上线的防护规则先不要直接开拦截模式。先把规则模块打开,模式设为观察或者告警运行一段时间,积累几天的日志后,再来调参数和开拦截。直接生产环境上拦截,很可能因为某个接口的请求特征被误判而误伤正常用户。
模块方面,SQL注入、XSS、路径穿越、命令注入这几个是必开的。登录接口、文件上传接口这些重点路径,可以单独加更严格的规则。日志分析阶段尤其要留意拦截日志里是不是出现了正常业务请求的URL,这是误报的典型信号。
4.2 CC防护与限速策略
CC攻击大概是最让人头疼的一类流量攻击,因为它不需要真实的漏洞,只要发起大量请求就能把后端资源耗尽。雷池的CC防护模块可以有效应对这类场景,但参数设置必须结合业务实际情况来调。
我们当时的业务QPS峰值大约在几百,给单IP设置了一个相对保守的阈值,比如每秒50次请求,超过后触发告警。如果连续触发多次,则封禁该IP一段时间。这个参数对普通用户完全无感,因为正常用户很难在单秒内发起50次请求。但如果业务本身有大批量数据获取类的API接口,就需要单独评估。
有几个容易被忽略的点:办公网出口通常会是同一个IP,比如整个公司几百人共用一个出口IP,这种IP一旦被误封,影响面会很大。需要把办公网出口IP加入白名单或者宽限名单。另外,有些爬虫脚本会分布到很多个IP上打请求,这种场景CC防护单看IP意义不大,还需要结合访问频率的分布特征来判断。
封禁时长的选择也要注意。封禁时间太短,攻击者等一会就可以继续打;太长,误伤的普通用户要等很久才能恢复。我们实测下来24小时的封禁时长比较平衡,配合人工审核接口,确认是攻击后再延长封禁。
4.3 IP黑白名单与自定义规则
IP白名单适合用在管理后台这类敏感路径上。我们给后台管理地址加了一个IP白名单,只有内部跳板机的IP能访问,其他来源一律403。这个配置看起来简单,但能挡掉大量自动扫描。
自定义规则则用来应对业务特有的攻击模式。举例来说,某次日志里出现了一个用户提交参数的畸形JSON请求,基础规则没有拦下来。我们就用自定义规则,匹配了该路径下所有请求头中的Content-Type字段,要求必须是application/json,不符合的请求直接拦截。这类精准规则比全局规则误报少得多,能用局部解决就不用全局。
配置自定义规则时,正则表达式的写法要特别小心。我建议在正式生效前,先用一段历史请求日志做回放测试,确认规则不会误伤正常请求。雷池控制台里可以查看规则的命中记录,这个功能多利用,能省很多排查时间。
5. 高可用故障演练与常见问题排查实录
5.1 故障演练:把WAF节点杀掉会发生什么
高可用架构不能光说不练,必须通过故障演练来验证切换路径。我们做的第一个实验很直接:在流量高峰期模拟WAF节点宕机,手动停掉节点A的Docker服务。
停止命令执行后,最先是Keepalived和Nginx的健康检查开始工作。Nginx在大概十几秒内就检测到了WAF节点A无响应,随即将这台节点摘除,所有新的请求自动转发到WAF节点B。用户侧的表现是一次登录态的保持可能出现几秒钟的延迟或者一个页面刷新缓慢,但基本没有感知到明显的服务中断。WAF节点B的访问日志显示,流量在摘除完成后已经完全过渡。
但这个过程中也暴露了一个问题:WAF节点A上记录的一部分实时日志,在节点崩溃前没有同步到节点B。由于我们选择了主备模式,节点切换后一段时间内,攻击日志和审计记录会有缺失。对高可用要求不算极端的业务来说这可以接受,但如果你想做完整的安全审计,要考虑把两个WAF节点的日志统一采集到单独的日志中心去。这也是社区版能力边界的一个体现。
演练结束后,我们的结论是:主备切换链路完全可用,但配置同步和日志统一采集还需要补齐。
5.2 配置漂移与双节点同步方案
因为雷池社区版没有提供开箱即用的多节点配置同步,两台WAF节点之间的配置一致性需要自己维护。这个问题用一句话说就是,如果只在节点A上添加了一条新规则,而节点B上忘了同步,一旦切换发生,节点B的防护策略就不是最新的,等于裸奔。
比较直观的方案是定期做数据目录的备份和恢复。每隔一段时间,把主用节点的配置和数据目录打包,复制到备用节点上恢复一遍。实际操作中,我们可以通过脚本完成这个过程。先导出配置,同步到备用节点,再重新加载控制台服务。整个过程控制在分钟级,可以放在业务低峰期执行。
这种做法的问题在于,规则变更频繁的业务下,同步间隔期间两边配置还是会漂移。更合理的做法是,把规则变更动作规范成一套操作流程:任何规则修改都先在主节点做完,然后立刻执行同步脚本,不隔夜、不拖延。把配置同步纳入上线流程,而不是事后想起来再补。
实际体验下来,这个手动同步的频率取决于规则变更的频次。如果业务稳定,一周同步一次完全够用;如果业务在快速迭代期,每天同步一次也不为过。
5.3 误报、告警与常见问题速查
误报处理是WAF上线后最常面对的问题。我们遇到过这样一个案例:某个业务接口的查询参数带了较长的特殊字符串,基础规则把这个请求识别成SQL注入尝试,直接拦截了。但排查日志后发现,这其实是业务本身生成的一串加密参数。处理方式是先确认请求合法,然后在站点规则里加一条该接口的参数白名单,绕过SQL注入检测模块。
这里要强调的是,加白名单要锁精确的路径和参数,不要为了省事对整个域名放开规则。整个域名放开规则等于把WAF的这道防线抽掉了,安全和便利的平衡点在于白名单的粒度。
下面把遇到过的问题整理成一张速查表,给后面运维的人参考。
| 故障现象 | 可能原因 | 处理方式 |
|---|---|---|
| 某个正常接口被拦截 | 规则误判 | 查看拦截日志,精确加白名单或调整规则 |
| 后端收到大量重复请求 | 健康检查使用了动态接口 | 改用静态路径或轻量健康检查接口 |
| WAF节点宕机后业务中断 | 接入层未配置多WAF节点或摘除过慢 | 接入层配置多个上游节点并调低fail_timeout |
| 证书到期后访问异常 | 证书未及时更新 | 建立证书到期清单和提醒机制 |
| 日志盘被写满 | 日志积累过多 | 日志目录独立磁盘,定期归档清理 |
| 后台管理路径被扫描 | 管理端暴露在公网 | 用IP白名单限死访问来源 |
再补充一个告警设置的建议:WAF的告警不要只会发邮件,因为邮件很容易被淹没在通知堆里。我们当时把告警接入了内部即时通讯群,设置成只推送风险等级较高的拦截事件和CC防护触发的封禁事件。普通级别的拦截日志不推送,需要时自己查控制台。这样告警的有效率会高很多,不会被长期拉到消息免打扰名单里。
写在最后的几个建议
如果让我把这次改造的核心体会浓缩成几条,大概是这样的。
第一,任何安全工具都必须放进完整的高可用链路里去看,单点的WAF比没有WAF更危险。第二,安全策略的配置要经历“观察、调优、拦截、再调优”的循环,不要开完拦截就不管了。第三,故障演练和配置同步虽然不产生直接收益,但在关键时刻能决定整个架构能不能真正扛住故障。
最后再分享一个我个人的操作习惯:每次改完规则或者调整完架构参数,都会把配置导出或者备份一份,然后在控制台的历史记录里做一条备注。这个习惯在几个月后排查一个诡异问题时救了命,当时就是靠一条备注快速定位到是某次配置变更引发的连带问题。细节不起眼,但架不住真的有用。
这套架构上线运行到现在,期间又经历过几次流量高峰和攻击尝试,整体表现稳定。如果你也正准备把雷池放进你的高可用架构里,希望这篇内容能给你一些可操作的参考。