Hand-authored platform/vuln-scanner (non-gating Flux tier, digest-pinned Trivy 0.76.1 and python 3.14.9-alpine, read-only RBAC, per-pod oauth2-proxy, internal-only cve.<domain>), five alerts with promtool tests, R-VULN-01..09 registry entries, and vuln-* L0 checks with mutation self-tests. src/ and the bot workflow are integrated separately. Co-Authored-By: Claude Opus 5.5 <[email protected]>
168 lines
6.9 KiB
YAML
168 lines
6.9 KiB
YAML
---
|
|
# promtool unit tests for platform/vuln-scanner/alerts.yaml (R-VULN-06).
|
|
#
|
|
# Found by platform/monitoring/tests/run.sh through the COMPANION convention. Run just this file with
|
|
# platform/monitoring/tests/run.sh platform/vuln-scanner/alerts.promtool-test.yaml
|
|
#
|
|
# Each rule is pinned both ways. VulnScanStale and VulnTriageBotSilent additionally pin the two cases
|
|
# the spec names by hand: the metric ABSENT must FIRE, and a metric that vanishes must RE-FIRE.
|
|
# conformance/tests/l0-static/vuln-alerts-unit-tested reads this file and fails if any of those cases
|
|
# is removed.
|
|
#
|
|
# Timestamps: promtool evaluates from t=0, so a timestamp series can equal its own evaluation time
|
|
# with `0+60x<N>` at a 1m interval (time() - value == 0: fresh).
|
|
rule_files:
|
|
- alerts.rules.yaml
|
|
evaluation_interval: 1m
|
|
|
|
tests:
|
|
- interval: 1m
|
|
name: healthy - fresh run, fresh database, heartbeat, zero backlog, nothing fires
|
|
input_series:
|
|
- series: 'vuln_scanner_last_complete_run_timestamp_seconds{namespace="vuln-scanner"}'
|
|
values: '0+60x60'
|
|
- series: 'vuln_scanner_database_updated_timestamp_seconds{namespace="vuln-scanner"}'
|
|
values: '0+60x60'
|
|
- series: 'vuln_scanner_triage_heartbeat_timestamp_seconds{namespace="vuln-scanner"}'
|
|
values: '0+60x60'
|
|
- series: 'vuln_scanner_failed_targets_over_24h{namespace="vuln-scanner"}'
|
|
values: '0x60'
|
|
- series: 'vuln_scanner_critical_fix_overdue{namespace="vuln-scanner"}'
|
|
values: '0x60'
|
|
promql_expr_test:
|
|
- expr: 'count(ALERTS{alertname=~"Vuln.*"})'
|
|
eval_time: 50m
|
|
exp_samples: []
|
|
|
|
- interval: 1m
|
|
name: scan stale - last complete run older than 26h fires after the for window
|
|
input_series:
|
|
- series: 'vuln_scanner_last_complete_run_timestamp_seconds{namespace="vuln-scanner"}'
|
|
values: '0x1600'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="VulnScanStale", alertstate="firing"}'
|
|
eval_time: 1559m
|
|
exp_samples: []
|
|
- expr: 'ALERTS{alertname="VulnScanStale", alertstate="firing"}'
|
|
eval_time: 1590m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="VulnScanStale", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: scan stale ABSENT - metric never existed, VulnScanStale still fires
|
|
input_series:
|
|
# A healthy, unrelated metric proves this is the absent() arm, not a broken test file.
|
|
- series: 'vuln_scanner_failed_targets_over_24h{namespace="vuln-scanner"}'
|
|
values: '0x60'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="VulnScanStale", alertstate="firing"}'
|
|
eval_time: 20m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="VulnScanStale", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: scan metric VANISHES - a scanner that dies re-fires instead of latching clear
|
|
input_series:
|
|
- series: 'vuln_scanner_last_complete_run_timestamp_seconds{namespace="vuln-scanner"}'
|
|
values: '0+60x20 stale'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="VulnScanStale", alertstate="firing"}'
|
|
eval_time: 18m
|
|
exp_samples: []
|
|
- expr: 'ALERTS{alertname="VulnScanStale", alertstate="firing"}'
|
|
eval_time: 40m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="VulnScanStale", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: database stale - older than 48h fires after the for window
|
|
input_series:
|
|
- series: 'vuln_scanner_database_updated_timestamp_seconds{namespace="vuln-scanner"}'
|
|
values: '0x2960'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="VulnDatabaseStale", alertstate="firing"}'
|
|
eval_time: 2870m
|
|
exp_samples: []
|
|
- expr: 'ALERTS{alertname="VulnDatabaseStale", alertstate="firing"}'
|
|
eval_time: 2900m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="VulnDatabaseStale", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: coverage gap - a failed target over 24h fires, zero is quiet
|
|
input_series:
|
|
- series: 'vuln_scanner_failed_targets_over_24h{namespace="vuln-scanner"}'
|
|
values: '0x20 3x40'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="VulnCoverageGap", alertstate="firing"}'
|
|
eval_time: 25m
|
|
exp_samples: []
|
|
- expr: 'ALERTS{alertname="VulnCoverageGap", alertstate="firing"}'
|
|
eval_time: 40m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="VulnCoverageGap", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: critical fix overdue - fires, one grouped alert instance
|
|
input_series:
|
|
- series: 'vuln_scanner_critical_fix_overdue{namespace="vuln-scanner"}'
|
|
values: '0x20 12x40'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="VulnCriticalFixAvailable", alertstate="firing"}'
|
|
eval_time: 25m
|
|
exp_samples: []
|
|
- expr: 'ALERTS{alertname="VulnCriticalFixAvailable", alertstate="firing"}'
|
|
eval_time: 40m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="VulnCriticalFixAvailable", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
- expr: 'count(ALERTS{alertname="VulnCriticalFixAvailable", alertstate="firing"})'
|
|
eval_time: 40m
|
|
exp_samples:
|
|
- labels: '{}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: triage bot silent - last heartbeat older than 26h fires after the for window
|
|
input_series:
|
|
- series: 'vuln_scanner_triage_heartbeat_timestamp_seconds{namespace="vuln-scanner"}'
|
|
values: '0x1600'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="VulnTriageBotSilent", alertstate="firing"}'
|
|
eval_time: 1559m
|
|
exp_samples: []
|
|
- expr: 'ALERTS{alertname="VulnTriageBotSilent", alertstate="firing"}'
|
|
eval_time: 1590m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="VulnTriageBotSilent", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: triage bot silent ABSENT - no heartbeat metric ever, VulnTriageBotSilent still fires
|
|
input_series:
|
|
- series: 'vuln_scanner_failed_targets_over_24h{namespace="vuln-scanner"}'
|
|
values: '0x60'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="VulnTriageBotSilent", alertstate="firing"}'
|
|
eval_time: 20m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="VulnTriageBotSilent", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: triage heartbeat VANISHES - re-fires instead of latching clear
|
|
input_series:
|
|
- series: 'vuln_scanner_triage_heartbeat_timestamp_seconds{namespace="vuln-scanner"}'
|
|
values: '0+60x20 stale'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="VulnTriageBotSilent", alertstate="firing"}'
|
|
eval_time: 40m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="VulnTriageBotSilent", alertstate="firing", severity="warning"}'
|
|
value: 1
|