1. 从一堆 Nginx 日志里捞出攻击痕迹,到底难在哪
如果你手上有一台对外提供服务的机器,Nginx 的access.log每天都在膨胀。一天几十万行、几百 MB 是常态,遇到爬虫或者扫描器扫站,日志量还能翻几倍。真正让人头疼的不是日志大,而是日志分析这件事本身没法靠人眼完成:你想知道昨天有没有人尝试 SQL 注入、有没有 IP 在短时间内疯狂撞你的登录接口、有没有路径遍历的探测请求,靠grep一条条翻,翻到天亮也翻不完。
我自己的场景很典型:一台跑着几个 Web 服务的机器,Nginx 日志按天切割,偶尔被扫就收到一堆 404 和 500。以前的做法是写几个零散的 shell 脚本,awk切字段、sort | uniq -c数 IP,能看个大概,但有几个硬伤。第一,字段解析全靠手写正则,Nginx 日志格式稍微改一下(比如加了$request_time)脚本就崩。第二,规则没法复用,今天想查 SQL 注入、明天想查目录穿越,每次都得重写。第三,也是最要命的,异常攻击的判定没有统一标准,全靠人拍脑袋,今天觉得这个 IP 可疑,明天换个同事看又觉得正常。
所以我想搭一套能自动跑起来的流程:日志进来先清洗成结构化数据,然后按规则命中异常攻击,最后定时吐出一份安全报表。这套流程里,OpenClaw 负责日志的采集、解析、规则匹配和报表生成,而它背后要调用模型来做一些语义判断(比如把可疑的 User-Agent 归类、把攻击 payload 翻译成人话写进报表),这部分我统一走 TaoToken 的 API 通道。这样做的直接好处是:OpenClaw 的配置里只需要维护一个 Base URL 和一个 Key,模型切换、额度管理都在一个地方,不用在每台机器上散落一堆不同厂商的 Key。
这篇文章就是把这套东西从零跑通的完整记录。你会看到config.toml和settings.json长什么样、怎么跑一次日志清洗任务、怎么确认异常攻击真的被命中、以及最后怎么导出安全报表。适合谁看:手上有一台 Linux 机器、会基本的命令行、想给自己的服务加一层自动化日志监控的运维或后端同学。不需要你懂机器学习,规则引擎那部分够用了。
2. 前置准备:TaoToken 统一 Key 与 OpenClaw 环境
在写配置之前,先把两件事理清楚:OpenClaw 装在哪、TaoToken 的 Key 怎么拿。这两步做完,后面的配置才有地方填。
先说 OpenClaw 的定位。它不是一个开箱即用的 SaaS,而是一个跑在你机器上的日志处理服务。核心工作流是「读日志文件 → 按解析器切成字段 → 过规则引擎打标签 → 写库/出报表」。它本身不绑定任何模型厂商,模型调用是通过一个可配置的 HTTP 客户端发出去的。这就意味着,只要把客户端的 Base URL 指向 TaoToken 的 API 地址,OpenClaw 里所有需要模型的地方(比如攻击描述生成、可疑请求归类)都会走同一条通道。
TaoToken 这边,你需要的是一个 API Key。拿到 Key 的路径是登录后在控制台创建,具体入口在 API Keys 页面。创建完你会得到一串以sk-开头的字符串,这串东西就是后面配置里要填的api_key。注意,Key 只在创建时完整显示一次,记得当场复制存好,别等关了页面再回来找。
环境方面,我用的是一台 Ubuntu 22.04 的机器,Python 3.10。OpenClaw 对 Python 版本要求不严,3.8 以上都能跑。依赖装在一个虚拟环境里,避免污染系统 Python。下面是完整的环境准备命令,你可以直接照着敲:
# 1. 建目录,放 OpenClaw 和它的数据 mkdir -p /opt/openclaw && cd /opt/openclaw # 2. 创建虚拟环境 python3 -m venv venv source venv/bin/activate # 3. 安装核心依赖 pip install --upgrade pip pip install openclaw-core requests pyyaml jinja2 # 4. 验证装好了 openclaw --versionopenclaw --version能打印出版本号,说明主程序就位了。接下来是目录结构,OpenClaw 默认会读当前工作目录下的config.toml和settings.json,所以建议把配置和数据分开放:
mkdir -p /opt/openclaw/{config,data,reports,logs}config/放config.toml和settings.jsondata/放清洗后的中间数据reports/放生成的安全报表logs/放 OpenClaw 自己的运行日志
这里有个容易踩的坑:OpenClaw 读日志文件需要权限。如果你的 Nginx 日志在/var/log/nginx/access.log,而 OpenClaw 是用普通用户跑的,会读不到。两个办法,要么把 OpenClaw 跑在能读该文件的用户下,要么给日志文件加读权限。我图省事,直接把 OpenClaw 跑在了一个属于adm组的用户下,因为 Ubuntu 上 Nginx 日志默认就是adm组可读。
TaoToken 的 Key 拿到后,先别急着写进配置文件,用一条 curl 验证一下 Key 是活的,省得后面配置全写完才发现 Key 填错了:
curl -s https://taotoken.net/api/v1/models \ -H "Authorization: Bearer sk-你的Key" | head -c 300能返回一个模型列表的 JSON,就说明 Key 没问题、网络也通。这一步花三十秒,能省掉后面半小时的排查。如果这里就报 401,那问题在 Key 本身,跟 OpenClaw 无关,先去控制台确认 Key 有没有被禁用或者复制时多了空格。
3. 可复制配置:config.toml 与 settings.json 骨架
这一节是全文的核心,配置写对了,后面就是跑命令的事。OpenClaw 的配置分两块:config.toml管日志源、解析器、规则和输出,settings.json管模型调用的通道参数。我先把两份完整骨架贴出来,再逐段解释关键字段。
先看config.toml:
# /opt/openclaw/config/config.toml [input.nginx_access] type = "file_tail" enabled = true path = "/var/log/nginx/access.log" parser = "nginx_combined" tags = ["web", "prod"] [parser.nginx_combined] format = "regex" pattern = '^(?P<remote_addr>\S+) - - \[(?P<time_local>[^\]]+)\] "(?P<request>[^"]*)" (?P<status>\d+) (?P<body_bytes>\d+) "(?P<referer>[^"]*)" "(?P<user_agent>[^"]*)"' time_format = "%d/%b/%Y:%H:%M:%S %z" fields = ["remote_addr", "time_local", "request", "status", "body_bytes", "referer", "user_agent"] [rule.sql_injection] enabled = true severity = "high" match_field = "request" match_regex = '(?i)(union\s+select|select\s+.*\s+from|insert\s+into|drop\s+table|or\s+1=1)' tag = "sql_injection" [rule.path_traversal] enabled = true severity = "medium" match_field = "request" match_regex = '(\.\./|\.\.\\|%2e%2e%2f)' tag = "path_traversal" [rule.brute_force] enabled = true severity = "high" type = "threshold" group_by = "remote_addr" window_seconds = 60 threshold = 30 match_field = "status" match_value = "401" tag = "brute_force" [output.elasticsearch] enabled = false hosts = ["http://localhost:9200"] index_prefix = "openclaw_" [output.report] enabled = true format = "markdown" output_dir = "/opt/openclaw/reports" schedule = "daily"再看settings.json,这是模型通道的配置:
{ "model_provider": { "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model_id": "claude-3-5-sonnet", "timeout": 60, "max_retries": 2 }, "enrichment": { "enable_ai_summary": true, "summary_language": "zh", "max_events_per_summary": 50 }, "report": { "title": "每日安全态势报表", "include_raw_samples": true, "top_n_attackers": 10 } }这两份配置里,有几个字段必须对齐,否则跑起来会出问题。
第一,base_url和api_key。base_url填https://taotoken.net/api,注意不要带末尾斜杠,也不要带/v1,OpenClaw 的客户端会自己拼路径。api_key就是你在控制台创建的那串。model_id填你要用的模型标识,这个标识要跟 TaoToken 支持的模型名一致,填错了会在调用时报模型不存在的错。
第二,parser里的pattern必须跟你实际的 Nginx 日志格式匹配。上面这份是标准的 combined 格式。如果你在 Nginx 里加了$request_time或者改了字段顺序,正则也要跟着改。验证正则对不对有个笨办法但很有效:拿一行真实日志,用 Python 的re.match跑一下,看能不能把字段都抓出来。
第三,rule.brute_force用的是阈值规则,不是正则。它的逻辑是:在 60 秒窗口内,同一个remote_addr出现 30 次以上状态码为 401 的请求,就判定为暴力破解。这个阈值你要根据自己的业务调,登录接口 QPS 高的站点,30 可能太敏感,会误报。
第四,output.report的schedule = "daily"表示每天生成一次报表。OpenClaw 的调度是基于它自己进程内的定时器,所以进程得一直挂着。如果你不想让它常驻,也可以手动触发报表生成,后面会讲。
配置写完后,先做一次语法检查,OpenClaw 提供了--check参数:
cd /opt/openclaw openclaw --config config/config.toml --settings config/settings.json --check如果输出config OK,说明两份文件都能被正确解析。如果报 TOML 解析错误,多半是引号或者转义的问题,正则里的反斜杠在 TOML 单引号字符串里不用转义,但在双引号里要写成\\,这点要注意。
4. 跑通验证:清洗任务、攻击命中与报表导出
配置就位后,分三步验证:先跑一次日志清洗,确认字段切得对;再确认异常攻击规则能命中;最后导出安全报表。
4.1 跑一次日志清洗任务
OpenClaw 提供了run子命令,可以只跑一次处理流程然后退出,适合调试:
cd /opt/openclaw source venv/bin/activate openclaw run \ --config config/config.toml \ --settings config/settings.json \ --once \ --input nginx_access \ --output data/cleaned.jsonl这条命令的意思是:读nginx_access这个输入源,跑一遍解析和规则匹配,把结果以 JSONL 格式写到data/cleaned.jsonl,然后退出。--once是关键,不加它会一直 tail 文件不退出。
跑完后看输出文件的前几行:
head -n 3 data/cleaned.jsonl | python3 -m json.tool你应该能看到类似这样的结构:
{ "remote_addr": "203.0.113.45", "time_local": "12/Oct/2024:08:31:22 +0800", "request": "GET /api/user?id=1%20union%20select%20password%20from%20users HTTP/1.1", "status": "200", "body_bytes": "512", "referer": "-", "user_agent": "sqlmap/1.7", "tags": ["sql_injection"], "severity": "high" }看到tags里有sql_injection,说明解析和规则匹配都通了。如果tags是空的,但字段都切出来了,那问题在规则的正则上,回去检查match_regex有没有写错。如果连字段都没切出来,那是pattern的问题,拿真实日志行去对。
这里有个细节值得说:request字段里包含了 URL 和查询串,SQL 注入的 payload 通常藏在查询串里,所以规则直接对整个request做正则匹配是合理的。但要注意,有些正常请求的 URL 里也可能出现select这种词(比如某个商品叫「select 系列」),会误报。降低误报的办法是把正则写得更严格,比如要求union和select同时出现,或者加上%20这种编码特征。
4.2 确认异常攻击命中记录
清洗跑通后,单独验证一下规则命中情况。OpenClaw 有个stats子命令,能统计某个结果文件里各标签的数量:
openclaw stats --input data/cleaned.jsonl --group-by tags输出会类似:
sql_injection 12 path_traversal 5 brute_force 3 (none) 9820这说明在这一次处理的一万条日志里,有 12 条命中了 SQL 注入、5 条路径遍历、3 条暴力破解。数字对不对,你可以用 grep 交叉验证一下:
grep -c -iE 'union.*select|or 1=1' /var/log/nginx/access.log如果 grep 出来的数字跟stats里的sql_injection接近,说明规则没漏。注意 grep 是大小写敏感的,加-i忽略大小写,跟规则里的(?i)对应。
暴力破解那条规则因为是阈值型的,单看一条日志看不出来,得看聚合结果。OpenClaw 在命中阈值规则时,会在结果里加一个group_key字段,值是触发阈值的那个 IP。你可以这样筛出来:
python3 -c " import json with open('data/cleaned.jsonl') as f: for line in f: e = json.loads(line) if 'brute_force' in e.get('tags', []): print(e['remote_addr'], e['time_local'], e['status']) "能看到同一个 IP 在短时间内出现多次 401,就说明阈值规则生效了。
4.3 导出安全报表
报表生成是 OpenClaw 里调用模型的地方。它会先把命中的攻击事件聚合,然后把这些事件交给模型,让模型生成一段人类可读的态势描述,最后套进 Markdown 模板输出。触发报表生成:
openclaw report \ --config config/config.toml \ --settings config/settings.json \ --input data/cleaned.jsonl \ --output reports/security_report_$(date +%Y%m%d).md跑完后打开报表文件:
cat reports/security_report_20241012.md你会看到类似这样的内容:
# 每日安全态势报表 ## 概览 - 处理日志总量:9840 条 - 命中攻击事件:20 条 - 高危事件:15 条 - 涉及独立攻击 IP:7 个 ## TOP 攻击来源 | IP | 命中次数 | 主要攻击类型 | |----|---------|-------------| | 203.0.113.45 | 8 | sql_injection | | 198.51.100.22 | 5 | path_traversal | | 192.0.2.77 | 3 | brute_force | ## 攻击详情摘要 203.0.113.45 在 08:31 至 08:35 之间发起了 8 次 SQL 注入尝试, 目标集中在 /api/user 接口,payload 中包含 union select 特征。 建议将该 IP 加入临时封禁列表,并检查 /api/user 的参数过滤逻辑。这段摘要就是模型生成的。它把原始的攻击 payload 翻译成了运维能直接看懂的话,还给了处置建议。如果你不想每次都调模型,可以在settings.json里把enable_ai_summary设为false,报表就只输出统计表格,不生成文字摘要。
报表里的 TOP 攻击来源表格是 OpenClaw 自己聚合的,不依赖模型,所以即使模型调用失败,表格部分依然完整。这点设计得比较稳,模型挂了不至于整份报表都出不来。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
配置和验证跑下来,最容易在这几个地方卡住。我把真实遇到过的报错和排查路径列出来,你对着改。
报错一:401 Unauthorized
openclaw.report - ERROR - model call failed: 401 Unauthorized这个最直接,Key 不对。三种可能:Key 复制时带了空格、Key 被禁用、base_url填错了导致请求发到了别的地方。排查顺序:先用第 2 节那条 curl 命令单独测 Key,如果 curl 也 401,那就是 Key 本身的问题,去控制台重新创建一个。如果 curl 通了但 OpenClaw 报 401,检查settings.json里api_key的值有没有被引号包错,或者有没有多余的空格。JSON 里字符串值必须用双引号,别写成单引号。
报错二:local proxy failed
requests.exceptions.ProxyError: HTTPSConnectionPool(host='taotoken.net', port=443): Max retries exceeded ... local proxy failed这个报错说明你的机器上配了 HTTP 代理环境变量,而 OpenClaw 的请求走了这个代理但代理不通。检查环境变量:
env | grep -i proxy如果有http_proxy或https_proxy,而你又不需要代理,直接 unset 掉再跑:
unset http_proxy https_proxy all_proxy或者在settings.json里显式声明不走代理,OpenClaw 的客户端支持no_proxy配置。这个报错跟 TaoToken 本身无关,是本地网络环境的问题。
报错三:reading choices
KeyError: 'choices'这个报错发生在解析模型返回结果的时候。正常情况下,模型 API 返回的 JSON 里有个choices数组,OpenClaw 从里面取第一个元素的内容。如果返回的 JSON 里没有choices,说明请求虽然发出去了,但返回的不是预期的结构。常见原因是model_id填错了,服务端返回了一个错误信息而不是正常的补全结果。排查办法:把settings.json里的model_id换成确认可用的模型名,或者手动发一条请求看返回结构:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{"model":"claude-3-5-sonnet","messages":[{"role":"user","content":"hi"}]}' | python3 -m json.tool如果这条 curl 返回的 JSON 里有choices,那问题就在 OpenClaw 的配置上;如果没有,那是模型名或请求体的问题。
报错四:OAuth 相关错误
OAuth token expired or invalid如果你用的是某些需要 OAuth 流程的模型通道,可能会遇到这个。TaoToken 的 API Key 方式是 Bearer Token,不涉及 OAuth 刷新流程,所以正常情况下不会出现这个报错。如果你在 OpenClaw 的日志里看到 OAuth 字样,多半是settings.json里混入了别的认证配置,或者 OpenClaw 的某个插件在尝试走 OAuth。检查settings.json里有没有多余的oauth_*字段,删掉即可。认证方式只保留api_key一种。
报错五:日志文件读不到
PermissionError: [Errno 13] Permission denied: '/var/log/nginx/access.log'这个不是模型的问题,是文件权限。前面提过,把 OpenClaw 跑在有读权限的用户下,或者给日志文件加读权限:
sudo usermod -aG adm $(whoami) # 重新登录使组权限生效排查完这些,再跑一次openclaw run --once,基本就能通了。如果还有别的报错,先看 OpenClaw 自己的运行日志logs/openclaw.log,里面会有更详细的堆栈信息。
6. 把这条链路固定下来:定时任务与后续扩展
跑通一次之后,接下来就是让它自动跑。OpenClaw 自己带调度,但如果你不想让它常驻,用系统的 cron 更省资源。我自己的做法是:清洗任务每 5 分钟跑一次,报表每天凌晨跑一次。
清洗任务的 cron 条目:
*/5 * * * * cd /opt/openclaw && ./venv/bin/openclaw run --config config/config.toml --settings config/settings.json --once --input nginx_access --output data/cleaned_$(date +\%Y\%m\%d\%H\%M).jsonl >> logs/cron.log 2>&1报表任务的 cron 条目:
0 1 * * * cd /opt/openclaw && ./venv/bin/openclaw report --config config/config.toml --settings config/settings.json --input data/cleaned_$(date +\%Y\%m\%d).jsonl --output reports/security_report_$(date +\%Y\%m\%d).md >> logs/cron.log 2>&1注意 cron 里的%要转义成\%,否则会被 cron 当成换行符。这个坑我踩过,任务死活不执行,查了半天才发现是%的问题。
后续想扩展的话,有几个方向。一是把output.elasticsearch打开,把清洗后的数据写进 ES,配合 Kibana 做可视化,比看 Markdown 报表直观。二是加更多规则,比如针对特定接口的异常频率、针对 User-Agent 的黑名单匹配。三是把报表的告警接出去,OpenClaw 支持 webhook 输出,命中高危事件时直接推到钉钉或者企业微信。
模型通道这块,因为所有调用都收敛在settings.json的base_url和api_key上,以后想换模型或者调整额度,只改这一个文件就行,不用动config.toml里的任何规则。这种把「日志处理逻辑」和「模型调用通道」分开的设计,是我觉得这套方案最值得保留的地方。规则可以慢慢调,通道保持稳定,两边互不干扰。
如果你还没拿到 Key,先去控制台创建一个,然后按第 2 节的 curl 验证一下。配置文件和命令都在上面了,照着敲一遍,半小时内能跑出第一份报表。