--- # Alert rules for the vulnerability scanner (R-VULN-06, docs/17 "Alerting"). # # ONE ALERT, ONE GROUPED EMAIL PER CONDITION, never one per CVE. The scanner's metrics are aggregates # with no per-CVE, per-image or per-workload label; `max()` strips scrape labels, so each rule yields a # single alert instance, and Alertmanager's `group_by: [alertname, namespace]` route delivers one email. # # THE TWO RULES WHOSE ABSENT-METRIC CASE IS LOAD-BEARING: VulnScanStale and VulnTriageBotSilent. Their # metrics are absent until the first complete run / first bot heartbeat, and absent when the scanner # or the scrape is dead; both are `or absent(...)` so a scanner that has never run, or an unscraped # one, fires instead of reading as healthy. Pinned by alerts.promtool-test.yaml in both directions. # VulnCriticalFixAvailable is expected to fire on first deployment (the backlog made visible). # # Severity follows the platform-health convention: warning (a condition to act on, not a page). apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: vuln-scanner namespace: vuln-scanner labels: app.kubernetes.io/name: vuln-scanner spec: groups: - name: platform-health.vulnerability-scanner rules: - alert: VulnScanStale # No complete run within 26 hours, or the metric is absent. 15m rides out a pod restart. expr: >- (time() - max(vuln_scanner_last_complete_run_timestamp_seconds) > 93600) or absent(vuln_scanner_last_complete_run_timestamp_seconds) for: 15m labels: severity: warning annotations: summary: No complete vulnerability scan in over 26 hours description: >- The scanner has not completed a run within 26 hours, or no scan metric exists at all (the scanner or its scrape is down). Findings on the CVE page are ageing. runbook_url: "https://${BASE_DOMAIN}/runbooks/vuln-scanner" - alert: VulnDatabaseStale # Vulnerability database older than 48 hours (docs/17): the run is degraded, not a success. expr: time() - max(vuln_scanner_database_updated_timestamp_seconds) > 172800 for: 15m labels: severity: warning annotations: summary: Vulnerability database is older than 48 hours description: >- The Trivy vulnerability database has not been refreshed in over 48 hours, so scans evaluate images against stale advisories and the run reports as degraded. runbook_url: "https://${BASE_DOMAIN}/runbooks/vuln-scanner" - alert: VulnCoverageGap # A target has been `failed` for more than 24 hours. credential-required targets are not # `failed` and do not count, so a private registry cannot hold this alert firing. expr: max(vuln_scanner_failed_targets_over_24h) > 0 for: 15m labels: severity: warning annotations: summary: "{{ $value }} image(s) have failed scanning for over 24 hours" description: >- One or more running images have failed scanning for more than 24 hours, so what is reported for them is out of date. The CVE page lists each target and the reason. runbook_url: "https://${BASE_DOMAIN}/runbooks/vuln-scanner" - alert: VulnCriticalFixAvailable # An unaccepted CRITICAL with a fix, first seen over 72 hours ago. The grace period gives # Renovate's daily cycle time to act, so this marks what automation has not resolved. expr: max(vuln_scanner_critical_fix_overdue) > 0 for: 15m labels: severity: warning annotations: summary: "{{ $value }} CRITICAL finding(s) with a fix available for over 72 hours" description: >- One or more unaccepted CRITICAL vulnerabilities have had a fix available for more than 72 hours. The CVE page has the details; this message deliberately names none of them. runbook_url: "https://${BASE_DOMAIN}/runbooks/vuln-scanner" - alert: VulnTriageBotSilent # No bot heartbeat for 26 hours, or the metric is absent (the bot has never run, or the # scanner or its scrape is down). expr: >- (time() - max(vuln_scanner_triage_heartbeat_timestamp_seconds) > 93600) or absent(vuln_scanner_triage_heartbeat_timestamp_seconds) for: 15m labels: severity: warning annotations: summary: CVE triage bot has not reported in over 26 hours description: >- The scheduled CVE triage bot has not posted a heartbeat in over 26 hours (or never has), so remediation states are stale and no approvals are being requested. runbook_url: "https://${BASE_DOMAIN}/runbooks/vuln-scanner"