148 lines
7.7 KiB
Markdown
148 lines
7.7 KiB
Markdown
# Notifications Rules
|
|
|
|
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
|
|
|
Rules decide which platform events deserve a notification, how aggressively they should be throttled, and which channels/actions should run. They are tenant-scoped contracts that guarantee deterministic routing across Notify.Worker replicas.
|
|
|
|
---
|
|
|
|
## 1. Rule lifecycle
|
|
|
|
1. **Authoring.** Operators create or update rules through the Notify WebService (`POST /rules`, `PATCH /rules/{id}`) or UI. Payloads are normalised to the current `NotifyRule` schema version.
|
|
2. **Evaluation.** Notify.Worker evaluates enabled rules per incoming event. Tenancy is enforced first, followed by match filters, VEX gates, throttles, and digest handling.
|
|
3. **Delivery.** Matching actions are enqueued with an idempotency key to prevent storm loops. Throttle rejections and digest coalescing are recorded in the delivery ledger.
|
|
4. **Audit.** Every change carries `createdBy`/`updatedBy` plus timestamps; the delivery ledger references `ruleId`/`actionId` for traceability.
|
|
|
|
---
|
|
|
|
## 2. Rule schema reference
|
|
|
|
| Field | Type | Notes |
|
|
|-------|------|-------|
|
|
| `ruleId` | string | Stable identifier; clients may provide UUID/slug. |
|
|
| `tenantId` | string | Must match the tenant header supplied when the rule is created. |
|
|
| `name` | string | Display label shown in UI and audits. |
|
|
| `description` | string? | Optional operator-facing note. |
|
|
| `enabled` | bool | Disabled rules remain stored but skipped during evaluation. |
|
|
| `labels` | map<string,string> | Sorted, trimmed key/value tags supporting filtering. |
|
|
| `metadata` | map<string,string> | Reserved for automation; stored verbatim (sorted). |
|
|
| `match` | [`NotifyRuleMatch`](#3-match-filters) | Declarative filters applied before actions execute. |
|
|
| `actions[]` | [`NotifyRuleAction`](#4-actions-throttles-and-digests) | Ordered set of channel dispatchers; minimum one. |
|
|
| `createdBy`/`createdAt` | string?, instant | Populated automatically when omitted. |
|
|
| `updatedBy`/`updatedAt` | string?, instant | Defaults to creation values when unspecified. |
|
|
| `schemaVersion` | string | Auto-upgraded during persistence; use for migrations. |
|
|
|
|
Rules are immutable snapshots; updates produce a full document write so workers observing change streams can refresh caches deterministically.
|
|
|
|
---
|
|
|
|
## 3. Match filters
|
|
|
|
`NotifyRuleMatch` narrows which events trigger the rule. All string collections are trimmed, deduplicated, and sorted to guarantee deterministic evaluation.
|
|
|
|
| Field | Type | Behaviour |
|
|
|-------|------|-----------|
|
|
| `eventKinds[]` | string | Lower-cased; supports any canonical Notify event (`scanner.report.ready`, `scheduler.rescan.delta`, `zastava.admission`, etc.). Empty list matches all kinds. |
|
|
| `namespaces[]` | string | Exact match against `event.scope.namespace`. Supports glob-style filters via upstream enrichment (planned). |
|
|
| `repositories[]` | string | Matches `event.scope.repo`. |
|
|
| `digests[]` | string | Lower-cased; matches `event.scope.digest`. |
|
|
| `labels[]` | string | Matches event attributes or delta labels (`kev`, `critical`, `license`, …). |
|
|
| `componentPurls[]` | string | Matches component identifiers inside the event payload when provided. |
|
|
| `minSeverity` | string? | Lower-cased severity gate (e.g., `medium`, `high`, `critical`). Evaluated on new findings inside event deltas; events lacking severity bypass this gate unless set. |
|
|
| `verdicts[]` | string | Accepts scan/report verdicts (`fail`, `warn`, `block`, `escalate`, `deny`). |
|
|
| `kevOnly` | bool? | When `true`, only KEV-tagged findings fire. |
|
|
| `vex` | object | Additional gating aligned with VEX consensus; see below. |
|
|
|
|
### 3.1 VEX gates
|
|
|
|
`NotifyRuleMatchVex` offers fine-grained control when VEX findings accompany events:
|
|
|
|
| Field | Default | Effect |
|
|
|-------|---------|--------|
|
|
| `includeAcceptedJustifications` | `true` | Include findings marked `not_affected`/`acceptable` in consensus. |
|
|
| `includeRejectedJustifications` | `false` | Surface findings the consensus rejected. |
|
|
| `includeUnknownJustifications` | `false` | Allow findings without explicit justification. |
|
|
| `justificationKinds[]` | `[]` | Optional allow-list of justification codes (e.g., `exploit_observed`, `component_not_present`). |
|
|
|
|
If the VEX block filters out every applicable finding, the rule is treated as a non-match and no actions run.
|
|
|
|
---
|
|
|
|
## 4. Actions, throttles, and digests
|
|
|
|
Each rule requires at least one action. Actions are deduplicated and sorted by `actionId`, so prefer deterministic identifiers.
|
|
|
|
| Field | Type | Notes |
|
|
|-------|------|-------|
|
|
| `actionId` | string | Stable identifier unique within the rule. |
|
|
| `channel` | string | Reference to a channel (`channelId`) configured in `/channels`. |
|
|
| `template` | string? | Template key to use for rendering; falls back to channel default when omitted. |
|
|
| `digest` | string? | Digest window key (`instant`, `5m`, `15m`, `1h`, `1d`). `instant` bypasses coalescing. |
|
|
| `throttle` | ISO8601 duration? | Optional throttle TTL (`PT300S`, `PT1H`). Prevents duplicate deliveries when the same idempotency hash appears before expiry. |
|
|
| `locale` | string? | BCP-47 tag (stored lower-case). Template lookup falls back to channel locale then `en-us`. |
|
|
| `enabled` | bool | Disabled actions skip rendering but remain stored. |
|
|
| `metadata` | map<string,string> | Connector-specific hints (priority, layout, etc.). |
|
|
|
|
### 4.1 Evaluation order
|
|
|
|
1. Verify channel exists and is enabled; disabled channels mark the delivery as `Dropped`.
|
|
2. Apply throttle idempotency key: `hash(ruleId|actionId|event.kind|scope.digest|delta.hash|dayBucket)`. Hits are logged as `Throttled`.
|
|
3. If the action defines a digest window other than `instant`, append the event to the open window and defer delivery until flush.
|
|
4. When delivery proceeds, the renderer resolves the template, locale, and metadata before invoking the connector.
|
|
|
|
---
|
|
|
|
## 5. Example rule payload
|
|
|
|
```json
|
|
{
|
|
"ruleId": "rule-critical-soc",
|
|
"tenantId": "tenant-dev",
|
|
"name": "Critical scanner verdicts",
|
|
"description": "Route KEV-tagged critical findings to SOC Slack with zero delay.",
|
|
"enabled": true,
|
|
"match": {
|
|
"eventKinds": ["scanner.report.ready"],
|
|
"labels": ["kev", "critical"],
|
|
"minSeverity": "critical",
|
|
"verdicts": ["fail", "block"],
|
|
"kevOnly": true
|
|
},
|
|
"actions": [
|
|
{
|
|
"actionId": "act-slack-critical",
|
|
"channel": "chn-slack-soc",
|
|
"template": "tmpl-critical",
|
|
"digest": "instant",
|
|
"throttle": "PT300S",
|
|
"locale": "en-us",
|
|
"metadata": {
|
|
"priority": "p1"
|
|
}
|
|
}
|
|
],
|
|
"labels": {
|
|
"owner": "soc"
|
|
},
|
|
"metadata": {
|
|
"revision": "12"
|
|
}
|
|
}
|
|
```
|
|
|
|
Dry-run calls (`POST /rules/{id}/test`) accept the same structure along with a sample Notify event payload to exercise match logic without invoking connectors.
|
|
|
|
---
|
|
|
|
## 6. Operational guidance
|
|
|
|
- Keep rule scopes narrow (namespace/repository) before relying on severity gates; this minimises noise and improves digest summarisation.
|
|
- Always configure a throttle window for instant actions to protect against repeated upstream retries.
|
|
- Use rule labels to organise dashboards and access control (e.g., `owner:soc`, `env:prod`).
|
|
- Prefer tenant-specific rule IDs so Offline Kit exports remain deterministic across environments.
|
|
- If a rule depends on derived metadata (e.g., policy verdict tags), list those dependencies in the rule description for audit readiness.
|
|
|
|
---
|
|
|
|
> **Imposed rule reminder:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|