Create ssh-login-alerting.md via n8n
This commit is contained in:
parent
ea5bc50260
commit
dda6eac571
153
Tech/Sessions/ssh-login-alerting.md
Normal file
153
Tech/Sessions/ssh-login-alerting.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
project: ssh-login-alerting
|
||||||
|
type: session-notes
|
||||||
|
status: active
|
||||||
|
path: Tech/Sessions
|
||||||
|
tags:
|
||||||
|
- pbs
|
||||||
|
- security
|
||||||
|
- ssh
|
||||||
|
- auditd
|
||||||
|
- n8n
|
||||||
|
- monitoring
|
||||||
|
created: 2026-04-21
|
||||||
|
updated: 2026-04-21
|
||||||
|
---
|
||||||
|
# SSH Login Alerting — Session Notes (April 21, 2026)
|
||||||
|
|
||||||
|
## What Got Done
|
||||||
|
|
||||||
|
- Installed Ansible on dev server (Ubuntu) via Ansible PPA
|
||||||
|
- Ran Lovebug's `ssh_login_alert` Ansible role against staging — first run
|
||||||
|
was clean, no errors
|
||||||
|
- Confirmed auditd is running, audit rules are loaded, and the dispatcher
|
||||||
|
plugin is wired to `ssh-login-alert.sh`
|
||||||
|
- Confirmed the n8n webhook URL works (manual curl triggers successfully)
|
||||||
|
- Confirmed `USER_LOGIN` events from sshd ARE being captured by auditd
|
||||||
|
(`ausearch -m USER_LOGIN` returns them)
|
||||||
|
- Identified and fixed the exe= quote escaping mismatch (changed from
|
||||||
|
escaped `\"` to single-quoted literal match)
|
||||||
|
- Confirmed the script receives events via stdin (debug logger proved it)
|
||||||
|
|
||||||
|
## Current Blocker
|
||||||
|
|
||||||
|
The script matches `USER_LOGIN` events but fails silently at username
|
||||||
|
extraction. The root cause:
|
||||||
|
|
||||||
|
`USER_LOGIN` events do NOT contain an `acct=` field. They use a different
|
||||||
|
format:
|
||||||
|
|
||||||
|
|
||||||
|
type=USER_LOGIN msg=audit(...): pid=... uid=0 auid=1000 ses=387
|
||||||
|
subj=unconfined msg='op=login id=1000 exe="/usr/sbin/sshd" hostname=X
|
||||||
|
addr=X terminal=/dev/pts/0 res=success'UID="root" AUID="tj57testadmin"
|
||||||
|
ID="tj57testadmin"
|
||||||
|
|
||||||
|
|
||||||
|
Key differences from what the script expects:
|
||||||
|
- Username is in `AUID="username"` (enriched suffix), not `acct="username"`
|
||||||
|
- Also available as `ID="username"` in the enriched suffix
|
||||||
|
- Numeric `auid=1000` is in the main body but needs `getent passwd` to
|
||||||
|
resolve
|
||||||
|
- `id=1000` inside the `msg='...'` block is also numeric
|
||||||
|
|
||||||
|
The script hits `[[ -n "$username" ]] || continue` with an empty username
|
||||||
|
and silently skips the event.
|
||||||
|
|
||||||
|
An `AUID` regex fallback was added but still failed — needs further
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
## Next Steps — Debugging Checklist
|
||||||
|
|
||||||
|
- [ ] SSH into staging and capture raw event: `sudo ausearch -m USER_LOGIN
|
||||||
|
--start recent --raw | tail -1`
|
||||||
|
- [ ] Test regex patterns manually against the raw event line in bash to
|
||||||
|
confirm which pattern actually matches
|
||||||
|
- [ ] Key test patterns to try in bash:
|
||||||
|
bash
|
||||||
|
line=''
|
||||||
|
# Test 1: AUID with quotes
|
||||||
|
[[ "$line" =~ AUID=\"([^\"]+)\" ]] && echo "AUID match:
|
||||||
|
${BASH_REMATCH[1]}" || echo "no AUID match"
|
||||||
|
# Test 2: acct with quotes
|
||||||
|
[[ "$line" =~ acct=\"([^\"]+)\" ]] && echo "acct match:
|
||||||
|
${BASH_REMATCH[1]}" || echo "no acct match"
|
||||||
|
# Test 3: ID with quotes
|
||||||
|
[[ "$line" =~ ID=\"([^\"]+)\" ]] && echo "ID match: ${BASH_REMATCH[1]}"
|
||||||
|
|| echo "no ID match"
|
||||||
|
# Test 4: auid numeric then resolve
|
||||||
|
[[ "$line" =~ auid=([0-9]+) ]] && echo "auid match: ${BASH_REMATCH[1]}"
|
||||||
|
|| echo "no auid match"
|
||||||
|
|
||||||
|
- [ ] Once the working regex is identified, update the username extraction
|
||||||
|
block in `/usr/local/bin/ssh-login-alert.sh`
|
||||||
|
- [ ] Restart auditd: `sudo service auditd restart`
|
||||||
|
- [ ] SSH in from another terminal and check: `sudo journalctl -t
|
||||||
|
ssh-login-alert --since "1 minute ago" --no-pager -o cat`
|
||||||
|
- [ ] Confirm Google Chat notification arrives
|
||||||
|
- [ ] Once working on staging, backport the fix into the Ansible role
|
||||||
|
template
|
||||||
|
- [ ] Remove the old commented-out exe= line from the script
|
||||||
|
|
||||||
|
## Architecture Reminder
|
||||||
|
|
||||||
|
|
||||||
|
SSH login → auditd captures USER_LOGIN event → audispd plugin pipes to
|
||||||
|
ssh-login-alert.sh →
|
||||||
|
script extracts fields + POSTs JSON to n8n webhook → n8n formats + sends to
|
||||||
|
Google Chat
|
||||||
|
|
||||||
|
|
||||||
|
## Useful Debug Commands
|
||||||
|
|
||||||
|
bash
|
||||||
|
# Check auditd status
|
||||||
|
sudo systemctl status auditd
|
||||||
|
|
||||||
|
# Check loaded audit rules
|
||||||
|
sudo auditctl -l
|
||||||
|
|
||||||
|
# Search for SSH login events
|
||||||
|
sudo ausearch -m USER_LOGIN --start recent
|
||||||
|
|
||||||
|
# Raw event format (what the script actually sees)
|
||||||
|
sudo ausearch -m USER_LOGIN --start recent --raw | tail -1
|
||||||
|
|
||||||
|
# Tail audit log for live events
|
||||||
|
sudo tail -f /var/log/audit/audit.log
|
||||||
|
|
||||||
|
# Check script logs
|
||||||
|
sudo journalctl -t ssh-login-alert --since "5 minutes ago" --no-pager -o cat
|
||||||
|
|
||||||
|
# Restart auditd (picks up script changes)
|
||||||
|
sudo service auditd restart
|
||||||
|
|
||||||
|
# Verify config file
|
||||||
|
sudo cat /etc/pbs-alerts/config
|
||||||
|
|
||||||
|
|
||||||
|
## Learnings
|
||||||
|
|
||||||
|
- `USER_LOGIN` is a PAM userspace event, not a syscall — audit rule `key=`
|
||||||
|
tags don't apply to it
|
||||||
|
- The exe= field in USER_LOGIN events uses literal double quotes, not
|
||||||
|
escaped quotes — use single-quoted bash patterns for matching
|
||||||
|
- auditd dispatcher plugins (`type = always`) receive ALL events —
|
||||||
|
filtering must happen in the script
|
||||||
|
- Debug logger lines spawning per-event can overwhelm auditd's plugin queue
|
||||||
|
— use sparingly and remove after testing
|
||||||
|
- `sudo ausearch -m USER_LOGIN --start recent --raw` is the best way to see
|
||||||
|
exactly what the script receives
|
||||||
|
|
||||||
|
## Open Items from Session
|
||||||
|
|
||||||
|
- Ansible user strategy: `pbsdeploy` (no password, no sudo, key-only) is
|
||||||
|
for CI/CD only. Ansible playbooks should run as admin account with `-K` for
|
||||||
|
sudo. These are separate concerns — revisit when CI/CD pipeline discussion
|
||||||
|
comes back up.
|
||||||
|
---
|
||||||
|
*Session Date: April 21, 2026*
|
||||||
|
*Next Session: Continue debugging username regex, get end-to-end alert
|
||||||
|
working*
|
||||||
|
|
||||||
|
...sent from Jenny & Travis
|
||||||
Loading…
Reference in New Issue
Block a user