SFTP Monitoring and Daily Digest
Summary
Two shell scripts work together to monitor SFTP activity. sftp-monitor.sh runs persistently, tailing /var/log/auth.log for events from the sftp-feeds group, and posts real-time Slack alerts to #sftp-monitoring (channel ID C0APAF806F8). It maintains a daily JSON counter at /var/lib/sftp-monitor/daily.json. sftp-daily-digest.sh runs via cron at end-of-day, posts a summary table of the day’s activity, and resets the daily log.
State Diagram
stateDiagram-v2 state "Surveillance active" as Watching state "Connexion réussie détectée" as SuccessDetected state "Échec de connexion détecté" as FailureDetected state "Upload détecté" as UploadDetected state "Vérification partenaire (succès)" as IsPartner state "Alerte Slack envoyée" as SlackAlert state "Événement ignoré" as Ignored state "Vérification partenaire (échec)" as IsPartnerFail state "Comptage silencieux" as SilentCount state "Digest quotidien envoyé" as DigestPosted state "Journal réinitialisé" as DailyLogReset [*] --> Watching: tail -n 0 -F /var/log/auth.log Watching --> SuccessDetected: auth.log shows accepted password Watching --> FailureDetected: auth.log shows failed password Watching --> UploadDetected: auth.log shows SFTP write close SuccessDetected --> IsPartner: check if user is in sftp-feeds group IsPartner --> SlackAlert: post connected alert and update daily.json IsPartner --> Ignored: not in sftp-feeds group FailureDetected --> IsPartnerFail: check sftp-feeds group IsPartnerFail --> SlackAlert: post "Connection failure" warning + update daily.json IsPartnerFail --> SilentCount: unknown user — count only in daily.json UploadDetected --> SlackAlert: post uploaded filename alert [*] --> DigestPosted: cron runs sftp-daily-digest.sh DigestPosted --> DailyLogReset: rm daily.json
Steps
1. Real-Time Monitor Setup (Actor: system)
sftp-monitor.sh starts by:
- Checking that
internal-sftp -l INFOis configured insshd_config(upload detection requires this). Posts a Slack warning if missing. - Initializing the daily log JSON:
{ date, partners: {}, unknown_attempts: 0 }. - Starting
tail -n 0 -F /var/log/auth.log— processes only new lines, follows log rotation.
Environment: Requires SLACK_BOT_TOKEN env var. Channel hardcoded to C0APAF806F8.
2. Successful Connection Event (Actor: system)
Pattern matched: Accepted password for {user} from {ip}.
is_sftp_user() uses id -nG to check if the user belongs to sftp-feeds group.
If a partner user:
- Posts Slack:
"✅ *{Partner}* s'est connecté depuis {ip} — {date}" - Increments
daily.json partners[user].success
3. Failed Connection Event (Actor: system)
Pattern matched: Failed password for {user}.
If a partner user:
- Posts Slack:
"🔴 Échec de connexion de *{Partner}* — check if password changed" - Increments
daily.json partners[user].failed
If unknown user:
- Increments
daily.json unknown_attemptssilently (no Slack noise)
4. File Upload Detection (Actor: system)
Pattern matched: internal-sftp.*close.*WRITE.
Extracts filename from log line. The recent user is heuristically derived from the last session opened for sftp line in auth.log (best-effort attribution).
Posts Slack: "📄 *{user}* a uploadé {filename}"
Increments daily.json partners[user].upload.
Note: Requires internal-sftp -l INFO in sshd_config. Without it, upload events are not logged.
5. Timestamp Formatting (Actor: system)
extract_ts() handles both systemd ISO format (2026-03-30T06:07:55...) and traditional syslog format (Mar 30 06:07:55). format_time() formats to French "30/03 à 06h07".
6. Daily Digest (Actor: system, tools: slack)
sftp-daily-digest.sh — intended to run via cron (e.g. 59 23 * * *):
- Reads
/var/lib/sftp-monitor/daily.json. - If no file or no activity: posts
"Aucune activité aujourd'hui."and exits. - If only unknown attempts: posts count of blocked connections.
- Otherwise: posts a Markdown table with columns
Partenaire | Connexions | Échecs | Uploads | Downloadsper partner, plus totals row and unknown attempts footer. - Deletes
daily.jsonto reset for the next day.
Environment: Requires SLACK_BOT_TOKEN env var.
Error States
SLACK_BOT_TOKENnot set → script exits with errorinternal-sftp -l INFOmissing → upload events not detected, Slack warning posted on startupjqnot installed → daily log operations will fail silently- Log rotation →
tail -Fautomatically re-follows the new log file
Related Processes
- partner-onboarding — partners must be in the
sftp-feedsgroup for events to be monitored - sftp-xml-ingestion — SFTP upload triggers the file-watcher pipeline after this monitoring detects the connection