Caddy集成OWASP Coraza WAF:开源Web应用防火墙实战配置指南
1. 项目概述与核心价值最近在折腾个人项目后端用的是Go写的顺手就选了Caddy作为反向代理服务器图的就是它配置简单、自动HTTPS。但项目上线前安全这块心里总不踏实尤其是防SQL注入、XSS这些常见的Web攻击。市面上商业WAFWeb应用防火墙功能强大但价格不菲对于个人项目或者小团队来说成本压力不小。于是我开始寻找开源的、能和Caddy无缝集成的WAF方案最终锁定了OWASP Coraza。OWASP Coraza是什么简单说它是一个用Go语言编写的、开源的Web应用防火墙引擎。它的最大优势是100%兼容ModSecurity的SecRules语法和OWASP核心规则集CRS。这意味着所有为ModSecurity编写的、经过实战检验的庞大规则库你都可以直接拿来用在Coraza上。而coraza-caddy这个模块就是专门为Caddy服务器打造的Coraza集成插件。它让你能在Caddy的配置文件中像启用一个普通模块一样轻松地为你的网站或API加上一层强大的WAF防护。我花了几天时间从零开始研究、配置、测试最终成功将Coraza WAF集成到了我的Caddy服务中并且全程免费。这篇文章就是我这趟“踩坑”之旅的完整记录。我会详细拆解从环境准备、编译Caddy、配置规则、到实战测试和问题排查的全过程。无论你是个人开发者想保护自己的博客还是运维同学在为公司的轻量级服务寻找安全方案这篇指南都能给你提供一条清晰的、可复现的路径。2. 环境准备与Caddy编译在开始配置WAF之前我们得先有一个“载体”——也就是集成了Coraza插件的Caddy服务器。官方提供了几种方式但最可靠、最灵活的还是自己用xcaddy工具编译。2.1 安装Go与xcaddyCoraza和这个插件都是用Go写的所以第一步是安装Go语言环境。去Go官网下载对应你操作系统的最新稳定版目前是1.21安装过程很简单一路下一步就行。安装完成后打开终端运行go version确认安装成功。接下来安装xcaddy这是Caddy官方推荐的定制化构建工具。go install github.com/caddyserver/xcaddy/cmd/xcaddylatest这条命令会把xcaddy安装到你的$GOPATH/bin目录下。确保这个目录在你的系统PATH环境变量里这样你就可以在任意位置使用xcaddy命令了。如果遇到“command not found”可能需要手动将$GOPATH/bin添加到你的shell配置文件如~/.bashrc或~/.zshrc中然后执行source命令生效。2.2 编译带Coraza插件的Caddy有了xcaddy编译就一行命令的事。但这里有几个细节需要注意直接关系到编译的成功与否。基础编译命令xcaddy build --with github.com/corazawaf/coraza-caddy/v2这条命令会从Caddy官方仓库拉取最新源码并集成coraza-caddy/v2插件最终在当前目录生成一个名为caddy的可执行文件。编译过程中的常见问题与解决网络问题导致依赖下载失败Go模块依赖需要从proxy.golang.org等镜像站下载。如果你在国内可能会遇到超时。解决方案是设置Go模块代理go env -w GOPROXYhttps://goproxy.cn,direct设置后再执行编译命令。版本兼容性问题coraza-caddy插件有其兼容的Caddy版本范围。如果xcaddy默认拉取的最新版Caddy与插件不兼容编译会报错。这时可以指定Caddy版本xcaddy build v2.7.6 --with github.com/corazawaf/coraza-caddy/v2具体的兼容版本需要去coraza-caddy的GitHub仓库的go.mod文件里查看。在我撰写本文时v2.5.0版本的插件与Caddyv2.7.x兼容良好。编译平台指定如果你是在macOS或Windows上编译但最终要在Linux服务器上运行需要指定目标操作系统和架构xcaddy build --with github.com/corazawaf/coraza-caddy/v2 --output ./caddy-linux --gooslinux --goarchamd64这样生成的caddy-linux文件就可以直接上传到Linux服务器运行了。编译成功后你可以通过./caddy version来验证插件是否成功集成。输出信息中应该能看到包含coraza_waf的模块列表。实操心得建议在本地开发环境如Linux虚拟机或WSL2中完成编译这样环境更接近生产服务器能提前发现一些依赖库的问题。编译好的二进制文件可以直接扔到服务器上运行无需在服务器上安装完整的Go编译环境非常干净。3. Coraza WAF核心配置详解编译好Caddy后核心工作就转向了Caddyfile的配置。Coraza WAF的所有能力都通过coraza_waf这个配置块来开启和定义。3.1 配置块结构与强制指令首先有一个至关重要的前提为了让Coraza插件能拦截和处理所有请求必须在全局配置或站点配置的最开始使用order指令将其优先级设为最高。{ # 这个指令必须放在最前面 order coraza_waf first } yourdomain.com { # 你的其他配置... }如果忘记这一步Coraza可能会在其他模块如file_server、reverse_proxy处理完请求后才生效导致拦截失败。3.2directives字段规则加载的基石coraza_waf配置块的核心是directives字段它接受一个多行字符串里面填写的就是ModSecurity格式的规则。基本语法示例http://localhost:8080 { order coraza_waf first coraza_waf { directives # 启用规则引擎 SecRuleEngine On # 定义请求体处理大小 SecRequestBodyLimit 13107200 # 一条简单的测试规则访问 /test 路径则拒绝 SecRule REQUEST_URI “streq /test” “id:100,phase:2,deny,status:403,log” } respond “Hello, World!” }directives里的内容会被逐条解析。SecRuleEngine On是引擎开关必须开启。SecRequestBodyLimit设置请求体大小限制超过的请求体会被拒绝或部分处理这个值需要根据你的应用实际情况调整。规则语法速览一条典型的SecRule包含三个部分变量Variable如REQUEST_URI请求URI、ARGSGET/POST参数、REQUEST_HEADERS。操作符Operator如streq字符串相等、contains包含、rx正则表达式匹配。动作Action如deny拒绝、pass通过、log记录日志以及id规则ID、phase处理阶段、status返回状态码等参数。3.3load_owasp_crs字段加载核武器手动写规则既累又容易遗漏。Coraza最大的优势就是能直接使用OWASP CRS。通过设置load_owasp_crs字段为true插件会自动加载内置的CRS规则变量和配置文件路径映射为后续Include指令铺平道路。加载CRS的标准配置模式coraza_waf { load_owasp_crs directives SecRuleEngine On # 1. 包含CRS的推荐配置文件设置一些基础变量和动作 Include coraza.conf-recommended # 2. 包含CRS设置文件这里是示例生产环境应使用自定义的crs-setup.conf Include crs-setup.conf.example # 3. 包含所有CRS规则文件 Include owasp_crs/*.conf }这里的coraza.conf-recommended和crs-setup.conf.example是插件内置的配置别名指向特定的文件。owasp_crs/*.conf则会加载所有CRS规则。注意事项直接使用crs-setup.conf.example是不安全的因为它包含的是示例配置。生产环境中你必须将其复制出来根据官方CRS文档进行调优特别是要设置tx.crs_setup_version和关闭可能产生大量误报的规则如REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example中的规则需要重命名并自定义。我建议的流程是先使用示例配置让WAF跑起来然后根据日志逐步调整最后替换为你的自定义crs-setup.conf文件。3.4 规则文件的组织与管理当规则越来越多时全部写在Caddyfile里会难以维护。最佳实践是使用Include指令引入外部规则文件。1. 包含单个文件directives Include /etc/caddy/coraza/my_custom_rules.conf 2. 包含目录下所有.conf文件directives Include /etc/caddy/coraza/rules/*.conf 3. 使用相对路径相对于Caddy运行目录directives Include ./waf_configs/crs-setup.conf 我个人的习惯是在Caddy配置目录下建立一个coraza子目录里面按功能存放规则文件/etc/caddy/ ├── Caddyfile └── coraza/ ├── crs-setup.conf # 自定义的CRS主配置 ├── exclusion-rules.conf # 针对自己应用的白名单规则 ├── custom-rules.conf # 自定义的额外规则 └── paranoia-level-2/ # 如果需要可以按CRS的防护等级存放规则然后在Caddyfile里这样引入directives SecRuleEngine On Include /etc/caddy/coraza/crs-setup.conf Include /etc/caddy/coraza/exclusion-rules.conf Include /etc/caddy/coraza/custom-rules.conf 这种结构清晰便于版本控制如用Git管理也方便在不同环境开发、测试、生产间同步规则。4. 实战集成OWASP CRS并处理拦截响应理论说再多不如动手试。我们来完成一个最经典的场景为反向代理的后端服务启用完整的OWASP CRS防护并自定义拦截页面。4.1 准备CRS配置文件首先我们需要获取OWASP CRS规则集。虽然coraza-caddy插件内置了CRS但为了灵活调优我们最好自己下载一份。# 创建一个工作目录 mkdir -p ~/waf-config cd ~/waf-config # 克隆OWASP CRS仓库建议使用稳定版本分支如v3.3/master git clone --branch v3.3/master https://github.com/coreruleset/coreruleset.git cd coreruleset关键文件说明crs-setup.conf.example: 主配置文件示例包含所有可调参数。rules/: 目录下是所有具体的防护规则按攻击类型分类如SQL注入、XSS等。rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example: 用于在CRS规则前设置白名单的示例。创建生产环境配置# 复制示例配置文件并进行修改 cp crs-setup.conf.example crs-setup.conf cp rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf编辑crs-setup.conf找到并修改以下关键配置仅列举部分# 设置CRS版本用于规则更新追踪 SecAction \ “id:900990,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.crs_setup_version343” # 设置防护等级1-4越高越严格误报也可能越多 SecAction \ “id:900000,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.paranoia_level1” # 设置异常评分阈值超过则拦截 SecAction \ “id:900000,\ phase:1,\ nolog,\ pass,\ t:none,\ setvar:tx.inbound_anomaly_score_threshold5,\ setvar:tx.outbound_anomaly_score_threshold4”编辑rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf这里可以为你自己的应用添加白名单。例如如果你的登录接口/api/login的username参数经常包含特殊字符被误报可以添加SecRule REQUEST_URI “beginsWith /api/login” \ “id:1000,\ phase:1,\ pass,\ nolog,\ ctl:ruleRemoveTargetById942100;ARGS:username”这条规则的意思是对于以/api/login开头的请求移除ID为942100的规则对username参数的检查。4.2 编写完整的Caddyfile假设我们有一个运行在http://localhost:3000的Go Web应用我们要用Caddy做反向代理并启用WAF。# Caddyfile { # 全局设置必须将coraza_waf放在处理链首位 order coraza_waf first # 全局日志方便调试 log { output file /var/log/caddy/access.log format json } } myapp.example.com { # 启用Coraza WAF coraza_waf { load_owasp_crs directives SecRuleEngine On SecRequestBodyLimit 134217728 # 128MB SecRequestBodyAccess On # 包含我们准备好的配置文件 Include /path/to/your/coreruleset/crs-setup.conf Include /path/to/your/coreruleset/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf Include /path/to/your/coreruleset/rules/*.conf } # 使用Caddy的handle_errors指令自定义拦截响应 handle_errors { waf_blocked { err.status_code } 403 handle waf_blocked { header Content-Type “text/html; charsetutf-8” header X-WAF-Blocked “true” # 你可以返回一个简单的HTML页面 respond HTML !DOCTYPE html html headtitle请求被拦截/titlestylebody{font-family: sans-serif; padding: 2rem;}/style/head body h1请求被安全策略拦截/h1 p您的请求触发了Web应用防火墙规则。如果这是误报请联系管理员。/p p请求ID: {http.request.header.X-Request-ID}/p /body /html HTML 403 } } # 反向代理到后端应用 reverse_proxy localhost:3000 { header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up X-Forwarded-Proto {scheme} } # 可选的日志记录记录被WAF拦截的请求详情 log { output file /var/log/caddy/waf_blocked.log format json { time_format “rfc3339_nano” } # 可以添加条件只记录被WAF拦截的请求这需要结合Coraza的审计日志功能稍复杂 } }这个配置做了几件事全局设置了order。在站点配置中启用Coraza并加载了我们调优过的CRS规则。使用handle_errors捕获403状态码Coraza默认拦截状态码返回一个友好的自定义HTML页面而不是生硬的“403 Forbidden”。将请求代理到实际的后端应用。4.3 启动与测试启动Caddy# 假设编译好的caddy二进制文件和Caddyfile在当前目录 ./caddy run --config ./Caddyfile --adapter caddyfile或者以后台服务方式运行使用systemdsudo ./caddy start --config /etc/caddy/Caddyfile发送测试请求正常请求curl https://myapp.example.com/应该能正常返回后端应用的内容。触发SQL注入规则curl “https://myapp.example.com/search?q1 OR 11”。由于CRS规则集包含SQL注入检测这个请求有很大概率会被拦截返回我们自定义的403页面并在Caddy日志/Coraza审计日志中留下记录。触发XSS规则curl -X POST https://myapp.example.com/comment -d “contentscriptalert(1)/script”。查看日志检查Caddy的访问日志和错误日志确认拦截请求的日志格式。Coraza的拦截日志通常会通过Caddy的标准错误输出或你配置的日志文件记录里面包含了触发的规则ID、匹配的变量等信息是后续调优的关键依据。5. 高级配置与性能调优基础功能跑通后我们需要关注如何让WAF更高效、更准确地工作避免对正常业务造成影响。5.1 规则引擎模式与阶段Phase理解SecRuleEngine有三种模式On完全启用检测并执行动作如拒绝。DetectionOnly只检测并记录日志不执行拒绝动作。这是上线初期的推荐模式用于观察规则是否产生大量误报。Off完全关闭。规则执行的phase阶段决定了规则在请求/响应处理流程中的执行时机phase:1(REQUEST_HEADERS)请求头到达后。phase:2(REQUEST_BODY)请求体解析后。大部分输入验证规则在此阶段。phase:3(RESPONSE_HEADERS)响应头发送前。phase:4(RESPONSE_BODY)响应体发送前。用于检测响应中是否泄露敏感信息。phase:5(LOGGING)日志记录阶段。合理设置规则的phase能提升效率。例如一个基于URL路径的白名单规则在phase:1就可以判断通过无需进入更耗时的phase:2进行请求体解析。5.2 请求体处理配置优化对于上传文件或处理大量POST数据的应用请求体配置至关重要。SecRequestBodyLimit 134217728 # 最大请求体大小128MB SecRequestBodyAccess On # 是否处理请求体On为处理 SecRequestBodyInMemoryLimit 131072 # 请求体在内存中处理的上限128KB超过会写入磁盘 SecRequestBodyNoFilesLimit 1048576 # 不包含文件上传的请求体大小限制1MB SecRequestBodyLimitAction Reject # 超过限制时的动作Reject为拒绝 SecRule REQUEST_HEADERS:Content-Type “rx ^multipart/form-data” \ “id:200000,phase:1,t:none,nolog,pass,ctl:requestBodyProcessorURLENCODED|MULTIPART”调优建议SecRequestBodyLimit应根据业务需要设置不宜过大防止内存耗尽攻击。SecRequestBodyInMemoryLimit设置一个合理的值让小的请求体在内存中快速处理大的请求体缓存到磁盘平衡性能和内存使用。明确设置requestBodyProcessor确保能正确解析multipart/form-data格式文件上传。5.3 审计日志与调试当规则误报或需要分析攻击时详细的审计日志是救命稻草。Coraza支持配置审计日志引擎。# 在directives中添加 SecAuditEngine RelevantOnly # 只记录触发相关规则的请求 SecAuditLogParts ABCDEFGHIJKZ # 记录审计日志的哪些部分Z是完整的请求/响应体慎用数据量大 SecAuditLogType Serial # 日志类型Serial是顺序写入文件 SecAuditLog /var/log/coraza/audit.log # 审计日志路径 SecAuditLogStorageDir /var/log/coraza/audit # 如果使用Concurrent类型日志存储目录 SecDebugLog /var/log/coraza/debug.log # 调试日志非常详细仅调试时开启 SecDebugLogLevel 3 # 调试日志级别0-9数字越大越详细生产环境建议SecAuditEngine RelevantOnly是平衡点。SecAuditLogParts通常用ABCEFHJK就够了包含了请求头、响应头、触发的规则等关键信息避免记录完整的请求体Z以保护用户隐私和节省空间。SecDebugLog和SecDebugLogLevel仅在排查疑难杂症时临时开启因为它会产生海量日志严重影响性能。5.4 性能调优要点规则数量与复杂度CRS全规则集包含数百条规则。在paranoia_level1时大部分高危规则已启用。如果性能压力大可以考虑只启用特定类型的规则如只启用SQL注入和XSS规则或者使用ctl:ruleRemoveById禁用已知对自身应用无害的规则。白名单排除规则这是减少误报和提升性能最有效的手段。在REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf中为你的API路径和参数添加精确的白名单。例如为接受富文本的编辑器接口禁用XSS检测规则。正则表达式优化CRS规则中大量使用正则表达式。过于复杂的正则式是性能杀手。虽然我们无法修改CRS内置规则但可以通过更新到最新版CRS来获取性能优化。同时确保你的排除规则足够精确避免让大量请求去匹配复杂的正则。资源限制合理设置SecRequestBodyLimit和SecPcreMatchLimit正则匹配限制次数防止DoS攻击。使用DetectionOnly模式观察上线前先在DetectionOnly模式下运行一段时间如一周分析审计日志确认没有影响业务的误报后再切换到On模式。6. 常见问题排查与解决方案实录在实际部署中你肯定会遇到各种问题。下面是我踩过的一些坑和解决方法。6.1 编译与启动问题问题1编译失败提示go: module github.com/corazawaf/coraza-caddy/v2latest found but does not contain package原因xcaddy在解析插件路径时可能使用了错误的模块版本或路径。解决尝试指定完整的模块路径和版本。去GitHub仓库查看最新的release tag例如xcaddy build --with github.com/corazawaf/coraza-caddy/v2v2.5.0问题2Caddy启动失败报错Error during parsing: parsing directive coraza_waf: module not installed原因运行的Caddy二进制文件没有编译进coraza_waf模块。解决确认你运行的caddy命令是否是你刚刚用xcaddy编译出来的那个。使用./caddy list-modules | grep coraza检查模块列表。6.2 规则配置与拦截问题问题3WAF似乎没有生效攻击请求没有被拦截排查步骤检查order指令这是最常见的原因。确保在全局或对应站点块的最顶部有order coraza_waf first。检查SecRuleEngine确认设置为On或DetectionOnly。检查规则加载确认Include路径正确文件可读。可以在directives最前面加一条简单的测试规则如SecRule ARGS “test” “id:999,deny,log,msg:test rule”然后访问/?argtest看是否被拦截。查看日志启动Caddy时加上--adapter caddyfile --config ./Caddyfile --watch在前台运行并观察标准错误输出。Coraza的解析错误和拦截日志通常会打印在这里。问题4正常业务请求被误拦截误报排查步骤获取拦截详情当请求被拦截时查看Caddy的日志或Coraza的审计日志。关键信息是规则IDRule ID例如[id “942100”]。定位规则根据规则ID去OWASP CRS规则文件在rules/目录下中搜索。例如ID 942100通常在REQUEST-942-APPLICATION-ATTACK-SQLI.conf文件中。查看该规则的具体描述和匹配模式。分析原因对比你的请求参数和规则的正则表达式。常见的误报原因包括参数中包含SQL关键字如SELECT、WHERE、特殊字符编码、或某些编程语言生成的特定字符串。添加白名单在REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf中添加排除规则。例如如果你的/api/search接口的q参数允许用户输入OR这个词而被942100规则拦截可以添加SecRule REQUEST_URI “beginsWith /api/search” \ “id:1001,\ phase:1,\ pass,\ nolog,\ ctl:ruleRemoveTargetById942100;ARGS:q”ctl:ruleRemoveTargetById表示移除指定ID规则对特定目标的检查。问题5文件上传被拦截原因CRS规则200003可能会拦截某些文件上传的请求头或内容。解决确认你的SecRequestBodyAccess和SecRequestBodyLimit设置足够大。为文件上传接口添加白名单禁用对REQUEST_BODY的某些检查。例如对于/api/upload接口SecRule REQUEST_URI “streq /api/upload” \ “id:1002,\ phase:1,\ pass,\ nolog,\ ctl:ruleRemoveTargetById200003;REQUEST_BODY”注意这降低了安全性请确保你的上传接口本身有严格的文件类型和内容检查。6.3 性能与日志问题问题6启用WAF后服务器响应明显变慢排查与解决开启DetectionOnly模式先确认是WAF导致的延迟。分析审计日志看是否集中在某些特定规则或请求上。使用工具如tail -f和grep分析。调整规则集降低paranoia_level从2或3降为1。在crs-setup.conf中可以按需禁用整个规则文件例如如果你的应用是纯API且无HTML输出可以尝试禁用响应体检查相关的规则文件如RESPONSE-950-*。优化排除规则确保白名单足够精确避免不必要的规则匹配。检查正则匹配限制SecPcreMatchLimit设置过低可能导致复杂请求提前终止匹配但设置过高可能消耗资源。默认值通常够用。问题7审计日志文件增长过快解决调整SecAuditLogParts移除不需要的部分如Z请求/响应体。将SecAuditEngine设置为RelevantOnly。使用日志轮转工具如logrotate。创建一个/etc/logrotate.d/coraza文件/var/log/coraza/*.log { daily rotate 7 compress delaycompress missingok notifempty create 644 caddy caddy postrotate # 如果Caddy不是以服务运行可能需要发送信号重新打开日志文件 # kill -USR1 cat /var/run/caddy.pid 2/dev/null 2/dev/null || true endscript }部署OWASP Coraza WAF for Caddy是一个从“可用”到“好用”的持续调优过程。初期以DetectionOnly模式运行结合详细的审计日志耐心地梳理和添加排除规则是减少误报、让WAF平稳融入生产环境的关键。这套完全免费的开源方案在提供强大安全防护的同时也给予了我们极大的灵活性和控制力。当你看到日志中那些被成功拦截的SQL注入和XSS攻击尝试时会觉得这一切的折腾都是值得的。