check-opencloud-security 1.2.2__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/CHANGELOG.md +25 -0
  2. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/PKG-INFO +94 -15
  3. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/README.md +93 -14
  4. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/check_opencloud_security.py +346 -53
  5. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/baseline.py +152 -1
  6. check_opencloud_security-1.3.0/opencloud_local_scan/prometheus.py +175 -0
  7. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/wizard.py +13 -0
  8. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/pyproject.toml +1 -1
  9. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_baseline.py +93 -0
  10. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_env_config.py +17 -0
  11. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_multi_host.py +82 -0
  12. check_opencloud_security-1.3.0/tests/test_prometheus.py +81 -0
  13. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_webhook.py +67 -1
  14. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/.gitignore +0 -0
  15. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/LICENSE +0 -0
  16. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/ansible/README.md +0 -0
  17. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/contrib/README.md +0 -0
  18. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/docs/README.md +0 -0
  19. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/README.md +0 -0
  20. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/__init__.py +0 -0
  21. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/cli.py +0 -0
  22. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/completion.py +0 -0
  23. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/config.py +0 -0
  24. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/data/release_schedule.json +0 -0
  25. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/data/vulnerabilities.json +0 -0
  26. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/factory.py +0 -0
  27. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/hardening.py +0 -0
  28. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/releases.py +0 -0
  29. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/scanner.py +0 -0
  30. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/secrets.py +0 -0
  31. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/selfupdate.py +0 -0
  32. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/service.py +0 -0
  33. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/versions.py +0 -0
  34. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/opencloud_local_scan/vulndb.py +0 -0
  35. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/secrets/README.md +0 -0
  36. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/__init__.py +0 -0
  37. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/conftest.py +0 -0
  38. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/fake_opencloud.py +0 -0
  39. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_completion.py +0 -0
  40. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_concurrency.py +0 -0
  41. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_config.py +0 -0
  42. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_e2e_cli.py +0 -0
  43. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_explain.py +0 -0
  44. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_local_scanner.py +0 -0
  45. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_perfdata.py +0 -0
  46. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_rating_thresholds.py +0 -0
  47. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_release_notes.py +0 -0
  48. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_release_schedule.py +0 -0
  49. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_releases.py +0 -0
  50. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_selfupdate.py +0 -0
  51. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_service.py +0 -0
  52. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_update_script.py +0 -0
  53. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_version.py +0 -0
  54. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_vulndb.py +0 -0
  55. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_waivers.py +0 -0
  56. {check_opencloud_security-1.2.2 → check_opencloud_security-1.3.0}/tests/test_wizard.py +0 -0
@@ -12,6 +12,31 @@ entry to `RELEASE.md` and uses it as the body of the GitHub release.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [1.3.0] - 2026-08-13
16
+
17
+ ### Added
18
+
19
+ - Itemized baseline diffs in text, Markdown, and Slack Block Kit JSON,
20
+ covering CVEs, hardening and check changes, rating/lifecycle trends, and
21
+ installed/update-version shifts. Webhooks now carry the structured diff.
22
+ - Native Prometheus text output and a lightweight `/metrics` exporter for
23
+ Kubernetes and cloud-native monitoring. The exporter refreshes scans on
24
+ demand with a configurable cache interval and requires no extra dependency.
25
+
26
+ ### Changed
27
+
28
+ - Multi-host checks now run one worker per target, up to the configurable
29
+ default ceiling of five. A single-host check remains single-threaded, and
30
+ result blocks and Nagios perfdata remain isolated and ordered by input host.
31
+
32
+ ## [1.2.3] - 2026-08-13
33
+
34
+ ### Security
35
+
36
+ - Block webhook notifications to private, loopback, and link-local addresses
37
+ by default to prevent server-side request forgery. Internal receivers require
38
+ the explicit `--allow-private-webhooks` / `COS_ALLOW_PRIVATE_WEBHOOKS` opt-out.
39
+
15
40
  ## [1.2.2] - 2026-08-13
16
41
 
17
42
  ### Documentation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: check-opencloud-security
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: Check the security level of your OpenCloud instance with a built-in scanner - no API access required
5
5
  Author: Massoud Ahmed
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -703,6 +703,7 @@ Description-Content-Type: text/markdown
703
703
  * [Command](#command)
704
704
  * [Options:](#options)
705
705
  * [Checking multiple hosts](#checking-multiple-hosts)
706
+ * [Prometheus & Kubernetes integration](#prometheus--kubernetes-integration)
706
707
  * [Environment variables](#environment-variables)
707
708
  * [The built-in scanner](#the-built-in-scanner)
708
709
  * [What the scanner checks](#what-the-scanner-checks)
@@ -1178,7 +1179,11 @@ check-opencloud-security --host <Hostname> --check-hardening
1178
1179
  | `--insecure` | Do not verify the instance's TLS certificate | *False* | `COS_INSECURE` |
1179
1180
  | `--no-extra-checks` | Only check product, version and security headers | *False* | `COS_NO_EXTRA_CHECKS` |
1180
1181
  | `--no-debug-ports` | Skip probing the OpenCloud debug ports | *False* | `COS_NO_DEBUG_PORTS` |
1181
- | `--concurrency` | Probes run in parallel; `1` means no multithreading | `1` | `COS_SCANNER_CONCURRENCY` |
1182
+ | `--concurrency` | Maximum parallel host workers; one is used per host up to this ceiling | `5` | `COS_CONCURRENCY` |
1183
+ | `--format` | One-shot output format: `nagios` or `prometheus` | `nagios` | `COS_FORMAT` |
1184
+ | `--prometheus-listen-port` | Serve native `/metrics` on this port until stopped | disabled | `COS_PROMETHEUS_LISTEN_PORT` |
1185
+ | `--prometheus-listen-addr` | Bind address for the native Prometheus exporter | `0.0.0.0` | `COS_PROMETHEUS_LISTEN_ADDR` |
1186
+ | `--scrape-interval` | Seconds to cache exporter scan results (`0` scans on every scrape) | `60` | `COS_SCRAPE_INTERVAL` |
1182
1187
  | `--ignore-hardening`| Hardening measure or check to accept, repeatable, comma-separated and wildcard capable | *None* | `COS_SCANNER_IGNORE_HARDENINGS` |
1183
1188
  | `--release-track` | Release track this instance follows: `rolling`, `production` or `lts` | inferred | `COS_SCANNER_RELEASE_TRACK` |
1184
1189
  | `--update-source` | Where the newest release comes from: `auto`, `feed`, `pinned`, `bundled`, `off` | `auto` | `COS_UPDATE_SOURCE` |
@@ -1189,11 +1194,13 @@ check-opencloud-security --host <Hostname> --check-hardening
1189
1194
  | `--update-warning` | Report WARNING when a newer release is available | *False* | `COS_UPDATE_WARNING` |
1190
1195
  | `--baseline` | File that remembers the findings of the last run, one entry per host | *None* | `COS_BASELINE` |
1191
1196
  | `--warn-on-new` | Only alert on findings that are new or worse than the baseline; needs `--baseline` | *False* | `COS_WARN_ON_NEW` |
1197
+ | `--diff-format` | Render baseline changes as `text`, `markdown`, or Slack Block Kit `slack`/`json` | `text` | `COS_DIFF_FORMAT` |
1192
1198
  | `--self-update-check` | Note when a newer version of the plugin is published on PyPI; never changes the exit code | *False* | `COS_SELF_UPDATE_CHECK` |
1193
1199
  | `--webhook-url` | Optional endpoint notified when the check reaches the configured state | *None* (disabled) | `COS_WEBHOOK_URL` |
1194
1200
  | `--webhook-on` | Lowest state that triggers the webhook (`critical`, `warning`, `unknown`, `always`) | `critical` | `COS_WEBHOOK_ON` |
1195
1201
  | `--webhook-header` | Extra header for the webhook request, repeatable | *None* | `COS_WEBHOOK_HEADERS` |
1196
1202
  | `--webhook-timeout` | HTTP timeout in seconds for the webhook call | `10` | `COS_WEBHOOK_TIMEOUT` |
1203
+ | `--allow-private-webhooks` | Permit webhooks to private, loopback, or link-local addresses | *False* | `COS_ALLOW_PRIVATE_WEBHOOKS` |
1197
1204
  | `--retries` | Retry attempts for transient network errors | `2` | `COS_RETRIES` |
1198
1205
  | `--backoff-factor` | Exponential backoff factor (seconds) between retries | `0.5` | `COS_BACKOFF_FACTOR` |
1199
1206
  | `--config` | Path to the configuration file (`.json` as JSON, else YAML) | auto-discovered | `COS_CONFIG_FILE` |
@@ -1215,9 +1222,14 @@ environment variables.
1215
1222
  check-opencloud-security --host opencloud1.example.com,opencloud2.example.com
1216
1223
  ```
1217
1224
 
1218
- Hosts are processed one by one. The output starts with a one-line summary
1225
+ Hosts run concurrently: the plugin creates one worker per host, up to the
1226
+ default ceiling of five. A single-host check remains strictly single-threaded,
1227
+ with no host worker pool. Set `--concurrency` or `COS_CONCURRENCY` to lower or
1228
+ raise the ceiling (up to 32), for example `--concurrency 2` for at most two
1229
+ hosts at a time. Each worker keeps its result and Nagios perfdata separate;
1230
+ the output starts with a one-line summary
1219
1231
  (e.g. `Checked 2 host(s): overall CRITICAL (1 CRITICAL, 1 OK)`), followed by
1220
- one result block per host. The plugin exits with the worst status found
1232
+ one result block per host in the same order as the input. The plugin exits with the worst status found
1221
1233
  across all hosts, using the usual Nagios/Icinga priority: `CRITICAL` >
1222
1234
  `WARNING` > `UNKNOWN` > `OK`. A single host still produces the original,
1223
1235
  single-block output and exit code, so existing single-host setups are
@@ -1231,6 +1243,50 @@ entry may be a hostname, an IPv4 address, a bracketed IPv6 address or a full
1231
1243
  URL, with or without a port:
1232
1244
  `--host 10.0.0.5:9200,[2001:db8::1],https://cloud.example.com/`.
1233
1245
 
1246
+ # Prometheus & Kubernetes integration
1247
+
1248
+ Use `--format=prometheus` to produce a one-shot Prometheus text payload:
1249
+
1250
+ ```shell
1251
+ check-opencloud-security --host opencloud.example.com --format=prometheus
1252
+ ```
1253
+
1254
+ For pull-based monitoring, run the built-in exporter. It serves `/metrics`,
1255
+ refreshing each configured target on the first scrape and then at the
1256
+ `--scrape-interval` (60 seconds by default). Set it to `0` only when every
1257
+ scrape should trigger a scan:
1258
+
1259
+ ```shell
1260
+ check-opencloud-security --host opencloud.example.com \
1261
+ --prometheus-listen-port 9102 --prometheus-listen-addr 0.0.0.0
1262
+ ```
1263
+
1264
+ The Docker image needs no extra package or sidecar:
1265
+
1266
+ ```shell
1267
+ docker run --rm -p 9102:9102 check-opencloud-security \
1268
+ --host opencloud.example.com --prometheus-listen-port 9102
1269
+ ```
1270
+
1271
+ In Kubernetes, run the same command in a Deployment, expose port `9102`, and
1272
+ point a ServiceMonitor or scrape configuration at `/metrics`. The exporter
1273
+ publishes `opencloud_security_rating_score`,
1274
+ `opencloud_security_vulnerabilities_total`,
1275
+ `opencloud_security_hardenings_missing_total`,
1276
+ `opencloud_security_failed_extra_checks_total`,
1277
+ `opencloud_security_support_days_remaining`,
1278
+ `opencloud_security_update_available`,
1279
+ `opencloud_security_scan_duration_seconds`, and
1280
+ `opencloud_security_scrape_success`. The `host` label identifies the configured
1281
+ target; rating also carries `domain`, `product`, and `version`.
1282
+
1283
+ For Grafana, show `opencloud_security_rating_score` in a stat panel with
1284
+ thresholds at `3` (warning) and `1` (critical), graph
1285
+ `opencloud_security_support_days_remaining`, and alert when
1286
+ `opencloud_security_scrape_success == 0`. See the
1287
+ [Prometheus and Grafana guide](docs/prometheus.md) for alerting and legacy
1288
+ textfile/Pushgateway patterns.
1289
+
1234
1290
  # Environment variables
1235
1291
  Every option has a `COS_`-prefixed environment variable equivalent (see the
1236
1292
  table above). This is especially useful for Docker, systemd, and cron, where
@@ -1378,17 +1434,13 @@ scanner:
1378
1434
  ### Speeding the scan up
1379
1435
 
1380
1436
  A scan spends nearly all of its time waiting for the instance to answer: around
1381
- twenty HTTP requests and five TCP connects, one after the other. `--concurrency`
1382
- runs them in parallel instead:
1383
-
1384
- ```bash
1385
- check-opencloud-security --host opencloud.example.com --concurrency 8
1386
- ```
1387
-
1388
- The default is `1`, which means no multithreading at all - a plain sequential
1389
- scan, exactly as before. Raising it shortens a run considerably, at the price of
1390
- a burst of parallel requests against the instance, and is most noticeable when
1391
- debug-port probing runs into a firewall that swallows the connections.
1437
+ twenty HTTP requests and five TCP connects, one after the other.
1438
+ `scanner.concurrency` runs those probes in parallel for a single-host scan;
1439
+ raising it shortens a run considerably, at the price of a burst of parallel
1440
+ requests against the instance, and is most noticeable when debug-port probing
1441
+ runs into a firewall that swallows the connections. `--concurrency` instead
1442
+ controls the outer host-worker ceiling described in
1443
+ [Checking multiple hosts](#checking-multiple-hosts).
1392
1444
 
1393
1445
  The setting changes only the timing, never the verdict: the result document
1394
1446
  lists the same findings in the same order whatever the value is. Values above
@@ -1979,6 +2031,10 @@ check-opencloud-security --host opencloud.example.com \
1979
2031
  environment variable: `COS_WEBHOOK_HEADERS="X-Auth-Token: abc; X-Env: prod"`.
1980
2032
  - `--webhook-timeout` / `COS_WEBHOOK_TIMEOUT` (default `10`) limits the
1981
2033
  webhook call; it is independent of the scan `--timeout`.
2034
+ - Webhook destinations that resolve to private, loopback, or link-local
2035
+ addresses are blocked to prevent server-side request forgery. Set
2036
+ `--allow-private-webhooks` or `COS_ALLOW_PRIVATE_WEBHOOKS=true` only for an
2037
+ intentional internal receiver.
1982
2038
 
1983
2039
  Delivery reuses `--retries` / `--backoff-factor`. **A failing webhook never
1984
2040
  changes the check result** - the plugin appends `Webhook delivery failed` to
@@ -2144,6 +2200,29 @@ Baseline: No new findings since 2026-01-14T09:00:00+00:00 (1 known issue(s) unch
2144
2200
  Suppressed by --warn-on-new: this run would otherwise be WARNING (WARNING: 1 hardening measure(s) missing, but no known vulnerabilities.)
2145
2201
  ```
2146
2202
 
2203
+ Every comparison also lists added and resolved CVEs, hardening and additional
2204
+ check changes, rating/EOL/support-horizon changes, and installed or target
2205
+ version shifts. `text` is the default for logs. For a GitHub Actions step
2206
+ summary or pull-request comment, select Markdown:
2207
+
2208
+ ```shell
2209
+ check-opencloud-security -H opencloud.example.com \
2210
+ --baseline /var/lib/check_opencloud/baseline.json \
2211
+ --diff-format markdown >> "$GITHUB_STEP_SUMMARY"
2212
+ ```
2213
+
2214
+ Use `--diff-format slack` (or `json`) for Slack Block Kit JSON. When a webhook
2215
+ is configured, every baseline comparison is included as `baseline_diff`; Slack
2216
+ format additionally puts the blocks and color banner at the top level for
2217
+ incoming webhooks:
2218
+
2219
+ ```shell
2220
+ check-opencloud-security -H opencloud.example.com \
2221
+ --baseline /var/lib/check_opencloud/baseline.json \
2222
+ --diff-format slack --webhook-url 'https://hooks.slack.com/services/<token>' \
2223
+ --webhook-on always
2224
+ ```
2225
+
2147
2226
  What counts as a regression, and therefore still alerts:
2148
2227
 
2149
2228
  - a finding that was not there last time - a new advisory, a hardening measure
@@ -14,6 +14,7 @@
14
14
  * [Command](#command)
15
15
  * [Options:](#options)
16
16
  * [Checking multiple hosts](#checking-multiple-hosts)
17
+ * [Prometheus & Kubernetes integration](#prometheus--kubernetes-integration)
17
18
  * [Environment variables](#environment-variables)
18
19
  * [The built-in scanner](#the-built-in-scanner)
19
20
  * [What the scanner checks](#what-the-scanner-checks)
@@ -489,7 +490,11 @@ check-opencloud-security --host <Hostname> --check-hardening
489
490
  | `--insecure` | Do not verify the instance's TLS certificate | *False* | `COS_INSECURE` |
490
491
  | `--no-extra-checks` | Only check product, version and security headers | *False* | `COS_NO_EXTRA_CHECKS` |
491
492
  | `--no-debug-ports` | Skip probing the OpenCloud debug ports | *False* | `COS_NO_DEBUG_PORTS` |
492
- | `--concurrency` | Probes run in parallel; `1` means no multithreading | `1` | `COS_SCANNER_CONCURRENCY` |
493
+ | `--concurrency` | Maximum parallel host workers; one is used per host up to this ceiling | `5` | `COS_CONCURRENCY` |
494
+ | `--format` | One-shot output format: `nagios` or `prometheus` | `nagios` | `COS_FORMAT` |
495
+ | `--prometheus-listen-port` | Serve native `/metrics` on this port until stopped | disabled | `COS_PROMETHEUS_LISTEN_PORT` |
496
+ | `--prometheus-listen-addr` | Bind address for the native Prometheus exporter | `0.0.0.0` | `COS_PROMETHEUS_LISTEN_ADDR` |
497
+ | `--scrape-interval` | Seconds to cache exporter scan results (`0` scans on every scrape) | `60` | `COS_SCRAPE_INTERVAL` |
493
498
  | `--ignore-hardening`| Hardening measure or check to accept, repeatable, comma-separated and wildcard capable | *None* | `COS_SCANNER_IGNORE_HARDENINGS` |
494
499
  | `--release-track` | Release track this instance follows: `rolling`, `production` or `lts` | inferred | `COS_SCANNER_RELEASE_TRACK` |
495
500
  | `--update-source` | Where the newest release comes from: `auto`, `feed`, `pinned`, `bundled`, `off` | `auto` | `COS_UPDATE_SOURCE` |
@@ -500,11 +505,13 @@ check-opencloud-security --host <Hostname> --check-hardening
500
505
  | `--update-warning` | Report WARNING when a newer release is available | *False* | `COS_UPDATE_WARNING` |
501
506
  | `--baseline` | File that remembers the findings of the last run, one entry per host | *None* | `COS_BASELINE` |
502
507
  | `--warn-on-new` | Only alert on findings that are new or worse than the baseline; needs `--baseline` | *False* | `COS_WARN_ON_NEW` |
508
+ | `--diff-format` | Render baseline changes as `text`, `markdown`, or Slack Block Kit `slack`/`json` | `text` | `COS_DIFF_FORMAT` |
503
509
  | `--self-update-check` | Note when a newer version of the plugin is published on PyPI; never changes the exit code | *False* | `COS_SELF_UPDATE_CHECK` |
504
510
  | `--webhook-url` | Optional endpoint notified when the check reaches the configured state | *None* (disabled) | `COS_WEBHOOK_URL` |
505
511
  | `--webhook-on` | Lowest state that triggers the webhook (`critical`, `warning`, `unknown`, `always`) | `critical` | `COS_WEBHOOK_ON` |
506
512
  | `--webhook-header` | Extra header for the webhook request, repeatable | *None* | `COS_WEBHOOK_HEADERS` |
507
513
  | `--webhook-timeout` | HTTP timeout in seconds for the webhook call | `10` | `COS_WEBHOOK_TIMEOUT` |
514
+ | `--allow-private-webhooks` | Permit webhooks to private, loopback, or link-local addresses | *False* | `COS_ALLOW_PRIVATE_WEBHOOKS` |
508
515
  | `--retries` | Retry attempts for transient network errors | `2` | `COS_RETRIES` |
509
516
  | `--backoff-factor` | Exponential backoff factor (seconds) between retries | `0.5` | `COS_BACKOFF_FACTOR` |
510
517
  | `--config` | Path to the configuration file (`.json` as JSON, else YAML) | auto-discovered | `COS_CONFIG_FILE` |
@@ -526,9 +533,14 @@ environment variables.
526
533
  check-opencloud-security --host opencloud1.example.com,opencloud2.example.com
527
534
  ```
528
535
 
529
- Hosts are processed one by one. The output starts with a one-line summary
536
+ Hosts run concurrently: the plugin creates one worker per host, up to the
537
+ default ceiling of five. A single-host check remains strictly single-threaded,
538
+ with no host worker pool. Set `--concurrency` or `COS_CONCURRENCY` to lower or
539
+ raise the ceiling (up to 32), for example `--concurrency 2` for at most two
540
+ hosts at a time. Each worker keeps its result and Nagios perfdata separate;
541
+ the output starts with a one-line summary
530
542
  (e.g. `Checked 2 host(s): overall CRITICAL (1 CRITICAL, 1 OK)`), followed by
531
- one result block per host. The plugin exits with the worst status found
543
+ one result block per host in the same order as the input. The plugin exits with the worst status found
532
544
  across all hosts, using the usual Nagios/Icinga priority: `CRITICAL` >
533
545
  `WARNING` > `UNKNOWN` > `OK`. A single host still produces the original,
534
546
  single-block output and exit code, so existing single-host setups are
@@ -542,6 +554,50 @@ entry may be a hostname, an IPv4 address, a bracketed IPv6 address or a full
542
554
  URL, with or without a port:
543
555
  `--host 10.0.0.5:9200,[2001:db8::1],https://cloud.example.com/`.
544
556
 
557
+ # Prometheus & Kubernetes integration
558
+
559
+ Use `--format=prometheus` to produce a one-shot Prometheus text payload:
560
+
561
+ ```shell
562
+ check-opencloud-security --host opencloud.example.com --format=prometheus
563
+ ```
564
+
565
+ For pull-based monitoring, run the built-in exporter. It serves `/metrics`,
566
+ refreshing each configured target on the first scrape and then at the
567
+ `--scrape-interval` (60 seconds by default). Set it to `0` only when every
568
+ scrape should trigger a scan:
569
+
570
+ ```shell
571
+ check-opencloud-security --host opencloud.example.com \
572
+ --prometheus-listen-port 9102 --prometheus-listen-addr 0.0.0.0
573
+ ```
574
+
575
+ The Docker image needs no extra package or sidecar:
576
+
577
+ ```shell
578
+ docker run --rm -p 9102:9102 check-opencloud-security \
579
+ --host opencloud.example.com --prometheus-listen-port 9102
580
+ ```
581
+
582
+ In Kubernetes, run the same command in a Deployment, expose port `9102`, and
583
+ point a ServiceMonitor or scrape configuration at `/metrics`. The exporter
584
+ publishes `opencloud_security_rating_score`,
585
+ `opencloud_security_vulnerabilities_total`,
586
+ `opencloud_security_hardenings_missing_total`,
587
+ `opencloud_security_failed_extra_checks_total`,
588
+ `opencloud_security_support_days_remaining`,
589
+ `opencloud_security_update_available`,
590
+ `opencloud_security_scan_duration_seconds`, and
591
+ `opencloud_security_scrape_success`. The `host` label identifies the configured
592
+ target; rating also carries `domain`, `product`, and `version`.
593
+
594
+ For Grafana, show `opencloud_security_rating_score` in a stat panel with
595
+ thresholds at `3` (warning) and `1` (critical), graph
596
+ `opencloud_security_support_days_remaining`, and alert when
597
+ `opencloud_security_scrape_success == 0`. See the
598
+ [Prometheus and Grafana guide](docs/prometheus.md) for alerting and legacy
599
+ textfile/Pushgateway patterns.
600
+
545
601
  # Environment variables
546
602
  Every option has a `COS_`-prefixed environment variable equivalent (see the
547
603
  table above). This is especially useful for Docker, systemd, and cron, where
@@ -689,17 +745,13 @@ scanner:
689
745
  ### Speeding the scan up
690
746
 
691
747
  A scan spends nearly all of its time waiting for the instance to answer: around
692
- twenty HTTP requests and five TCP connects, one after the other. `--concurrency`
693
- runs them in parallel instead:
694
-
695
- ```bash
696
- check-opencloud-security --host opencloud.example.com --concurrency 8
697
- ```
698
-
699
- The default is `1`, which means no multithreading at all - a plain sequential
700
- scan, exactly as before. Raising it shortens a run considerably, at the price of
701
- a burst of parallel requests against the instance, and is most noticeable when
702
- debug-port probing runs into a firewall that swallows the connections.
748
+ twenty HTTP requests and five TCP connects, one after the other.
749
+ `scanner.concurrency` runs those probes in parallel for a single-host scan;
750
+ raising it shortens a run considerably, at the price of a burst of parallel
751
+ requests against the instance, and is most noticeable when debug-port probing
752
+ runs into a firewall that swallows the connections. `--concurrency` instead
753
+ controls the outer host-worker ceiling described in
754
+ [Checking multiple hosts](#checking-multiple-hosts).
703
755
 
704
756
  The setting changes only the timing, never the verdict: the result document
705
757
  lists the same findings in the same order whatever the value is. Values above
@@ -1290,6 +1342,10 @@ check-opencloud-security --host opencloud.example.com \
1290
1342
  environment variable: `COS_WEBHOOK_HEADERS="X-Auth-Token: abc; X-Env: prod"`.
1291
1343
  - `--webhook-timeout` / `COS_WEBHOOK_TIMEOUT` (default `10`) limits the
1292
1344
  webhook call; it is independent of the scan `--timeout`.
1345
+ - Webhook destinations that resolve to private, loopback, or link-local
1346
+ addresses are blocked to prevent server-side request forgery. Set
1347
+ `--allow-private-webhooks` or `COS_ALLOW_PRIVATE_WEBHOOKS=true` only for an
1348
+ intentional internal receiver.
1293
1349
 
1294
1350
  Delivery reuses `--retries` / `--backoff-factor`. **A failing webhook never
1295
1351
  changes the check result** - the plugin appends `Webhook delivery failed` to
@@ -1455,6 +1511,29 @@ Baseline: No new findings since 2026-01-14T09:00:00+00:00 (1 known issue(s) unch
1455
1511
  Suppressed by --warn-on-new: this run would otherwise be WARNING (WARNING: 1 hardening measure(s) missing, but no known vulnerabilities.)
1456
1512
  ```
1457
1513
 
1514
+ Every comparison also lists added and resolved CVEs, hardening and additional
1515
+ check changes, rating/EOL/support-horizon changes, and installed or target
1516
+ version shifts. `text` is the default for logs. For a GitHub Actions step
1517
+ summary or pull-request comment, select Markdown:
1518
+
1519
+ ```shell
1520
+ check-opencloud-security -H opencloud.example.com \
1521
+ --baseline /var/lib/check_opencloud/baseline.json \
1522
+ --diff-format markdown >> "$GITHUB_STEP_SUMMARY"
1523
+ ```
1524
+
1525
+ Use `--diff-format slack` (or `json`) for Slack Block Kit JSON. When a webhook
1526
+ is configured, every baseline comparison is included as `baseline_diff`; Slack
1527
+ format additionally puts the blocks and color banner at the top level for
1528
+ incoming webhooks:
1529
+
1530
+ ```shell
1531
+ check-opencloud-security -H opencloud.example.com \
1532
+ --baseline /var/lib/check_opencloud/baseline.json \
1533
+ --diff-format slack --webhook-url 'https://hooks.slack.com/services/<token>' \
1534
+ --webhook-on always
1535
+ ```
1536
+
1458
1537
  What counts as a regression, and therefore still alerts:
1459
1538
 
1460
1539
  - a finding that was not there last time - a new advisory, a hardening measure