osmedeus 事件工作流实战:Event 触发器与事件生成系统完整指南
【免费下载链接】osmedeusA Modern Orchestration Engine for Security项目地址: https://gitcode.com/GitHub_Trending/os/osmedeus
osmedeus 内置了一套完整的事件驱动(Event-Driven)工作流系统,让安全扫描任务之间可以通过「事件」而非人工串联进行自动联动。本文以仓库中test/testdata/workflows/events/目录下的官方示例为主线,系统讲解事件发射(Emitter)、事件接收(Receiver)与触发器(Trigger)的完整用法,包括generate_event/generate_event_from_file两个事件函数、过滤与去重触发器配置,以及背后的源码实现机制。读完本文,你将能够在自己的 osmedeus 工作流中独立编写"扫描 A 产出事件 → 自动触发扫描 B"的自动化链路,并掌握事件系统的可靠性设计(服务端失败自动降级到数据库队列与 Webhook)。
事件系统概览:Emitter 与 Receiver
在 osmedeus 中,事件系统由两类角色构成:
- Emitter(事件发射器):工作流在执行过程中,通过内置函数向事件系统"发布"事件。典型场景是子域名枚举完成、漏洞扫描发现高危项、爬虫抓取到新 URL 等。
- Receiver(事件接收器):通过
triggers配置监听特定topic的工作流。当事件到达且匹配触发条件时,osmedeus 的 Event Receiver 会自动拉起对应工作流执行。
事件从产生到触发的完整链路如下:
- 工作流中的 function 步骤调用
generate_event(...)或generate_event_from_file(...); - 事件经由
SendEventWithFallback发送:分布式模式下优先走 Redis Pub/Sub,否则尝试发送到本地 Server,发送失败时降级为数据库队列,并同时推送到已配置的 Webhook; - Event Receiver 收到事件后,将其与已注册工作流的触发器进行 topic 匹配;
- 匹配成功后再执行过滤器(filters)与去重(dedupe)逻辑;
- 通过全部校验后,对应工作流被自动触发执行。
仓库目录test/testdata/workflows/events/下共提供了 5 个示例工作流,覆盖事件系统的全部核心场景:
| 分类 | 文件 | 说明 |
|---|---|---|
| Emitters | simple-emitter.yaml | 基础事件发射,演示generate_event与generate_event_from_file |
| Emitters | vuln-emitter.yaml | 模拟漏洞扫描器发射结构化漏洞发现事件 |
| Receivers | simple-receiver.yaml | 基础事件触发,监听 discovery 事件 |
| Receivers | filtered-receiver.yaml | 高级过滤,包含严重级别校验与 Webhook 通知 |
| Receivers | dedupe-receiver.yaml | 事件去重,防止重复处理 |
事件函数详解
事件函数在internal/functions/event_functions.go中实现,通过 goja JS 运行时暴露给工作流调用。
generate_event(workspace, topic, source, data_type, data)
发射单条事件,可携带结构化数据。五个参数含义如下:
| 参数 | 说明 |
|---|---|
workspace | 事件所属工作空间,通常传入{{TargetSpace}}模板变量 |
topic | 事件主题,如discovery.asset、scan.finding,Receiver 据此匹配 |
source | 事件来源标识,如扫描器名称my-scanner |
data_type | 数据类型,如subdomain、vulnerability |
data | 事件数据,可以是字符串,也可以是 JS 对象(结构化 JSON) |
- type: function function: | generate_event("{{TargetSpace}}", "discovery.asset", "my-scanner", "subdomain", "api.example.com")从源码看(internal/functions/event_functions.go),该函数有几点重要行为:
- 必填校验:
workspace、topic、source、data_type四个字段缺一不可,缺失时记录 Warn 日志并返回false; - 上下文自动填充:
runID(RunID)与workflowName(WorkflowName)会从运行时上下文自动读取,无需手动传入;从工作流执行时sourceType标记为run,通过osmedeus eval等独立场景执行为eval,无 run 上下文时会生成 8 位短 UUID 作为 runID; - 可靠性设计:底层调用
notify.SendEventWithFallback,即使服务端暂不可用,事件也会进入数据库队列等待后续处理,因此函数最终仍然返回true。
generate_event_from_file(workspace, topic, source, data_type, file_path)
逐行读取文件并为每一行发射一条事件,返回发射的事件条数。适合把枚举结果文件批量转成事件流。
- type: function functions: - 'generate_event_from_file("{{TargetSpace}}", "discovery.asset", "my-scanner", "subdomain", "{{Output}}/subdomains.txt")'从源码看(internal/functions/event_functions.go),该函数会跳过空白行,每行作为事件data发射一条;文件不存在或读取失败时返回 0。返回值为成功发射的条数,可配合set_var保存并在后续步骤中引用(示例中即用get_var("count")打印发射数量)。
Emitter 工作流实战
基础发射器:simple-emitter.yaml
simple-emitter.yaml 演示了三种典型发射方式:
1. 单条事件发射——先用 bash 步骤构造资产文件,再对单个目标发射一条事件:
- name: discover-assets type: bash command: | echo "api.{{target}}" > {{Output}}/assets.txt echo "www.{{target}}" >> {{Output}}/assets.txt echo "mail.{{target}}" >> {{Output}}/assets.txt - name: emit-single-event type: function functions: - 'generate_event("{{TargetSpace}}", "discovery.asset", "simple-emitter", "subdomain", "new.{{target}}")'2. 批量事件发射——读取文件逐行发射,并用返回值打印统计信息:
- name: emit-events-from-file type: function functions: - 'set_var("count", generate_event_from_file("{{TargetSpace}}", "discovery.asset", "simple-emitter", "subdomain", "{{Output}}/assets.txt"))' - 'print_green("Emitted " + get_var("count") + " events from file")'3. 结构化事件发射——data 参数传入 JS 对象,形成结构化 JSON 事件:
- name: emit-structured-event type: function function: | generate_event("{{TargetSpace}}", "discovery.complete", "simple-emitter", "summary", { target: "{{target}}", asset_count: 3, status: "completed" })注意该示例中步骤间混合使用了functions(列表)与function(单条)两种写法,均为合法语法。
漏洞扫描模拟器:vuln-emitter.yaml
vuln-emitter.yaml 模拟了一个真实的漏洞扫描器,发射scan.finding主题的结构化事件,事件数据中携带url、severity、template_id、confirmed、matched_at、description等字段:
- name: emit-critical-finding type: function function: | generate_event("{{TargetSpace}}", "scan.finding", "vuln-scanner", "vulnerability", { url: "https://{{target}}/admin", severity: "critical", template_id: "exposed-admin-panel", confirmed: true, matched_at: "/admin", description: "Admin panel exposed without authentication" }) post_run: - 'print_red("CRITICAL: Exposed admin panel found")'该示例同时演示了post_run钩子的用法:事件发射后立即在发射端输出彩色告警。它分别发射 critical / high / low 三个级别的 finding,并最终发射一条scan.complete汇总事件——这为下文filtered-receiver的"仅对 high/critical 告警、对 completed 事件统计校验"提供了绝佳的联调对象。
Receiver 与 Trigger 配置
Receiver 的核心是工作流顶层的triggers配置块。触发器结构定义在 internal/core/trigger.go,事件配置EventConfig支持以下字段:
| 字段 | 说明 |
|---|---|
topic | 监听的事件主题,如discovery.asset;支持 glob 通配(*、test*、*.new、assets.*.created) |
filters | JS 表达式过滤器列表,如event.data.severity == 'high',全部通过才触发 |
filter_functions | 可调用内置工具函数的 JS 过滤器,如contains(event.data.url, '/api/') |
dedupe_key | 去重键模板,如{{event.data.url}},可用{{event.source}}等组成复合键 |
dedupe_window | 去重窗口时长,如5s、1m、5m、1h,窗口内重复事件被忽略 |
基础事件触发
triggers: - name: on-new-asset on: event event: topic: "discovery.asset" enabled: true监听discovery.asset主题的所有事件。topic 匹配逻辑见 internal/core/trigger.go:topic 为空则匹配一切事件;含通配符时使用path.Match做 glob 匹配,否则精确匹配。
过滤事件触发
triggers: - name: on-high-severity on: event event: topic: "scan.finding" filters: - "event.data.severity == 'high'" - "event.data.confirmed == true" enabled: true过滤器是 JS 表达式,可访问事件对象event(含event.topic、event.source、event.data_type、event.data.*等)。所有过滤器必须全部求值为真,触发器才会命中。这使得"只对确认过的高危漏洞告警"这类精细控制成为可能。
去重事件触发
triggers: - name: on-url-dedupe on: event event: topic: "crawler.url" dedupe_key: "{{event.data.url}}" dedupe_window: "5m" enabled: truededupe_key是一个模板字符串,事件到达后会渲染该模板作为唯一键;在dedupe_window窗口内(如 5 分钟)相同键的事件会被丢弃。从源码看(internal/core/trigger.go),去重生效的前提是dedupe_key非空且dedupe_window解析为大于 0 的时长(time.ParseDuration解析,支持5s、1m、1h等 Go 时长格式)。
Receiver 工作流实战
基础接收器:simple-receiver.yaml
simple-receiver.yaml 注册了两个触发器:一个事件触发器on-new-asset,监听discovery.asset主题并附加来源与类型过滤;一个被禁用的手动触发器manual-trigger(enabled: false),演示了触发器可随时开关:
triggers: - name: on-new-asset on: event event: topic: "discovery.asset" filters: - "event.source == 'simple-emitter'" - "event.data_type == 'subdomain'" enabled: true - name: manual-trigger on: manual enabled: false触发后,工作流将{{target}}写入{{Output}}/processed-assets.txt完成资产落地处理。
过滤接收器:filtered-receiver.yaml
filtered-receiver.yaml 是高级过滤的完整示例,两个触发器分别处理漏洞告警与扫描汇总:
triggers: - name: on-high-severity-finding on: event event: topic: "scan.finding" filters: - "event.data.severity == 'high' || event.data.severity == 'critical'" - "event.data.confirmed == true" enabled: true - name: on-scan-complete on: event event: topic: "scan.complete" filters: - "event.data.finding_count > 0" enabled: true注意过滤器支持||逻辑或与>数值比较等完整 JS 语法。触发后该工作流会调用notify_webhook("High severity finding detected: {{target}}")发送外部通知,构成"扫描发现高危 → 自动告警"的闭环。与vuln-emitter.yaml联调时,low 级别的 finding 会被第一个触发器正确过滤掉(severity == 'low'不满足high || critical)。
去重接收器:dedupe-receiver.yaml
dedupe-receiver.yaml 演示了两种去重键的写法:单字段键与复合键:
triggers: - name: on-new-url-dedupe on: event event: topic: "crawler.url" filters: - "event.data_type == 'url'" dedupe_key: "{{event.data.url}}" dedupe_window: "5m" enabled: true - name: on-asset-composite-dedupe on: event event: topic: "discovery.asset" dedupe_key: "{{event.source}}-{{event.data.value}}" dedupe_window: "1h" enabled: trueon-asset-composite-dedupe用{{event.source}}-{{event.data.value}}组合成复合去重键,实现"同一来源 + 同一资产"维度上的去重——即使不同来源发射了相同资产,也不会被误判为重复。这在多个扫描器可能产出重叠结果的真实场景中非常实用。
运行与联调
命令行方式
# 运行发射器生成事件 osmedeus run -m simple-emitter -t example.com # 运行接收器处理事件(若已注册到调度器,会自动被事件触发) osmedeus run -m simple-receiver -t example.com-m指定 module 名称,-t指定目标。完整联调路径为:先启动/注册接收器工作流(事件接收器随调度器默认启用),再运行发射器,观察接收器是否被自动拉起。官方事件接收器文档 docs/api/event-receiver.mdx 指出:事件接收相关端点仅在事件接收器启用时可用(默认随 scheduler 启用)。
API 方式
除工作流内部发射外,事件系统还暴露了 HTTP API(实现见 pkg/server/handlers/event_receiver.go),可用于外部系统注入事件:
查询事件接收器状态:
curl http://localhost:8002/osm/api/event-receiver/status \ -H "Authorization: Bearer $TOKEN"列出已注册的事件触发工作流:
curl http://localhost:8002/osm/api/event-receiver/workflows \ -H "Authorization: Bearer $TOKEN"通过 API 发射事件:
curl -X POST http://localhost:8002/osm/api/events/emit \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "topic": "discovery.asset", "source": "external-scanner", "data_type": "subdomain", "data": {"value": "api.example.com"} }'EmitEventRequest结构(pkg/server/handlers/event_receiver.go)支持topic(必填)、name、source、data、data_type字段,事件source_type会被标记为api。这让 CI/CD、外部扫描器甚至定时任务都能无缝接入 osmedeus 的事件生态。
源码级可靠性机制
事件函数最终都汇入notify.SendEventWithFallback(internal/notify/server_event.go),其核心设计是"多级降级,事件不丢":
- 分布式模式(Redis 已配置):优先通过 Redis Pub/Sub 发布事件(
SendEventViaRedis),成功后仍会同步发送结构化 Webhook;Redis 失败则继续走 HTTP 链路; - 本地 Server 直连:通过
ServerEventClient.SendEvent发送到本机 Server 的事件接收器; - 数据库队列兜底:服务端不可达时,事件写入数据库队列等待后续补发;
- Webhook 通知:事件同时推送到配置的 Webhook 端点。
这套链路解释了为何generate_event在服务端不可用时仍返回true——事件并未丢失,而是进入了持久化队列。在单机部署下,Emitter 与 Receiver 通过本地 Server 完成解耦;在分布式部署下,则通过 Redis 实现跨节点的事件广播,是 osmedeus 多云/多节点扫描编排的基础设施。
小结
事件系统把 osmedeus 从"手工串 workflow"升级为"事件驱动自动编排":
- 发射端:用
generate_event/generate_event_from_file把扫描中间产物(子域名、URL、漏洞发现、扫描汇总)转成结构化事件流; - 接收端:用
triggers+event配置按 topic 订阅事件,配合filters做精细筛选、dedupe_key+dedupe_window防重复处理; - 可靠性:事件经 Redis / HTTP / 数据库队列 / Webhook 多级降级通道送达,服务端短暂不可用不会造成事件丢失;
- 开放性:除工作流内部发射外,HTTP API(
/osm/api/events/emit)允许任何外部系统注入事件。
建议按test/testdata/workflows/events/目录的 5 个示例逐步实践:先用simple-emitter+simple-receiver打通链路,再加入filtered-receiver的过滤逻辑与vuln-emitter联调,最后用dedupe-receiver验证去重效果,即可完整掌握 osmedeus 事件驱动编排的核心能力。
【免费下载链接】osmedeusA Modern Orchestration Engine for Security项目地址: https://gitcode.com/GitHub_Trending/os/osmedeus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考