Vincent-P-essy/playbook-automator

GitHub: Vincent-P-essy/playbook-automator

一个以安全为默认理念的轻量级 SOAR 引擎,通过试运行、审批门控、幂等执行和自动逆序回滚等机制,确保应急响应剧本在自动化运行时的安全性与可追溯性。

Stars: 0 | Forks: 0

# playbook-automator [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/Vincent-P-essy/playbook-automator/actions/workflows/ci.yml) [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml) [![Tests](https://img.shields.io/badge/tests-53-brightgreen)](tests) [![Dry run](https://img.shields.io/badge/default-dry%20run-0969da)](src/soar/engine.py) [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) 使用 YAML 编写的应急响应剧本(playbook),在严密的防护机制下执行—— 正是这些机制决定了是否有人在凌晨 03:00 还会信任它们:对不可逆步骤进行审批确认、基于 key 的幂等性、 失败时按逆序自动回滚,以及无论步骤是否实际运行都会留下完整的执行记录。 ![dry run](https://static.pigsec.cn/wp-content/uploads/repos/cas/11/114496bcd1512f6eae657706376102a0196f243b83c2077f4959c71b5de66f0e.png) 请注意 `isolate` 这一行。它的条件依赖于前序步骤生成的某个值, 因此试运行(dry run)无法确切判断它是否会执行—— 于是它如实报告了该情况,指明了具体的条件,而不是妄加猜测。 ## 三个至关重要的特性 **每个破坏性步骤都必须声明如何撤销。** 如果一个破坏性步骤没有声明回滚逻辑,它将**无法通过验证**。 在突发事件中错误地隔离主机,只会让一个问题变成两个; 而凌晨 03:15 负责撤销操作的人,通常正是那个已经被折腾得够呛的人。 **每个步骤都基于 key 保证幂等性。** 这个 key 源自解析后的参数,而非当前运行实例—— 因此,在超时后的第二次尝试能够识别出第一次尝试所做的工作。如果以 run id 作为 key, 每次重试都会被视为一次全新的执行,而这恰恰是大家想要避免的 bug。 **审批是步骤本身固有的属性。** “执行任何操作前先问我一下”这种泛泛的提示往往会被顺手点击确认。 而“在进行这个具体的不可逆操作前请求确认,并准确展示它将做什么以及如何撤销”的提示, 才会让人真正仔细阅读。 ![show](https://static.pigsec.cn/wp-content/uploads/repos/cas/3c/3cca2d44a6d1eef020765a89697feddb8a2cbb11cc77a8a353b86a8e432bc6b1.png) ## 同一个剧本的实战执行 ![execute](https://static.pigsec.cn/wp-content/uploads/repos/cas/20/206fdf599e8e1f4c1472fcefbc598a37df756f483195d34925fc5c91e5f995e5.png) 一旦失败,将自动按**逆序**逐步回退: ``` revoke revoke_token ok revoked tok_a1b2c3 block_source block_ip ok blocked 203.0.113.44 isolate isolate_host failed connector timed out block_source:rollback unblock_ip rolled_back unblocked 203.0.113.44 revoke:rollback notify rolled_back posted to #security ``` 有两个细节比回滚机制本身更值得关注: - **报告为 `already_done` 的步骤不会被回滚。** 撤销本次运行并未执行的操作,正是回滚引发新事故的原因。 - **回滚失败只记录,不抛出异常。** 如果回滚在中途失败,系统会处于一个无人预期且无人知晓的状态。 ## 编写剧本 ``` id: compromised-credential inputs: [token, host, source_ip, reporter] steps: - id: enrich action: lookup_host # read-only: no gate, no rollback needed params: { host: "{host}" } approval: never bind: { criticality: criticality } - id: snapshot action: snapshot_host description: >- Forensics before containment. Isolation can trigger cleanup routines and destroy the evidence of how the credential leaked. params: { host: "{host}" } - id: isolate action: isolate_host when: "criticality == critical" # not for a wiki server params: { host: "{host}" } approval: always rollback: action: release_host params: { host: "{host}" } ``` `soar validate` 能够在事故发生之前(而不是在应对事故的紧要关头)检测出以下问题: - 破坏性步骤缺少回滚逻辑 - 调用了未注册的 action(并会列出当前已注册的 action) - 缺少必要的参数 - **存在永远无法被填充的占位符** —— 这是剧本中最常见的 bug。这种 bug 会导致程序执行到生产环境时, 将字面量 IP 地址 `{source_ip}` 作为目标阻塞流程 - 破坏性步骤被设置为在无人值守下运行(这是允许的,但必须是你深思熟虑后的有意为之) ![validate](https://static.pigsec.cn/wp-content/uploads/repos/cas/7c/7cc8ae8ef34359a8910ede2055f4baa8ba4e78309e3e4b2dca8c4d055737f731.png) ## 条件表达式不是 Python ``` evaluate("criticality == critical", context) evaluate("distance_km > 5000", context) evaluate("tag not in tags", context) ``` 这里特意使用了一个轻量级的表达式解析器,而不是直接使用 `eval`。剧本本质上是一种配置, 通常由非代码审核人员编写;如果条件表达式可以执行任意 Python 代码, 那就等同于将一个随时可能通过 pull request 触发的远程代码执行漏洞(RCE)拱手送人。 ## 连接器 ![actions](https://static.pigsec.cn/wp-content/uploads/repos/cas/27/271e7dca8ce5e08599b9ac9cbe4b7b3e3bf18b48ded10ddf0f670ebfa54f424c.png) 每个连接器都会声明自身是否具有**破坏性**:即其执行结果的影响是否会超越本次运行生命周期, 以及是否需要有人专门进行撤销操作。仅仅这一个标志位, 就驱动了审批拦截、回滚验证以及试运行时的输出内容。 内置的连接器都是模拟的,但包含了真实场景中必须处理的状态逻辑—— 例如,对已隔离的主机再次执行隔离会报告 `already_done`; 吊销一个未知的 token 会被*拒绝*,而不是直接报错或谎报成功。 要将 `isolate_host` 对接到真实的 EDR,只需编写对应的处理函数和配置凭证即可。 而它周围的一切——审批门控、幂等 key、回滚机制、执行记录——才是真正困难且通常最缺失的核心部分。 ## 恢复失败的运行 ``` soar run playbook.yaml --input host=web-01 --execute --out run.json # ... 它在第 5 步失败 ... soar run playbook.yaml --input host=web-01 --execute --resume run.json ``` 已完成的步骤会被跳过,**并且它们绑定的值会被重新注入**到上下文中。 如果只跳过而不重新注入值,曾在这里引发过一个真实的 bug: 恢复操作在跳过的步骤处成功了,但在下一步却失败了, 因为后续步骤引用了一个根本没有被生成出来的值。现在有一个专门以该 bug 命名的测试用例。 ## 安装与运行 ``` git clone https://github.com/Vincent-P-essy/playbook-automator cd playbook-automator pip install -e . soar validate playbooks soar show playbooks/compromised-credential.yaml soar run playbooks/compromised-credential.yaml \ --input token=tok_a1b2c3 --input host=pay-web-03 \ --input source_ip=203.0.113.44 --input reporter=secret-scanner ``` 添加 `--execute` 参数以执行实际操作。在没有 TTY 且未指定 `--yes` 的情况下, 受门控的步骤会被**拒绝**执行,而不是自动批准—— 无人值守的运行绝不能悄悄地给自己授予执行权限。 ## 功能边界 - **步骤按顺序在同一进程中执行。** 没有任务分发,也没有并行处理。应急响应剧本通常很简短, 且步骤的先后顺序往往至关重要;引入 DAG(有向无环图)引擎带来的潜在故障模式,可能远比它能解决的要多。 - **内置的连接器均为模拟实现。** 这个引擎本身才是核心产品。 - **回滚采取尽力而为策略。** 如果撤销操作同样失败,系统会将其记录下来,这时就需要人工介入了。 没有什么工具能让一个本身就不稳定的 API 变得可靠。 - **不提供调度器,也没有内置队列。** 你可以通过 SIEM 的 webhook 或 cron 来触发它。 如果把触发层也揽过来,它就变成了一个平台,而不是一个好用的工具了。 ## 项目布局 ``` src/soar/ playbook.py the YAML schema and its validation engine.py dry run, approval, idempotency, rollback, the record connectors/ the action registry, plus simulated connectors with state cli.py run · validate · show · actions playbooks/ compromised-credential · suspicious-login ``` ## 许可证 MIT
标签:PB级数据处理, Python, SOAR, 安全运维, 文档结构分析, 无后门, 自动化编排, 逆向工具