Skip to content

Send droplet security events to Discord - #25

Closed
OpenSource-For-Freedom wants to merge 1 commit into
mainfrom
feat/droplet-security-alerts
Closed

Send droplet security events to Discord#25
OpenSource-For-Freedom wants to merge 1 commit into
mainfrom
feat/droplet-security-alerts

Conversation

@OpenSource-For-Freedom

Copy link
Copy Markdown
Owner

Security monitoring that pushes out from the droplet, because nothing can reach in.

GitHub runners cannot touch this box (port 22 is open only to DigitalOcean console ranges), so anything watching sshd, sudo, auditd or fail2ban has to live on the server itself.

What it reports

SSH logins, bursts of failed auth, sudo commands, fail2ban bans, container not-running or restarting, changes to the whitelist/ops files auditd already watches, and a filling disk.

A systemd timer runs it every 15 minutes. It is silent when there is nothing to say, which is almost always.

The credential question, head on

The webhook now lives on the server. That deserves an argument, not a shrug.

Where: /etc/minecraft-alerts.env, root:root 0600, outside the git repo and outside the container. Minecraft runs as uid 1000 with a read-only rootfs and all capabilities dropped, so compromising the game does not reach it.

What theft buys an attacker: posting to a private Discord channel, and spamming it. Annoying, not dangerous.

What it does not buy: any route back into the droplet. A webhook is write-only and one-directional — Discord cannot send commands through it.

That asymmetry is the whole reason this is acceptable. The alternatives (opening a port, or parking an SSH credential in CI so something external could collect these events) are strictly worse. Alerts should push out.

Rotation is one command: delete the webhook in Discord, re-run the installer. The old URL dies instantly.

Other hardening

  • Installer prompts with read -s rather than taking an argument, which would land in shell history and /proc/<pid>/cmdline where any local user could read it mid-run
  • Script never echoes the webhook, never passes -v to curl
  • systemd unit: NoNewPrivileges, PrivateTmp, ProtectHome, ProtectKernelTunables, RestrictSUIDSGID, LockPersonality
  • Checkpoint written even when a post fails: re-reporting the same window forever is worse than missing one alert, because a channel that repeats itself stops being read

Installed and exercised on the live droplet before committing

env file          root:root 600          verified
script            root:root 755          verified
quiet run         "no security events since 30 minutes ago"
forced 24h window "posted 1 event(s)"    found real SSH logins
webhook in journal 0 occurrences

To install after merge: sudo ./alerts/install_alerts.sh on the droplet. It is already running there from this test, so the timer just needs enabling via the installer if you want it permanent.

GitHub runners cannot reach this box: port 22 is open only to
DigitalOcean console ranges. So anything that watches sshd, sudo, auditd
or fail2ban has to live ON the droplet and push outward. This does that.

Reports SSH logins, bursts of failed auth, sudo commands, fail2ban bans,
container not-running or restarting, changes to whitelist/ops files that
auditd is already watching, and a filling disk. A systemd timer runs it
every 15 minutes and it stays completely silent when there is nothing to
say, which is almost always.

THE CREDENTIAL QUESTION, since this puts a webhook on the server.

It lives in /etc/minecraft-alerts.env as root:root 0600, outside the git
repo and outside the container. The game runs as uid 1000 with a
read-only rootfs and all capabilities dropped, so compromising Minecraft
does not reach it.

What stealing it buys an attacker: posting messages to a private Discord
channel, and spamming it. Annoying, not dangerous. What it does NOT buy
is any route back into the droplet, because a webhook is write-only and
one-directional; Discord cannot send commands through it. That asymmetry
is the entire reason this is acceptable. The alternatives, opening a port
or parking an SSH credential in CI so something outside could collect
these events, are both strictly worse.

The installer prompts for the URL with read -s rather than taking an
argument, because an argument lands in shell history and in
/proc/<pid>/cmdline where any local user can read it mid-run. The script
never echoes the webhook, never passes -v to curl, and the systemd unit
carries NoNewPrivileges, PrivateTmp, ProtectHome and friends.

The checkpoint is written even when the post fails. Re-reporting the same
window forever is worse than missing one alert, because an alert channel
that repeats itself stops being read.

Installed and exercised on the live droplet before committing: env file
verified root:root 600, a quiet run correctly reported nothing, a forced
24-hour window found real SSH logins and posted them, and the webhook
appears zero times in the journal.
@OpenSource-For-Freedom
OpenSource-For-Freedom deleted the feat/droplet-security-alerts branch August 15, 2026 18:08
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