Skip to content

fix: cut tailnet-wide exposure, narrow crowdsec allowlists, tighten secret handling - #387

Merged
frostplexx merged 6 commits into
mainfrom
fix/tailnet-exposure
Oct 6, 2026
Merged

frostplexx merged 6 commits into
mainfrom
fix/tailnet-exposure

Conversation

@frostplexx

Copy link
Copy Markdown
Member

Follow-up to the security audit. Six fixes plus the gluetun image-name fix that was stranded on the ci/deploy-cadence branch after #385 merged.

Port exposure (all previously published on 0.0.0.0, reachable tailnet-wide via the trusted tailscale0 interface)

  • authelia 9091 → 127.0.0.1:9091 — caddy (host) is the sole entry point
  • gluetun HTTP proxy 8081 → 127.0.0.1:8081 — no consumer in the repo
  • upsnap — keeps --network=host (LAN device scanning with NET_RAW), but the web UI now binds 127.0.0.1:8090 via UPSNAP_HTTP_LISTEN (the publish map is ignored under host networking, so a prefix alone can't fix this one). Also drops a stray SLSKD_REMOTE_CONFIGURATION copy-paste var
  • unifi — device/portal ports bound to the LAN IP 192.168.0.85 instead of 0.0.0.0. Devices already target that IP (see the set-inform note), so adoption is unaffected — but they are no longer tailnet-wide. Not 127.0.0.1 because that would break device inform
  • haproxy stats — *:8404 → 127.0.0.1:8404 (scraped by the local host-networked gatus; remote viewing via ssh -L 8404:127.0.0.1:8404 eclair). Deliberately not bound to the tailnet IP: haproxy would fail at boot when tailscale brings the address up after haproxy starts. The now-dead tailscale0 firewall rule removed

CrowdSec

  • LAPI allowlist + parser whitelist no longer blanket-allow RFC1918 / CGNAT 100.64.0.0/10 / ULA — only loopback, link-local and the two tailnet infra IPs (100.99.168.34 eclair, 100.120.32.9 sorbet). A compromised LAN device (hairpin dual-A record) or tailnet node can now be banned

Secrets

  • subtidal token: 0444 → 0400, owned by a new system user matching the container's uid 1000
  • upload-secrets.sh: age key streamed over stdin instead of an ssh command-line argument (world-readable in /proc), StrictHostKeyChecking=no removed

Housekeeping

  • gluetun image ref → ghcr.io/qdm12/gluetun (same digest; qmcgaw/gluetun is the deprecated docker-hub mirror)

Both toplevels build against this branch. Merging does not deploy anything directly — the daily 07:00 UTC change-guarded deploy picks it up.

Not touched (flagged, didn't want to guess intent): gluetun's published 47594/tcp + udp (no consumer in the repo — say the word if it should be loopback/LAN too), and trustedInterfaces = ["tailscale0"] itself.

- authelia 9091: loopback only; caddy is the sole entry point
- gluetun 8081 (HTTP proxy): loopback only
- upsnap: --network=host is kept for LAN scanning, but the web UI now
  binds 127.0.0.1 via UPSNAP_HTTP_LISTEN (the publish map is ignored
  under host networking); drops stray SLSKD_REMOTE_CONFIGURATION env
- unifi: device/portal ports bound to the LAN IP (192.168.0.85) instead
  of 0.0.0.0 — APs already target that IP, and tailscale0 is a trusted
  interface, so a wildcard bind exposed every port tailnet-wide
- haproxy stats: loopback-only bind instead of *:8404 (scraped by the
  local host-networked gatus; remote access via ssh -L)
The LAPI allowlist and parser whitelist blanket-allowed all of RFC1918,
CGNAT 100.64.0.0/10 and ULA, so LAN-originated attacks on the public
vhosts (hairpin dual-A record) and compromised tailnet nodes could
never be banned. Allowlist only loopback/link-local and the two tailnet
infra IPs (eclair 100.99.168.34, sorbet 100.120.32.9).
The app-only token was rendered world-readable (0444). Give the secret
to a system user matching the container's uid 1000 and render it 0400.
The key was passed through an ssh command-line argument (world-readable
in /proc, remote shell history) with StrictHostKeyChecking=no. Use the
README's stdin pattern and let the first connect pin the host key.
@frostplexx
frostplexx enabled auto-merge October 6, 2026 06:57
@frostplexx
frostplexx merged commit 207f91e into main Oct 6, 2026
6 checks passed
@frostplexx
frostplexx deleted the fix/tailnet-exposure branch October 6, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant