Compare commits

...
9 Commits
Author SHA1 Message Date
k.renner 0001f2f0c2 Merge pull request 'fix(crowdsec): never ban successful UI bursts on http-crawl-non_statics' (#1478) from worktree-crowdsec-ma-imageproxy-whitelist into main
Reviewed-on: #1478
2026-10-27 13:26:45 +00:00
claude-botandClaude Opus 5.5 e855921b39 fix(crowdsec): drop crawl-non_statics alerts for successful bursts
ci/woodpecker/pr/ci-manifests-test Pipeline is running
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
Replaces the per-service Music Assistant/Immich parser whitelists with
one postoverflow whitelist: http-crawl-non_statics overflows are dropped
unless at least half of the requests in the alert are 4xx.

Every self-ban in the alert history (MA 350/368/477, Immich 149/150) was
a burst of 200s from a signed-in UI. Every scanner ban on this scenario
was 404s (/.env, /.git/...). The per-service rules only covered apps
that had already failed and muted all scenarios on their paths; this
covers any image-heavy UI and touches only this scenario.

Verified in a crowdsecurity/crowdsec:v1.8.1 replay: a 60x200 burst is
whitelisted, a 60x404 burst with distinct paths still overflows and is
not whitelisted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:23:43 +01:00
k.renner ec5b7314d6 Merge pull request 'fix(ansible): no from= on the CI key for internet hosts' (#1479) from ssh-ci-key-from-per-host into main
ci/woodpecker/push/deploy-netbird-proxy Pipeline was successful
ci/woodpecker/push/deploy-ssh-ca Pipeline was successful
Reviewed-on: #1479
2026-10-27 13:20:28 +00:00
claude-botandClaude Opus 5.5 d46d6fde93 fix(ansible): no from= on the CI key for internet hosts
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
#1477 locked CI out of proxy-1: the CI key got from= the pod network,
but CI reaches the internet hosts NATed from the home WAN address
(proxy-2 journal: root logins from 84.171.37.13). The from= came from
plays/configure-ssh-keys.yml, which no pipeline ever ran, so it was
never in effect on those hosts. LAN hosts do see the pod address
(edge-1 journal: 10.244.2.x).

ssh_ci_key_from is now per host: the pod CIDRs by default, empty for
[netbird_proxy] and netbird.renner.uno. The owner already removed the
from= on proxy-1 by hand; this keeps the next run from restoring it.
Restricting SSH on internet hosts to the NetBird mesh is the follow-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:15:01 +01:00
claude-botandClaude Opus 5.5 16ca2f69b7 fix(crowdsec): whitelist Immich timeline/asset loads
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Scrolling the Immich timeline fetches a /api/timeline/bucket and then
every /api/assets/<uuid> in it; 46-54 requests in 3-9s fired
crowdsecurity/http-crawl-non_statics against a residential Telekom IPv6
(alerts 149/150, 2026-10-23).

Same parser-stage pattern as the Music Assistant whitelist, scoped to
the Immich Traefik router and those two path prefixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:55:40 +01:00
claude-botandClaude Opus 5.5 cb4cabeca7 fix(crowdsec): whitelist Music Assistant imageproxy cover-art loads
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
The MA UI loads every cover through /imageproxy/<hash>?size=512 in one
burst; a library view (47 requests in 3s) fired
crowdsecurity/http-crawl-non_statics and banned a residential Telekom
IPv6 (alert 477, 2026-10-27).

Parser-stage whitelist scoped to the MA Traefik router and the
/imageproxy/ path. The Traefik parser sets no target_fqdn, so the
router name is used as host scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:54:34 +01:00
k.renner 2f63d2d426 Merge pull request 'feat(ansible): proxy-1 accepts only break-glass and CI keys (SSH CA phase 4)' (#1477) from ssh-keys-managed-proxy-1 into main
ci/woodpecker/push/deploy-ssh-ca Pipeline was successful
Reviewed-on: #1477
2026-10-27 12:14:08 +00:00
claude-botandClaude Opus 5.5 04b704fdbd feat(ansible): proxy-1 accepts only break-glass and CI keys
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
First host in [ssh_keys_managed]. root on proxy-1 currently has
id_ecdsa (krenner@krenner-XPS-13-9310), woodpecker-ci@homelab
(SHA256:DQWPS...) and ansible-runner@ci-new (dead since #184). After
this run: breakglass-neo-6532348 and the same CI key with its from=
pod-network restriction; people log in with CA certificates. proxy-2
is the twin, so the public edge survives a mistake here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:49:46 +01:00
k.renner 63df267a2f Merge pull request 'feat(ansible): one admin key list; report authorized_keys (SSH CA phase 4, step 1)' (#1476) from ssh-keys-managed into main
ci/woodpecker/push/deploy-infrastructure Pipeline was successful
ci/woodpecker/push/deploy-ssh-ca Pipeline was successful
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
Reviewed-on: #1476
2026-10-27 11:38:59 +00:00
7 changed files with 38 additions and 3 deletions
+2
View File
@@ -7,6 +7,8 @@ when:
- "ansible/plays/configure-ssh-ca.yml"
- "ansible/inventory/pve_hosts.yml"
- "ansible/inventory/group_vars/all/**"
- "ansible/inventory/group_vars/netbird_proxy/vars.yml"
- "ansible/inventory/host_vars/netbird.renner.uno.yml"
- ".woodpecker/deploy-ssh-ca.yml"
- event: deployment
evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "deploy-ssh-ca"'
+7 -2
View File
@@ -10,11 +10,16 @@ ansible_python_interpreter: /usr/bin/python3
# certificates; these two are for when that path is unavailable.
# breakglass-neo: PIV key (slot 9a, PIN) on the offline YubiKey NEO.
# ssh -I /usr/lib/opensc-pkcs11.so root@<host>
# woodpecker-ci: the CI deploy key, only from the pod network.
# woodpecker-ci: the CI deploy key, from ssh_ci_key_from where set.
ssh_admin_authorized_keys:
- key: "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKvSl9LUP8u3S2Sq5YTWpW0Jxbue8cJhD7/LMFQbEsa3xo145HVR7pBzRxW71MUy+jgrgmpSlDhIb606Hswhvoo= breakglass-neo-6532348"
- key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICyNnX1I4W9jyDvCJ1DJauHSmbo1ZXSyBxVhSAwMWgen woodpecker-ci@homelab"
options: 'from="{{ ci_ssh_from_cidrs }}"'
options: "{{ ('from=\"' ~ ssh_ci_key_from ~ '\"') if ssh_ci_key_from else '' }}"
# Source the CI key is accepted from. LAN hosts see the pod address; hosts on
# the internet see the home WAN address (NAT, dynamic) and set this empty in
# their group/host vars.
ssh_ci_key_from: "{{ ci_ssh_from_cidrs }}"
# Network-of-origin restriction for the woodpecker-ci@homelab CI key
# (issue #184). Used as the authorized_keys `from=` option so the CI key only
@@ -0,0 +1,4 @@
---
# CI reaches these internet hosts NATed from the home WAN address, never from
# the pod network, so the CI key carries no from= restriction here.
ssh_ci_key_from: ""
@@ -2,3 +2,7 @@
# NetBird server - VPS not managed by Proxmox
# Enable Docker monitoring (cAdvisor) for container metrics
docker_monitoring_enabled: true
# CI reaches this internet host NATed from the home WAN address, never from
# the pod network, so the CI key carries no from= restriction here.
ssh_ci_key_from: ""
+1
View File
@@ -83,3 +83,4 @@ edge-1.iot.renner.uno
# + CI); everything else needs a CA certificate. Members must be in [ssh_ca].
# Added one host at a time after the [ssh_ca] report was reviewed.
[ssh_keys_managed]
proxy-1.renner.uno
@@ -14,7 +14,7 @@
user: "{{ item.item }}"
key: |
{% for k in ssh_admin_authorized_keys %}
{{ (k.options ~ ' ') if k.options is defined else '' }}{{ k.key }}
{{ (k.options ~ ' ') if (k.options | default('')) else '' }}{{ k.key }}
{% endfor %}
exclusive: true
loop: "{{ ssh_ca_admin_lookup.results }}"
@@ -80,6 +80,25 @@ spec:
# community blocklists.
- 162.55.50.97/32
- 2a01:4f8:c2c:4579::1/128
postoverflows:
s01-whitelist:
# http-crawl-non_statics counts requests regardless of outcome, so a
# signed-in user of any image-heavy UI (Immich timeline, Music
# Assistant covers) trips it with a burst of 200s. Scanners trip it
# with 404s for paths that do not exist. Drop the overflow unless at
# least half of its requests were client errors (the alert carries
# the scenario's last 5 events, cache_size: 5). Only this scenario
# is affected; probing/sensitive-files/cve still fire on their own.
crawl-non-statics-success-whitelist.yaml: |
name: krenner/crawl-non-statics-success-whitelist
description: "Ignore http-crawl-non_statics bursts that mostly succeeded"
whitelist:
reason: "crawl burst of successful requests is a UI, not a scanner"
expression:
- >-
evt.Overflow.Alert.GetScenario() == 'crowdsecurity/http-crawl-non_statics' &&
count(evt.Overflow.Alert.Events, {.GetMeta('http_status') startsWith '4'}) * 2
< len(evt.Overflow.Alert.Events)
# mTLS between agents and the LAPI. Do not switch back to password auth:
# the LAPI runs without persistence, so its machine-registration DB is