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:
@@ -60,3 +60,42 @@ sudo ufw delete allow 22/tcp
|
|||||||
- Access NPM admin: `https://proxy.<your-tailnet>.ts.net`
|
- Access NPM admin: `https://proxy.<your-tailnet>.ts.net`
|
||||||
- Default credentials: `admin@example.com` / `changeme`
|
- Default credentials: `admin@example.com` / `changeme`
|
||||||
- Optionally approve exit-node in Tailscale admin console
|
- 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.
|
||||||
|
|||||||
+42
-1
@@ -44,6 +44,13 @@ ACME_EMAIL="${ACME_EMAIL:-}"
|
|||||||
# Generate at https://login.tailscale.com/admin/settings/keys
|
# Generate at https://login.tailscale.com/admin/settings/keys
|
||||||
TS_AUTHKEY="${TS_AUTHKEY:-}"
|
TS_AUTHKEY="${TS_AUTHKEY:-}"
|
||||||
|
|
||||||
|
# rsyslog receiver for exposed services' logs (see "Configuring rsyslog" below).
|
||||||
|
RSYSLOG_PORT="${RSYSLOG_PORT:-5514}"
|
||||||
|
# Default 0.0.0.0 is fine: UFW's default-deny only opens 80/443 publicly, so
|
||||||
|
# this port is reachable exclusively over tailscale0 either way. Override to
|
||||||
|
# a specific tailnet IP to shrink the blast radius of log injection instead.
|
||||||
|
RSYSLOG_BIND_ADDR="${RSYSLOG_BIND_ADDR:-0.0.0.0}"
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
log_info "=== Proxy Server Deployment (Traefik v3) ==="
|
log_info "=== Proxy Server Deployment (Traefik v3) ==="
|
||||||
|
|
||||||
@@ -75,7 +82,7 @@ main() {
|
|||||||
|
|
||||||
log_info "Installing base packages..."
|
log_info "Installing base packages..."
|
||||||
sudo apt update -qq
|
sudo apt update -qq
|
||||||
sudo apt install -y -qq vim ca-certificates curl gnupg lsb-release fail2ban unattended-upgrades ufw ethtool networkd-dispatcher > /dev/null
|
sudo apt install -y -qq vim ca-certificates curl gnupg lsb-release fail2ban unattended-upgrades ufw ethtool networkd-dispatcher rsyslog > /dev/null
|
||||||
|
|
||||||
log_info "Installing Tailscale..."
|
log_info "Installing Tailscale..."
|
||||||
curl -fsSL https://tailscale.com/install.sh | sh
|
curl -fsSL https://tailscale.com/install.sh | sh
|
||||||
@@ -174,6 +181,40 @@ EOF
|
|||||||
|
|
||||||
sudo systemctl restart fail2ban
|
sudo systemctl restart fail2ban
|
||||||
|
|
||||||
|
log_info "Configuring 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 source of connections, so it would end up
|
||||||
|
# banning the proxy itself. Detection has to stay where the signal is
|
||||||
|
# (the service's application log); banning has to happen here, at the
|
||||||
|
# edge where public connections actually terminate. Services forward
|
||||||
|
# their logs to this receiver over TCP; adding a new one is a matter of
|
||||||
|
# dropping a 50-<service>.conf here (see proxy/README.md) — nothing else
|
||||||
|
# to touch.
|
||||||
|
sudo mkdir -p /var/log/remote
|
||||||
|
sudo tee /etc/rsyslog.d/10-remote-receiver.conf > /dev/null << EOF
|
||||||
|
module(load="imtcp")
|
||||||
|
|
||||||
|
\$RuleSet remoteLogs
|
||||||
|
\$template RemoteLogPath,"/var/log/remote/%HOSTNAME%.log"
|
||||||
|
*.* ?RemoteLogPath
|
||||||
|
\$RuleSet RSYSLOG_DefaultRuleset
|
||||||
|
|
||||||
|
input(type="imtcp" port="${RSYSLOG_PORT}" address="${RSYSLOG_BIND_ADDR}" ruleset="remoteLogs")
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo tee /etc/logrotate.d/remote-logs > /dev/null << 'EOF'
|
||||||
|
/var/log/remote/*.log {
|
||||||
|
daily
|
||||||
|
rotate 7
|
||||||
|
compress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
copytruncate
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo systemctl restart rsyslog
|
||||||
|
|
||||||
log_info "Creating Traefik stack under $TRAEFIK_DIR..."
|
log_info "Creating Traefik stack under $TRAEFIK_DIR..."
|
||||||
mkdir -p "$TRAEFIK_DIR/conf.d"
|
mkdir -p "$TRAEFIK_DIR/conf.d"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user