跳转至

三库运维助手(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)

  1. 三个库是 security-guard.sh 的唯一事实来源,解析器必须与检测器完全一致(检测器用 python3 + PyYAML),否则会出现「平台看到的」≠「检测器读到的」;
  2. nfarmsd 没有 YAML 依赖,且本机 cargo registry 不可用,不宜为改库新增依赖;
  3. 写盘要动 /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。