kingfisher/docs/ALERTS.md

149 lines
5.4 KiB
Markdown

# Alert Webhooks
Kingfisher can POST a scan summary (and optionally per-finding details) to one
or more webhooks when a scan completes — Slack, Microsoft Teams, Discord,
Mattermost, Google Chat, or any HTTPS endpoint that accepts a JSON POST.
Alerting is **best-effort**. A bad webhook produces a `WARN` line on stderr and
*never* changes the scan exit code; this avoids breaking CI when paging
infrastructure is having a bad day.
## Quick start
```bash
# Slack incoming webhook (format inferred from the URL host).
kingfisher scan ./repo \
--alert-webhook "$SLACK_SECURITY_WEBHOOK"
# Teams + a generic webhook in one run.
kingfisher scan ./repo \
--alert-webhook "$TEAMS_WEBHOOK" \
--alert-webhook "https://siem.example.com/ingest" \
--alert-format generic
# Discord webhook (auto-detected from discord.com).
kingfisher scan ./repo --alert-webhook "$DISCORD_SECURITY_WEBHOOK"
# Mattermost (self-hosted — format must be specified explicitly).
kingfisher scan ./repo \
--alert-webhook "https://mattermost.example.com/hooks/abc123" \
--alert-format mattermost
```
The format is inferred from the URL host:
| Host pattern | Inferred format |
|---------------------------------------|-----------------|
| `*.slack.com` | `slack` |
| `*.office.com` / `webhook.office.*` | `teams` |
| `discord.com` / `discordapp.com` | `discord` |
| `chat.googleapis.com` | `googlechat` |
| anything else | `generic` |
Set `--alert-format` to override. Mattermost has no canonical hostname (it is
always self-hosted), so it is **never** inferred — pass
`--alert-format mattermost` whenever you target a Mattermost server.
## Flags
| Flag | Default | Notes |
|------|---------|-------|
| `--alert-webhook URL` | *(none, repeatable)* | Destination URL; pass once per webhook. |
| `--alert-format slack\|teams\|generic\|discord\|mattermost\|googlechat` | inferred | Payload shape. |
| `--alert-on findings\|always` | `findings` | `always` posts even on a clean run. |
| `--alert-min-confidence low\|medium\|high` | `medium` | Findings below this are dropped from the payload. |
| `--alert-include-secret` | off | Include the (truncated to ~32 chars) secret value in the payload. |
Webhook URLs are sensitive: the host/path/query are redacted in logs. Pass them
via environment variables (`$SLACK_SECURITY_WEBHOOK`) or CI secrets, never
inline in committed files.
## Payload shapes
### Slack (Block Kit)
A header line, a "Top rules" section, an optional findings block (capped at 10
entries), and a context line with the Kingfisher version. Theme colour cues are
applied via the message structure itself.
### Microsoft Teams (MessageCard)
A coloured card — green if clean, amber if findings without active validation,
red if any active. Facts list active/inactive/unknown counts and the top rules.
### Generic JSON
```json
{
"schema_version": "1",
"kingfisher_version": "1.99.0",
"summary": {
"total": 3,
"active": 1,
"inactive": 1,
"unknown": 1,
"by_rule": [{"rule_id": "kingfisher.aws.1", "count": 2}],
"target": "./repo"
},
"findings": [ /* array of FindingReporterRecord, capped at 200 */ ],
"findings_omitted": 0
}
```
Findings are the same shape as `kingfisher scan --format json` produces, so
existing JSON consumers work unchanged.
### Discord (Embed)
A single embed with a color-coded sidebar — red on any active credential,
amber when findings exist but none are verified active, green on a clean run.
Inline `Active`/`Inactive`/`Unknown` fields, a `Top rules` field, the
per-finding detail in the embed `description` (capped at 10 entries), and a
footer with the Kingfisher version.
### Mattermost (Slack-compatible attachments)
Renders as a single attachment with the same red/amber/green sidebar (via the
legacy Slack `attachments[].color` field). Mattermost ≥ 5.x renders this
identically; we deliberately use legacy attachments instead of Block Kit
because Block Kit support in Mattermost is partial. Findings are listed in
the attachment `text` body, capped at 10 entries.
### Google Chat (cardsV2)
A modern `cardsV2` card with a "Summary" section (`decoratedText` widgets for
active/inactive/unknown counts and a top-rules paragraph) and a "Findings"
section (capped at 10 entries). Google Chat does not expose a card-color knob
in its public webhook API, so severity is conveyed textually — the title is
prefixed with 🚨 when any active credential is detected.
## Configuring via `kingfisher.yaml`
CLI flags and config-file webhooks are concatenated. Per-webhook overrides live
in the config so you can mix one Slack channel for active findings with a
broader Teams channel that paged on every run:
```yaml
alerts:
webhooks:
- url: https://hooks.slack.com/services/T0/B0/AAA
format: slack
on: findings
min_confidence: high
- url: https://outlook.office.com/webhook/XXX
format: teams
on: always
min_confidence: medium
include_secret: false
- url: https://discord.com/api/webhooks/123/abcdef
format: discord
on: findings
- url: https://mattermost.example.com/hooks/xxx
format: mattermost # required — never auto-inferred
on: findings
- url: https://chat.googleapis.com/v1/spaces/AAA/messages?key=k&token=t
format: googlechat
on: always
```
See [`docs/CONFIG.md`](CONFIG.md) for the full config schema.