feat(proxy): add generic rsyslog receiver for exposed services

A fail2ban jail running inside a service's own LXC only ever sees this
proxy's tailnet IP as the connection source, so it would end up
banning the proxy itself. Detection needs to stay at the service's
application log; banning needs to happen here, at the edge where
public connections terminate.

Adds a generic imtcp listener (port RSYSLOG_PORT, default 5514) that
routes anything unclaimed by a later 50-<service>.conf into
/var/log/remote/<sender-hostname>.log, plus logrotate with
copytruncate so fail2ban never loses its file descriptor. No
service-specific routing yet — that's one 50-<service>.conf per
service, documented here for the next issue to follow.

Refs #20
This commit is contained in:
Damien
2026-07-31 17:52:47 +02:00
parent 9927e4dec1
commit 75d10385d7
2 changed files with 81 additions and 1 deletions
+39
View File
@@ -60,3 +60,42 @@ sudo ufw delete allow 22/tcp
- Access NPM admin: `https://proxy.<your-tailnet>.ts.net`
- Default credentials: `admin@example.com` / `changeme`
- Optionally approve exit-node in Tailscale admin console
## Centralized log reception (rsyslog)
A fail2ban jail running inside an exposed service's own LXC only ever sees
this proxy's tailnet IP as the connection source, so it would end up
banning the proxy itself instead of the actual client. Detection has to
stay where the signal is (the service's application log); banning has to
happen here, where public connections terminate. Services forward their
logs to this proxy over TCP so a jail here can act on them.
| File | Purpose |
|------|---------|
| `/etc/rsyslog.d/10-remote-receiver.conf` | Generic `imtcp` listener, port `RSYSLOG_PORT` (default `5514`). Anything not claimed by a more specific routing file lands in `/var/log/remote/<sender-hostname>.log`. |
| `/etc/rsyslog.d/50-<service>.conf` | One per exposed service. Routes by tag/programname into that service's own logfile for its dedicated fail2ban jail. |
| `/etc/logrotate.d/remote-logs` | Rotation for everything under `/var/log/remote/` (`copytruncate`, so fail2ban never loses its file descriptor across a rotation). |
Adding a new exposed service is a matter of dropping its `50-<service>.conf`
here — nothing else in this list needs to change. A minimal example that
routes messages tagged `myservice` into their own file, in addition to the
generic catch-all:
```
$RuleSet remoteLogs
if $programname == 'myservice' then {
action(type="omfile" file="/var/log/myservice/myservice.log")
}
$RuleSet RSYSLOG_DefaultRuleset
```
`RSYSLOG_PORT` and `RSYSLOG_BIND_ADDR` (default `0.0.0.0`) are overridable
via environment. The default bind is safe as-is: UFW's default-deny only
opens `80/tcp` and `443/tcp` publicly, so port `5514` is reachable
exclusively over `tailscale0` regardless of the bind address. Binding to
the tailnet IP directly was considered and rejected — it would require
`tailscale up` to have already succeeded before rsyslog is configured,
which complicates the script's flow (a missing `TS_AUTHKEY` is tolerated
today). The residual risk is log injection from anything that reaches the
port (which can trigger a false fail2ban ban); narrow `RSYSLOG_BIND_ADDR`
to a specific tailnet IP if that risk becomes a concern.