Add SSL expiry alerts
This commit is contained in:
@@ -58,6 +58,10 @@ Used for SSL certificate status.
|
|||||||
- `secret` (string): Login password.
|
- `secret` (string): Login password.
|
||||||
- `token` (string): Optional static token (not recommended if it expires).
|
- `token` (string): Optional static token (not recommended if it expires).
|
||||||
- `verify_tls` (bool): Set to `false` for self-signed TLS.
|
- `verify_tls` (bool): Set to `false` for self-signed TLS.
|
||||||
|
- `alerts.enabled` (bool): Enable expiry notifications.
|
||||||
|
- `alerts.days` (list): Thresholds in days (e.g. 30/14/7/1).
|
||||||
|
- `alerts.cooldown_sec` (int): Cooldown between identical alerts.
|
||||||
|
- `alerts.interval_sec` (int): Check interval.
|
||||||
|
|
||||||
Token flow:
|
Token flow:
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,10 @@
|
|||||||
- `secret` (string): пароль.
|
- `secret` (string): пароль.
|
||||||
- `token` (string): опционально статический токен (не рекомендуется при истечении).
|
- `token` (string): опционально статический токен (не рекомендуется при истечении).
|
||||||
- `verify_tls` (bool): `false` для self-signed TLS.
|
- `verify_tls` (bool): `false` для self-signed TLS.
|
||||||
|
- `alerts.enabled` (bool): включить уведомления по истечению.
|
||||||
|
- `alerts.days` (list): пороги в днях (например 30/14/7/1).
|
||||||
|
- `alerts.cooldown_sec` (int): кулдаун между одинаковыми алертами.
|
||||||
|
- `alerts.interval_sec` (int): интервал проверки.
|
||||||
|
|
||||||
Логика токена:
|
Логика токена:
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,15 @@ npmplus:
|
|||||||
# Optional static token (not recommended if it expires)
|
# Optional static token (not recommended if it expires)
|
||||||
token: ""
|
token: ""
|
||||||
verify_tls: true
|
verify_tls: true
|
||||||
|
alerts:
|
||||||
|
enabled: true
|
||||||
|
days:
|
||||||
|
- 30
|
||||||
|
- 14
|
||||||
|
- 7
|
||||||
|
- 1
|
||||||
|
cooldown_sec: 86400
|
||||||
|
interval_sec: 3600
|
||||||
|
|
||||||
security:
|
security:
|
||||||
reboot_password: "CHANGE_ME"
|
reboot_password: "CHANGE_ME"
|
||||||
|
|||||||
3
main.py
3
main.py
@@ -9,6 +9,7 @@ from services.metrics import MetricsStore, start_sampler
|
|||||||
from services.queue import worker as queue_worker
|
from services.queue import worker as queue_worker
|
||||||
from services.notify import notify
|
from services.notify import notify
|
||||||
from services.audit import AuditMiddleware, audit_start
|
from services.audit import AuditMiddleware, audit_start
|
||||||
|
from services.ssl_alerts import monitor_ssl
|
||||||
import state
|
import state
|
||||||
import handlers.menu
|
import handlers.menu
|
||||||
import handlers.status
|
import handlers.status
|
||||||
@@ -42,6 +43,8 @@ async def main():
|
|||||||
asyncio.create_task(monitor_resources(cfg, notify, bot, ADMIN_ID))
|
asyncio.create_task(monitor_resources(cfg, notify, bot, ADMIN_ID))
|
||||||
if cfg.get("alerts", {}).get("smart_enabled", True):
|
if cfg.get("alerts", {}).get("smart_enabled", True):
|
||||||
asyncio.create_task(monitor_smart(cfg, notify, bot, ADMIN_ID))
|
asyncio.create_task(monitor_smart(cfg, notify, bot, ADMIN_ID))
|
||||||
|
if cfg.get("npmplus", {}).get("alerts", {}).get("enabled", True):
|
||||||
|
asyncio.create_task(monitor_ssl(cfg, notify, bot, ADMIN_ID))
|
||||||
state.METRICS_STORE = MetricsStore()
|
state.METRICS_STORE = MetricsStore()
|
||||||
asyncio.create_task(start_sampler(state.METRICS_STORE, interval=5))
|
asyncio.create_task(start_sampler(state.METRICS_STORE, interval=5))
|
||||||
asyncio.create_task(queue_worker())
|
asyncio.create_task(queue_worker())
|
||||||
|
|||||||
57
services/ssl_alerts.py
Normal file
57
services/ssl_alerts.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from services.npmplus import fetch_certificates, _parse_expiry
|
||||||
|
|
||||||
|
|
||||||
|
async def monitor_ssl(cfg: dict[str, Any], notify, bot, chat_id: int):
|
||||||
|
npm_cfg = cfg.get("npmplus", {})
|
||||||
|
alert_cfg = npm_cfg.get("alerts", {})
|
||||||
|
if not alert_cfg.get("enabled", True):
|
||||||
|
return
|
||||||
|
|
||||||
|
days_list = alert_cfg.get("days", [30, 14, 7, 1])
|
||||||
|
days_list = sorted({int(x) for x in days_list if int(x) >= 0}, reverse=True)
|
||||||
|
cooldown = int(alert_cfg.get("cooldown_sec", 86400))
|
||||||
|
interval = int(alert_cfg.get("interval_sec", 3600))
|
||||||
|
|
||||||
|
last_sent: dict[str, float] = {}
|
||||||
|
|
||||||
|
while True:
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
try:
|
||||||
|
certs = fetch_certificates(cfg)
|
||||||
|
except Exception:
|
||||||
|
await asyncio.sleep(interval)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for cert in certs:
|
||||||
|
name = cert.get("nice_name")
|
||||||
|
if not name:
|
||||||
|
domains = cert.get("domain_names") or []
|
||||||
|
if isinstance(domains, list):
|
||||||
|
name = ", ".join(domains)
|
||||||
|
if not name:
|
||||||
|
name = "unknown"
|
||||||
|
|
||||||
|
expiry = _parse_expiry(cert.get("expires_on"))
|
||||||
|
if expiry is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
days_left = (expiry - now).days
|
||||||
|
for threshold in days_list:
|
||||||
|
if days_left <= threshold:
|
||||||
|
key = f"{name}:{threshold}"
|
||||||
|
last_time = last_sent.get(key, 0)
|
||||||
|
if time.time() - last_time >= cooldown:
|
||||||
|
await notify(
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
f"⚠️ SSL `{name}` expires in {days_left}d (threshold {threshold}d)",
|
||||||
|
)
|
||||||
|
last_sent[key] = time.time()
|
||||||
|
break
|
||||||
|
|
||||||
|
await asyncio.sleep(interval)
|
||||||
Reference in New Issue
Block a user