三库运维助手(lib-ops.sh)¶
仓库路径:
deploy/nfos/lib-ops.sh| 线上路径:/opt/Cy/services/system.d/nfos/lib-ops.sh语言:bash(内含一段内嵌 python3 核心)| 定时器:无(被 nfarmsd 经 sudo 调用,不挂 timer)
用途¶
特征库 / 规则库 / 模板库的唯一写入口(「三库」),并负责校验、备份、原子替换与完整性基线同步。
谁在调用(源码 docstring):nfarmsd(online / console 平台的「安全防线」页)经 sudo 调用本脚本;请求体是 stdin 上的一个 JSON,脚本把结果以 JSON 输出到 stdout。
为什么要有这一层(而不是让 nfarmsd 自己读写 YAML)¶
- 三个库是
security-guard.sh的唯一事实来源,解析器必须与检测器完全一致(检测器用 python3 + PyYAML),否则会出现「平台看到的」≠「检测器读到的」; nfarmsd没有 YAML 依赖,且本机 cargo registry 不可用,不宜为改库新增依赖;- 写盘要动
/opt/Cy下的 root 文件,必须集中到一处做校验、备份、原子替换与完整性基线同步(否则「自己改库」会立刻触发IN-001完整性告警)。
安全约定(本脚本存在的另一半理由)¶
库内容只走 stdin,绝不出现在命令行参数里。
2026-09-14 云安全中心(Aegis)报出的「恶意脚本代码执行_v2」,根因就是 security-guard.sh 曾把整个特征库 JSON 当作 python 的 argv——里面的反弹 Shell 正则文本被主机 Agent 的引擎判为恶意脚本。本脚本彻底不把库内容放进 argv,且临时脚本只落在 root 独占的 state 目录。
存储模型(2026-09-23 迁移):SQLite 是唯一事实来源,YAML 是自动导出的兼容视图¶
libs.db(/opt/Cy/services/system.d/nfos/libs.db,schema v1)保存三库内容 + 全局策略;- 三库 YAML 仍然照旧写盘——
security-guard.sh/gen-waf-from-libs.py/log-audit.py零改动继续读 YAML(在线防线不因换存储而重启风险); - 每次成功写操作与
apply之后,都用当前 YAML 全量重建libs.db(幂等);nfarmsd的/libs/*优先读 SQLite,DB 缺失或落后于 YAML 时回退解析 YAML; - 入库失败绝不静默:写 stderr、响应
ok=false、进程退出码 3。
何时用¶
- 平台侧改库:只从
nfarmsd的「安全防线」页发起;运维不要手工编辑三库 YAML(会绕过校验、备份与基线同步,立刻触发IN-001)。 - 想只读体检「libs.db 与三库 YAML 是否一致」:
lib-ops.sh --check-sqlite(0=一致 /1=不一致)。 - 想用手工全量重建
libs.db:lib-ops.sh --sync-sqlite(0=成功 /3=失败)。 - 写完库之后:
{"op":"apply"}—— 校验三库 + 同步完整性基线 + 同步 SQLite。这是让改动生效并被检测器读到的唯一正确收尾动作。 - 回滚某次改库:
{"op":"rollback","kind":"...","backup":"/opt/.../state/lib-backups/xxx.yml"}。
命令与参数¶
argv 入口(可选;argv 里只有固定字面量,库内容依旧只走 stdin)¶
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
--check-sqlite |
字面量(case "${1:-}") |
无 | 只读校验 libs.db 与三库 YAML 是否一致(0=一致 1=不一致) |
--sync-sqlite |
字面量(case "${1:-}") |
无 | 用当前 YAML 手工全量重建 libs.db(0=成功 3=失败) |
共 2 个 argv 参数。 其余任何 argv 都会得到
{"ok": false, "errors": ["未知参数:…(只支持 --check-sqlite / --sync-sqlite;其余操作走 stdin JSON)"]} 并退出码 2。
stdin JSON 协议(唯一的写入口)¶
op |
附带字段 | 作用 |
|---|---|---|
summary |
— | 汇总三库状态 |
read |
kind |
读整库(signatures / rules / templates / policy) |
get |
kind + id |
取单条 |
validate |
kind + item |
校验单条(不写盘) |
create |
kind + item |
新增 |
update |
kind + item |
更新 |
delete |
kind + id |
删除 |
policy_update |
item |
更新全局策略 |
apply |
— | 校验三库 + 同步完整性基线 + 同步 SQLite |
rollback |
kind + backup |
按快照回滚 |
输出:{"ok":true|false,"data":{...},"errors":[...],"warnings":[...],"sha256_before":"...","sha256_after":"...","backup_path":"..."}
(上述协议与输出 JSON 结构保持不变;新增字段只见于 data.sqlite / 顶层 sqlite,属追加。)
kind 白名单为 ("signatures", "rules", "templates", "policy");read/get/validate/create/update/delete/policy_update/rollback 必须带 kind。
不带参数(且 stdin 为空)时的默认行为¶
raw = sys.stdin.read() 为空时输出
{"ok": false, "errors": ["stdin 上没有请求 JSON"]} 并退出码 0(注意:不是失败码)。
先决条件:脚本启动时要在
$NFOS_DIR/state下建一个临时 python 脚本 (mktemp "$STATE_DIR/lib-ops.XXXXXX.py",chmod 600,trap 'rm -f "$TMP"' EXIT)。 建不出来时输出{"ok":false,"errors":["无法在 state 目录创建临时脚本"]}并退出码 1。
示例¶
L=/opt/Cy/services/system.d/nfos/lib-ops.sh
# 1) 只读体检:libs.db 与三库 YAML 是否一致(0=一致 1=不一致)
sudo bash "$L" --check-sqlite
# 2) 手工全量重建 libs.db(0=成功 3=失败)
sudo bash "$L" --sync-sqlite
# 3) 汇总三库状态(库内容只走 stdin,不进 argv)
printf '%s' '{"op":"summary"}' | sudo bash "$L"
# 4) 读一个库
printf '%s' '{"op":"read","kind":"signatures"}' | sudo bash "$L"
# 5) 校验一条特征(不写盘)——先用它把 item 试对,再 create
printf '%s' '{"op":"validate","kind":"signatures","item":{"id":"XX-999","name":"示例"}}' | sudo bash "$L"
# 6) 改完库之后收尾(校验 + 同步完整性基线 + 同步 SQLite)
printf '%s' '{"op":"apply"}' | sudo bash "$L"
# 7) 按快照回滚(快照必须在 state/lib-backups/ 之内)
ls -l /opt/Cy/services/system.d/nfos/state/lib-backups/
printf '%s' '{"op":"rollback","kind":"signatures","backup":"/opt/Cy/services/system.d/nfos/state/lib-backups/signature-library.yml.<快照>"}' \
| sudo bash "$L"
依赖与权限¶
| 项 | 内容 |
|---|---|
| 解释器 | bash(#!/usr/bin/env bash,set -uo pipefail)+ python3(内嵌核心,经 heredoc NFOS_LIB_OPS_PY 落到临时脚本执行) |
| 第三方模块 | PyYAML(内嵌 python 核心解析三库 YAML) |
| 外部命令 | mktemp、chmod、python3、sqlite3(python 标准库模块,不是 CLI) |
| root | 需要。三库与 libs.db 都在 /opt/Cy 下、且 libs.db 是 root:nfarms 0640 |
sudo -n |
需要(调用方侧)。install-security-console.template.sh 落地的精确授权是 /etc/sudoers.d/nfos-lib-ops(install -m 440 -o root -g root),内容为 nfarms ALL=(root) NOPASSWD: /opt/Cy/services/system.d/nfos/lib-ops.sh —— 只放行这一个固定脚本。脚本自身不再嵌套 sudo(注释说明:sync_baseline() 刻意不跑 security-guard.sh --baseline,因为那需要嵌套 sudo,且会顺带重扫全盘 SUID) |
| 写入 | 三库 YAML signature-library.yml / rule-library.yml / template-library.yml(文本级改写,只替换目标条目所在块,其余注释与文档原样保留);libs.db(root:nfarms 0640);快照 state/lib-backups/;完整性基线 state/security-baseline.txt(写 .tmp.<pid> 后 os.replace,chmod 640);临时脚本 state/lib-ops.XXXXXX.py(chmod 600,退出即删) |
| 网络 | 不联网 |
| 数据类型白名单 | SEVERITIES = ("info","warning","high","critical");KNOWN_DETECT(与守卫检测分支一一对应:proc_shell_socket / proc_cmdline / proc_parent / egress / listen_external / file_hash / fs_change / auth_fail / auth_success / suid_new / web_attack / log_anomaly / none);KNOWN_CATEGORIES(13 项);KNOWN_ACTIONS = ("alert","ticket","record_only","isolate_hint","block_ip") |
幂等与失败语义¶
- 写操作是「先校验、后写盘」:任何写操作都先做全库校验,校验不过一个字节都不写(避免把检测器改瞎)。
- 写前必留快照:
state/lib-backups/,并把sha256+ 快照路径回填给调用方(backup_path)。rollback会校验快照路径必须位于BACKUP_DIR之内(os.path.realpath前缀比较),拒绝任意路径覆盖。 - 原子替换:YAML 写入先落临时文件(
chmod 644)再替换目标(chmod 640);基线文件走.tmp.<pid>+os.replace;libs.db重建也是原子替换(db_backup/replace在响应里可见)。 - 幂等:
apply与--sync-sqlite都是「用当前 YAML 全量重建」——INSERT … ON CONFLICT(id) DO UPDATE+ 删除多余行 + 原子替换,重复执行结果一致。 - 状态文件 / 临时文件:临时 python 脚本
state/lib-ops.XXXXXX.py由trap保证退出即删;不会残留半成品。 - 入库失败绝不静默:这是设计里的硬约束——
op_apply()先做校验与基线同步,再做 SQLite 入库;入库失败会把sqlite_error放进响应、写 stderr、返回ok=false+sqlite_sync_failed=true,进程退出码 3。源码注释:「否则『apply 成功』是假的」。 - 退出码:
| 退出码 | 含义 |
|---|---|
0 |
stdin JSON 操作正常(注意:包括「stdin 为空」「请求 JSON 解析失败」「未知 op」「未知 kind」「缺 kind」——这些都只体现在响应 ok=false + errors 里,退出码是 0);--check-sqlite 一致;--sync-sqlite 成功 |
1 |
--check-sqlite 不一致(打印 LIBS_DB_NOT_READY);或无法在 state 目录创建临时脚本 |
2 |
argv 未知参数 |
3 |
入库失败(res["sqlite_sync_failed"])——这是唯一能被脚本层看到的失败信号;或 --sync-sqlite 失败 |
排障要点:走 stdin 的操作不能只看退出码。判定依据是响应体的
ok字段 (--check-sqlite例外,它同时打印LIBS_DB_OK/LIBS_DB_NOT_READY)。
常见错误与排查¶
| 现象 | 代码里的对应分支 | 排查步骤 |
|---|---|---|
{"ok":false,"errors":["无法在 state 目录创建临时脚本"]},退出码 1 |
mktemp 失败分支 |
ls -ld /opt/Cy/services/system.d/nfos/state;权限/空间;确认以 root 运行 |
{"ok":false,"errors":["stdin 上没有请求 JSON"]} |
if not raw.strip() |
忘了用管道喂 JSON(退出码仍是 0):printf '%s' '{"op":"summary"}' \| sudo bash lib-ops.sh |
{"ok":false,"errors":["请求 JSON 解析失败: …"]} |
json.loads 的 except |
用 printf '%s' "$REQ" \| python3 -m json.tool 先验一遍(注意 shell 引号转义) |
{"ok":false,"errors":["未知 op: …"]} |
op 白名单校验 |
op 只能是 summary/apply/read/get/validate/create/update/delete/policy_update/rollback |
{"ok":false,"errors":["未知 kind: …(可选 signatures, rules, templates, policy)"]} |
kind 白名单校验 |
只认这四个 |
{"ok":false,"errors":["op=… 需要 kind 字段"]} |
needs_kind 校验 |
read/get/validate/create/update/delete/policy_update/rollback 都必须带 kind |
退出码 3、响应里有 "sqlite_sync_failed": true 与 data.sqlite_error |
op_apply() 的 SQLite 入库失败分支 |
三库校验与基线已同步,但 libs.db 没写成。看 stderr 的 [lib-ops] apply:SQLite 同步失败: …;检查 /opt/Cy/services/system.d/nfos/ 写权限与磁盘空间;修好后重跑 {"op":"apply"} |
{"ok":false,"errors":["完整性基线同步失败: 基线文件不存在:请先执行 sudo security-guard.sh --baseline"]} |
sync_baseline() 的基线缺失分支 |
先 sudo security-guard.sh --baseline 重建基线,再 apply |
{"ok":false,"errors":["基线中缺少条目:…(请执行 sudo security-guard.sh --baseline 重建)"]} |
sync_baseline() 的 missing 分支 |
同上;说明基线里没有这三个库文件的哈希行 |
{"ok":false,"errors":["快照路径必须在 /opt/…/state/lib-backups 之内(拒绝任意路径覆盖)"]} |
op_rollback() 的 realpath 前缀校验 |
rollback 的 backup 必须指向 state/lib-backups/ 下的文件 |
--check-sqlite 返回 1 且打印 LIBS_DB_NOT_READY |
ok = exists and not error and drift == [] and items_match is True |
响应 JSON 里有 exists / schema_version / counts / drift / last_sync_at / mismatch / error;看是「DB 不存在」「schema 不符」还是「内容漂移」 |
改库后立刻收到 IN-001 完整性告警 |
忘了 apply(sync_baseline() 就是给这个用的) |
printf '%s' '{"op":"apply"}' \| sudo bash lib-ops.sh;见 docs/CHANGELOG-2026-09-26.md「§9.9」记录的这次真实 IN-001 |
| 检测器读到的与平台看到的不一致 | 存储模型:SQLite 优先、漂移时回退 YAML | 先 --check-sqlite;不一致再 --sync-sqlite |
改 lib-ops.sh 的 KNOWN_DETECT 后库加载失败 |
KNOWN_DETECT 与守卫检测分支必须一一对应 |
对照 security-guard.sh 的检测分支;见 docs/CHANGELOG-2026-09-18.md「§4」与 docs/CHANGELOG-2026-09-23.md「§12」 |
平台侧调 lib-ops.sh 报 sudo: a password is required |
/etc/sudoers.d/nfos-lib-ops 缺失或被改 |
sudo -n cat /etc/sudoers.d/nfos-lib-ops;期望恰好是 nfarms ALL=(root) NOPASSWD: /opt/Cy/services/system.d/nfos/lib-ops.sh。用 install-security-console.template.sh 重建(它以 /var/tmp/nfos-lib-ops.sudoers 为中转,不留在 /etc/sudoers.d/ 之外的原地备份) |
回滚方式¶
本脚本会改三库 YAML、libs.db 与完整性基线——这是本仓库里副作用最强的一个脚本。回滚有三层:
| 层 | 手段 |
|---|---|
| 单次改库的回滚(首选) | {"op":"rollback","kind":"…","backup":"/opt/…/state/lib-backups/<快照>"} —— 脚本在每次写前都留了快照 |
| 存储层回退(回到 YAML) | 删掉 libs.db → nfarmsd 的 /libs/* 自动回退读 YAML(YAML 全程是权威的可读副本) |
| 脚本本体 | 用版本化备份覆盖回去 |
D=/opt/Cy/services/system.d/nfos
BK=$D/state/unit-backups
# 1) 列出可用快照,按需回滚某一库
ls -lt "$D/state/lib-backups/" | head
printf '%s' '{"op":"rollback","kind":"signatures","backup":"'"$D"'/state/lib-backups/<快照文件名>"}' \
| sudo bash "$D/lib-ops.sh"
# 2) 回滚完必须再 apply 一次(同步基线与 libs.db),否则会触发 IN-001
printf '%s' '{"op":"apply"}' | sudo bash "$D/lib-ops.sh"
# 3) 存储层彻底回退:删掉 libs.db —— nfarmsd 的 /libs/* 会自动回退读 YAML
# 先备份,再删
TS=$(date +%Y%m%d-%H%M%S)
sudo install -d -m 755 "$BK"
sudo cp -a "$D/libs.db" "$BK/libs.db.bak-$TS" 2>/dev/null
sudo rm -f "$D/libs.db"
# 需要时再重建:sudo bash "$D/lib-ops.sh" --sync-sqlite
# 4) 脚本本体回滚
sudo cp -a "$D/lib-ops.sh" "$BK/lib-ops.sh.bak-$TS"
sudo cp -a "$BK/lib-ops.sh.bak-<时间戳>" "$D/lib-ops.sh"
sudo chown root:root "$D/lib-ops.sh"
sudo chmod 755 "$D/lib-ops.sh"
# 5) 验收
sudo bash "$D/lib-ops.sh" --check-sqlite
printf '%s' '{"op":"summary"}' | sudo bash "$D/lib-ops.sh"
备份落点铁律:脚本/单元的任何备份放
/opt/Cy/services/system.d/nfos/state/unit-backups/(命名<文件名>.bak-YYYYMMDD-HHMMSS)或仓库侧/opt/Cy/nfarms/.backup/。 绝不能放在/etc/systemd/system/—— 会触发 PS-002 完整性告警。见 timers.md 第 11 节。⚠️ 改完三库必须
apply:sync_baseline()就是为了避免「自己改库」触发IN-001。 历史上有过真实案例(docs/CHANGELOG-2026-09-26.md「§9.9 同时关闭的工单与终态」记录了NFarms-SEC-202609261500-10659/-10660/-10661三张由改治理文件触发的真实IN-001工单)。
相关 CHANGELOG¶
| CHANGELOG | 章节标题 |
|---|---|
docs/CHANGELOG-2026-09-18.md |
§4. lib-ops.sh 识别新的 detect 类型 |
docs/CHANGELOG-2026-09-18.md |
§1. 特征库/规则库/模板库按《安全事件分析与应急响应指南》扩充 |
docs/CHANGELOG-2026-09-23.md |
§10. 三库存储迁移到 SQLite(YAML 降级为兼容视图) |
docs/CHANGELOG-2026-09-23.md |
§12. 修两个真缺口:主节点邮件腿 + 备节点审计跑不了 |
docs/CHANGELOG-2026-09-26.md |
§5.1 事故:库哨兵误判导致安全巡检停摆约 45 秒 |
docs/CHANGELOG-2026-09-26.md |
§9.9 同时关闭的工单与终态(改治理文件触发的真实 IN-001) |
docs/CHANGELOG-2026-09-26.md |
§13.8 PS-002 的正确修法(第二次才对)+ 一个「看起来对但永远不生效」的坑 |
存储迁移的执行脚本见 migrate-libs-to-sqlite.md;消费三库的在线防线见 log-audit.md 与
security-guard.sh;定时器全景见 timers.md。