diff --git a/README.md b/README.md
index 627f7a7..75eb583 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,17 @@ DISCLAIMER: This repository does not have anything to do with the LEGO Group. "l
* Copy `config.example.json` to `config.json`, edit as necessary
* Run `alerting/login.py` once to login into Matrix
+### Setting up login alerts
+
+* Copy `lego-login-alert` to your `/etc/sudoers.d`
+* Add this to your `/etc/ssh/sshd_config`:
+```
+# login alerts
+ForceCommand /opt/lego-monitoring/wrappers/login_wrapper.sh
+```
+
## Running
* `prettyprint.py` -- check and print all sensors
* `service.py` -- launch service
-* `lego-monitoring.service` is a systemd unit that starts `service.py`
+* `assets/lego-monitoring.service` is a systemd unit that starts `service.py`
diff --git a/alerting/alerts.py b/alerting/alerts.py
index 743b1a0..aaf04c7 100644
--- a/alerting/alerts.py
+++ b/alerting/alerts.py
@@ -1,13 +1,10 @@
-import json
from dataclasses import dataclass
from typing import Optional
-import aiofiles
import nio
from alerting.enum import AlertType, Severity
from misc import cvars
-from misc.common import CONFIG_FILE
from misc.config import get_config
diff --git a/alerting/enum.py b/alerting/enum.py
index e902fce..87e4b2b 100644
--- a/alerting/enum.py
+++ b/alerting/enum.py
@@ -8,7 +8,7 @@ class AlertType(StrEnum):
CPU = "CPU"
TEMP = "TEMP"
VULN = "VULN"
- LOGIN = "LOGIN" # TODO
+ LOGIN = "LOGIN"
SMART = "SMART" # TODO
RAID = "RAID"
DISKS = "DISKS"
diff --git a/assets/lego-login-alert b/assets/lego-login-alert
new file mode 100644
index 0000000..b816f8b
--- /dev/null
+++ b/assets/lego-login-alert
@@ -0,0 +1,2 @@
+Defaults env_keep += "SSH_CLIENT"
+ALL ALL=(ALL:ALL) NOPASSWD: /opt/lego-monitoring/wrappers/send_login_alert.sh
diff --git a/lego-monitoring.service b/assets/lego-monitoring.service
similarity index 100%
rename from lego-monitoring.service
rename to assets/lego-monitoring.service
diff --git a/config.example.json b/config.example.json
index 6aabe09..6fe847a 100644
--- a/config.example.json
+++ b/config.example.json
@@ -22,6 +22,9 @@
"severity": "CRITICAL"
}
]
+ },
+ "login": {
+ "hostname": "example.com"
}
}
}
diff --git a/misc/config.py b/misc/config.py
index ebb6598..6b1fb0c 100644
--- a/misc/config.py
+++ b/misc/config.py
@@ -38,11 +38,17 @@ class CheckWearoutConfig(NestedDeserializableDataclass):
disks: list[CheckWearoutDiskConfig]
+@dataclass
+class CheckLoginConfig:
+ hostname: str
+
+
@dataclass
class ChecksConfig(NestedDeserializableDataclass):
docker_registry: CheckDockerRegistryConfig
raid: CheckRaidConfig
wearout: CheckWearoutConfig
+ login: CheckLoginConfig
@dataclass
diff --git a/send_login_alert.py b/send_login_alert.py
new file mode 100644
index 0000000..b8f8898
--- /dev/null
+++ b/send_login_alert.py
@@ -0,0 +1,47 @@
+import asyncio
+import os
+import socket
+import sys
+
+from alerting import alerts
+from alerting.enum import AlertType, Severity
+from misc.config import get_config
+
+
+async def main():
+ check_config = get_config().checks.login
+
+ try:
+ from_where = os.environ["SSH_CLIENT"].split()[0]
+ except:
+ from_where = os.ttyname(sys.stdout.fileno())
+ is_local = True
+ else:
+ is_local = False
+
+ try:
+ actual_user = os.environ["SUDO_USER"]
+ except Exception as exc:
+ await alerts.send_alert(
+ alerts.Alert(
+ alert_type=AlertType.ERROR,
+ message=f"Failed to determine username for login from {from_where}, see logs",
+ severity=Severity.CRITICAL,
+ )
+ )
+ return
+
+ if not is_local:
+ rdns_result = socket.getnameinfo((from_where, 0), 0)[0]
+ message = f"Login from {from_where} as {actual_user} on `{check_config.hostname}`"
+ html_message = f"Login from {from_where} ({rdns_result}) as {actual_user} on {check_config.hostname}"
+ else:
+ message = f"Login from {from_where} as {actual_user} on {check_config.hostname}"
+ html_message = f"Login from {from_where} as {actual_user} on {check_config.hostname}"
+
+ alert = alerts.Alert(alert_type=AlertType.LOGIN, message=message, severity=Severity.INFO, html_message=html_message)
+ await alerts.send_alert(alert)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/wrappers/login_wrapper.sh b/wrappers/login_wrapper.sh
new file mode 100755
index 0000000..a69899b
--- /dev/null
+++ b/wrappers/login_wrapper.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+mydir=$(dirname "$0")
+sudo "$mydir/send_login_alert.sh"
+
+shell=$(getent passwd $LOGNAME | cut -d: -f7)
+if [[ -n $SSH_ORIGINAL_COMMAND ]] # command given, so run it
+then
+ exec "$shell" -c "$SSH_ORIGINAL_COMMAND"
+else # no command, so interactive login shell
+ exec "$shell" -il
+fi
diff --git a/wrappers/send_login_alert.sh b/wrappers/send_login_alert.sh
new file mode 100755
index 0000000..d10a433
--- /dev/null
+++ b/wrappers/send_login_alert.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+mydir=$(dirname "$0")
+"$mydir/../.venv/bin/python" "$mydir/../send_login_alert.py"