merge: sync github roadmap with kalinamall 0.9.11

This commit is contained in:
PTah
2026-06-19 22:34:14 +10:00
35 changed files with 335 additions and 114 deletions
+6 -6
View File
@@ -8,10 +8,10 @@
| Репозиторий | Назначение | | Репозиторий | Назначение |
|-------------|------------| |-------------|------------|
| [ssh-monitor](https://github.com/PTah/ssh-monitor) | Linux: SSH, sudo, logind, брутфорс, баны IP | | [ssh-monitor](https://git.kalinamall.ru/PapaTramp/ssh-monitor) | Linux: SSH, sudo, logind, брутфорс, баны IP |
| [RDP-login-monitor](https://github.com/PTah/RDP-login-monitor) | Windows: RDP/RDS, RD Gateway, WinRM, admin share C$/ADMIN$ | | [RDP-login-monitor](https://git.kalinamall.ru/PapaTramp/RDP-login-monitor) | Windows: RDP/RDS, RD Gateway, WinRM, admin share C$/ADMIN$ |
| **security-alert-center** (этот репозиторий) | Ubuntu 24.04: API, БД, UI, оповещения | | **security-alert-center** (этот репозиторий) | Ubuntu 24.04: API, БД, UI, оповещения |
| [seaca](https://github.com/PTah/seaca) | Android: мобильный клиент SAC, push (FCM) | | [seaca](https://git.kalinamall.ru/PapaTramp/seaca) | Android: мобильный клиент SAC, push (FCM) |
## Статус ## Статус
@@ -29,8 +29,8 @@
События приходят от агентов по [контракту ingest](docs/agent-integration.md) (`type`, `severity`, `details`): События приходят от агентов по [контракту ingest](docs/agent-integration.md) (`type`, `severity`, `details`):
- **Linux — [ssh-monitor](https://github.com/PTah/ssh-monitor):** SSH (успех/неудача), **sudo**, **systemd-logind**, брутфорс и баны IP (ipset), пороги без бана, ежедневный отчёт, heartbeat. - **Linux — [ssh-monitor](https://git.kalinamall.ru/PapaTramp/ssh-monitor):** SSH (успех/неудача), **sudo**, **systemd-logind**, брутфорс и баны IP (ipset), пороги без бана, ежедневный отчёт, heartbeat.
- **Windows — [RDP-login-monitor](https://github.com/PTah/RDP-login-monitor):** - **Windows — [RDP-login-monitor](https://git.kalinamall.ru/PapaTramp/RDP-login-monitor):**
- **RDP/RDS** (4624/4625); - **RDP/RDS** (4624/4625);
- **RD Gateway** (302/303), **WinRM / PowerShell Remoting** (Enter-PSSession, ID 91); - **RD Gateway** (302/303), **WinRM / PowerShell Remoting** (Enter-PSSession, ID 91);
- **админ-шары `C$` / `ADMIN$`** (Security **5140**, `smb.admin_share.access`); - **админ-шары `C$` / `ADMIN$`** (Security **5140**, `smb.admin_share.access`);
@@ -65,7 +65,7 @@
## Быстрый старт (native) ## Быстрый старт (native)
```bash ```bash
git clone https://github.com/PTah/security-alert-center.git /opt/security-alert-center git clone https://git.kalinamall.ru/PapaTramp/security-alert-center.git /opt/security-alert-center
# deploy/env.native.example -> config/sac-api.env # deploy/env.native.example -> config/sac-api.env
cd /opt/security-alert-center/backend cd /opt/security-alert-center/backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
+5 -5
View File
@@ -8,8 +8,8 @@ Self-hosted hub for collecting, storing, and displaying security events from Lin
| Repository | Role | | Repository | Role |
|------------|------| |------------|------|
| [ssh-monitor](https://github.com/PTah/ssh-monitor) | Linux: SSH, sudo, logind, brute-force, IP ban | | [ssh-monitor](https://git.kalinamall.ru/PapaTramp/ssh-monitor) | Linux: SSH, sudo, logind, brute-force, IP ban |
| [RDP-login-monitor](https://github.com/PTah/RDP-login-monitor) | Windows: RDP/RDS, RD Gateway, WinRM, admin share C$/ADMIN$ | | [RDP-login-monitor](https://git.kalinamall.ru/PapaTramp/RDP-login-monitor) | Windows: RDP/RDS, RD Gateway, WinRM, admin share C$/ADMIN$ |
| **security-alert-center** (this repo) | Ubuntu 24.04: API, database, UI, notifications | | **security-alert-center** (this repo) | Ubuntu 24.04: API, database, UI, notifications |
## Status ## Status
@@ -28,8 +28,8 @@ Self-hosted hub for collecting, storing, and displaying security events from Lin
Agents send events via the [ingest contract](docs/agent-integration.md) (`type`, `severity`, `details`): Agents send events via the [ingest contract](docs/agent-integration.md) (`type`, `severity`, `details`):
- **Linux — [ssh-monitor](https://github.com/PTah/ssh-monitor):** SSH success/failure, **sudo**, **systemd-logind**, brute-force and IP ban (ipset), thresholds without ban, daily report, heartbeat. - **Linux — [ssh-monitor](https://git.kalinamall.ru/PapaTramp/ssh-monitor):** SSH success/failure, **sudo**, **systemd-logind**, brute-force and IP ban (ipset), thresholds without ban, daily report, heartbeat.
- **Windows — [RDP-login-monitor](https://github.com/PTah/RDP-login-monitor):** - **Windows — [RDP-login-monitor](https://git.kalinamall.ru/PapaTramp/RDP-login-monitor):**
- **RDP/RDS** (4624/4625); - **RDP/RDS** (4624/4625);
- **RD Gateway** (302/303), **WinRM / PowerShell Remoting** (Enter-PSSession, event ID 91); - **RD Gateway** (302/303), **WinRM / PowerShell Remoting** (Enter-PSSession, event ID 91);
- **admin shares `C$` / `ADMIN$`** (Security **5140**, `smb.admin_share.access`); - **admin shares `C$` / `ADMIN$`** (Security **5140**, `smb.admin_share.access`);
@@ -64,7 +64,7 @@ Agents send events via the [ingest contract](docs/agent-integration.md) (`type`,
## Quick start (native) ## Quick start (native)
```bash ```bash
git clone https://github.com/PTah/security-alert-center.git /opt/security-alert-center git clone https://git.kalinamall.ru/PapaTramp/security-alert-center.git /opt/security-alert-center
# deploy/env.native.example -> config/sac-api.env # deploy/env.native.example -> config/sac-api.env
cd /opt/security-alert-center/backend cd /opt/security-alert-center/backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
+2 -2
View File
@@ -16,6 +16,7 @@ from app.models import Event, Problem
from app.models.mobile_device import MobileDevice from app.models.mobile_device import MobileDevice
from app.services.notification_severity import severity_meets_minimum from app.services.notification_severity import severity_meets_minimum
from app.services.notification_policy import get_effective_notification_policy from app.services.notification_policy import get_effective_notification_policy
from app.services.telegram_templates import format_event_mobile_push
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -170,8 +171,7 @@ def _send_fcm_data(
def _event_payload(event: Event) -> tuple[str, str, dict[str, str]]: def _event_payload(event: Event) -> tuple[str, str, dict[str, str]]:
title = f"[{event.severity}] {event.title}" title, body = format_event_mobile_push(event)
body = (event.summary or "")[:200]
data = { data = {
"kind": "event", "kind": "event",
"id": str(event.id), "id": str(event.id),
@@ -506,6 +506,43 @@ def format_event_telegram_html(event: Event) -> str:
return _append_event_source(_format_event_body_html(event), event) return _append_event_source(_format_event_body_html(event), event)
_TELEGRAM_INLINE_TAGS = re.compile(r"</?(?:b|i|u|s|code|pre|tg-spoiler)(\s[^>]*)?>", re.I)
_TELEGRAM_ANCHOR_OPEN = re.compile(r"<a\b[^>]*>", re.I)
_TELEGRAM_ANCHOR_CLOSE = re.compile(r"</a>", re.I)
_MOBILE_PUSH_BODY_MAX = 3500
def telegram_html_to_plaintext(text: str) -> str:
"""Plain text for Seaca push from the same HTML templates as Telegram."""
out = sanitize_telegram_html(text)
out = _TELEGRAM_INLINE_TAGS.sub("", out)
out = _TELEGRAM_ANCHOR_OPEN.sub("", out)
out = _TELEGRAM_ANCHOR_CLOSE.sub("", out)
out = html.unescape(out)
out = re.sub(r"\n{3,}", "\n\n", out)
return out.strip()
def format_event_mobile_push(event: Event) -> tuple[str, str]:
"""Title + body for FCM data payload (mirrors Telegram wording)."""
plain = telegram_html_to_plaintext(format_event_telegram_html(event))
lines = [line.strip() for line in plain.split("\n") if line.strip()]
if lines:
title = lines[0]
body = "\n".join(lines[1:]).strip()
if not body:
body = (event.summary or "").strip()
else:
title = f"[{event.severity}] {event.title}"
body = (event.summary or "").strip()
if len(title) > 120:
title = title[:117] + ""
if len(body) > _MOBILE_PUSH_BODY_MAX:
body = body[: _MOBILE_PUSH_BODY_MAX - 1] + ""
return title, body
def _format_rdg_html(event: Event) -> str: def _format_rdg_html(event: Event) -> str:
details = _details_dict(event) details = _details_dict(event)
if event.type.endswith(".success"): if event.type.endswith(".success"):
+1 -1
View File
@@ -1,5 +1,5 @@
"""Единый источник версии SAC (API, health, логи, OpenAPI).""" """Единый источник версии SAC (API, health, логи, OpenAPI)."""
APP_NAME = "Security Alert Center" APP_NAME = "Security Alert Center"
APP_VERSION = "0.9.10" APP_VERSION = "0.9.11"
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}" APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
+2 -2
View File
@@ -4,6 +4,6 @@ from app.version import APP_NAME, APP_VERSION, APP_VERSION_LABEL
def test_version_constants(): def test_version_constants():
assert APP_VERSION == "0.9.10" assert APP_VERSION == "0.9.11"
assert APP_NAME == "Security Alert Center" assert APP_NAME == "Security Alert Center"
assert APP_VERSION_LABEL == "Security Alert Center v.0.9.10" assert APP_VERSION_LABEL == "Security Alert Center v.0.9.11"
+24 -1
View File
@@ -1,10 +1,33 @@
"""FCM push must not break event ingest.""" """FCM push must not break event ingest."""
from datetime import datetime, timezone
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest
from app.services.mobile_notify import MobileNotConfiguredError, _send_fcm_data from app.models import Event, Host
from app.services.mobile_notify import MobileNotConfiguredError, _event_payload, _send_fcm_data
def test_event_payload_lifecycle_uses_telegram_wording():
host = Host(hostname="WIN01", display_name="K6A-DC3", os_family="windows")
event = Event(
event_id="00000000-0000-4000-8000-000000000511",
host_id=1,
host=host,
occurred_at=datetime(2026, 5, 29, 9, 0, tzinfo=timezone.utc),
category="agent",
type="agent.lifecycle",
severity="info",
title="started",
summary="short",
details={"lifecycle": "started", "trigger": "settings_reload", "telegram_via": "sac"},
payload={"source": {"product": "ssh-monitor", "product_version": "1.4.0-SAC"}},
)
title, body, data = _event_payload(event)
assert title == "✅ Агент запущен"
assert "graceful restart" in body
assert data["type"] == "agent.lifecycle"
def test_send_fcm_data_swallows_token_errors_by_default(): def test_send_fcm_data_swallows_token_errors_by_default():
+62 -1
View File
@@ -3,7 +3,13 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from app.models import Event, Host from app.models import Event, Host
from app.services.telegram_templates import format_event_telegram_html, format_rdp_login_html, html_escape from app.services.telegram_templates import (
format_event_mobile_push,
format_event_telegram_html,
format_rdp_login_html,
html_escape,
telegram_html_to_plaintext,
)
def test_html_escape_special_chars(): def test_html_escape_special_chars():
@@ -145,6 +151,61 @@ def test_event_telegram_includes_sac_source_for_sac_daily_report():
assert "📡 Оповещение: SAC (Security Alert Center)" in text assert "📡 Оповещение: SAC (Security Alert Center)" in text
def test_telegram_html_to_plaintext_strips_tags():
assert telegram_html_to_plaintext("<b>✅ Агент запущен</b>\n🏢 Хост: WIN01") == (
"✅ Агент запущен\n🏢 Хост: WIN01"
)
def test_format_event_mobile_push_lifecycle_includes_trigger():
host = Host(hostname="WIN01", display_name="K6A-DC3", os_family="windows")
event = Event(
event_id="00000000-0000-4000-8000-000000000509",
host_id=1,
host=host,
occurred_at=datetime(2026, 5, 29, 9, 0, tzinfo=timezone.utc),
category="agent",
type="agent.lifecycle",
severity="info",
title="started",
summary="Мониторинг запущен",
details={"lifecycle": "started", "trigger": "deploy_recycle", "telegram_via": "sac"},
payload={"source": {"product": "rdp-login-monitor", "product_version": "1.2.30-SAC"}},
)
title, body = format_event_mobile_push(event)
assert title == "✅ Агент запущен"
assert "K6A-DC3" in body
assert "1.2.30-SAC" in body
assert "обновление скрипта" in body
assert "📡 Оповещение: SAC" in body
def test_format_event_mobile_push_uses_notification_body():
host = Host(hostname="WIN01", display_name="K6A-DC3", os_family="windows")
body_text = "✅ Мониторинг логинов ЗАПУЩЕН\n🖥️ Сервер: K6A-DC3 (10.0.0.1)"
event = Event(
event_id="00000000-0000-4000-8000-000000000510",
host_id=1,
host=host,
occurred_at=datetime(2026, 5, 29, 9, 0, tzinfo=timezone.utc),
category="agent",
type="agent.lifecycle",
severity="info",
title="started",
summary="Мониторинг запущен",
details={
"lifecycle": "started",
"trigger": "boot",
"notification_body": body_text,
"telegram_via": "sac",
},
payload={"source": {"product": "rdp-login-monitor", "product_version": "1.2.31-SAC"}},
)
title, body = format_event_mobile_push(event)
assert title == "✅ Мониторинг логинов ЗАПУЩЕН"
assert "K6A-DC3 (10.0.0.1)" in body
def test_lifecycle_started_template(): def test_lifecycle_started_template():
host = Host(hostname="WIN01", display_name="K6A-DC3", os_family="windows") host = Host(hostname="WIN01", display_name="K6A-DC3", os_family="windows")
event = Event( event = Event(
+1 -1
View File
@@ -7,7 +7,7 @@ POSTGRES_PASSWORD=change-me-long-random
POSTGRES_DB=sac POSTGRES_DB=sac
# API (публичный URL для ссылок в ответах ingest) # API (публичный URL для ссылок в ответах ingest)
SAC_PUBLIC_URL=https://sac.example.com SAC_PUBLIC_URL=https://sac.kalinamall.ru
SAC_API_PORT=8000 SAC_API_PORT=8000
# JWT (UI — позже) # JWT (UI — позже)
+1 -1
View File
@@ -5,7 +5,7 @@
1. [docs/install-ubuntu-24.04-native.md](../docs/install-ubuntu-24.04-native.md) 1. [docs/install-ubuntu-24.04-native.md](../docs/install-ubuntu-24.04-native.md)
2. `deploy/env.native.example``/opt/security-alert-center/config/sac-api.env` 2. `deploy/env.native.example``/opt/security-alert-center/config/sac-api.env`
3. `deploy/systemd/sac-api.service``/etc/systemd/system/` 3. `deploy/systemd/sac-api.service``/etc/systemd/system/`
4. `deploy/nginx/sac.conf.tls.example``/etc/nginx/sites-available/sac` (wildcard TLS; только `sac.example.com`, чужие Host → 444) 4. `deploy/nginx/sac.conf.tls.example``/etc/nginx/sites-available/sac` (wildcard TLS; только `sac.kalinamall.ru`, чужие Host → 444)
или `sac.conf.example` — только HTTP / отладка или `sac.conf.example` — только HTTP / отладка
5. `deploy/sac-deploy.sh``/opt/sac-deploy.sh` — полное обновление после `git pull` 5. `deploy/sac-deploy.sh``/opt/sac-deploy.sh` — полное обновление после `git pull`
+1 -1
View File
@@ -5,7 +5,7 @@
DATABASE_URL=postgresql+psycopg2://sac:CHANGE_ME_POSTGRES_PASSWORD@127.0.0.1:5432/sac DATABASE_URL=postgresql+psycopg2://sac:CHANGE_ME_POSTGRES_PASSWORD@127.0.0.1:5432/sac
SAC_PUBLIC_URL=https://sac.example.com SAC_PUBLIC_URL=https://sac.kalinamall.ru
JWT_SECRET=CHANGE_ME_openssl_rand_hex_32 JWT_SECRET=CHANGE_ME_openssl_rand_hex_32
# API key для агентов: Authorization: Bearer <ключ> # API key для агентов: Authorization: Bearer <ключ>
+5 -5
View File
@@ -1,18 +1,18 @@
# /etc/nginx/sites-available/sac # /etc/nginx/sites-available/sac
# Production FQDN: sac.example.com # Production FQDN: sac.kalinamall.ru
# API: 127.0.0.1:8000 (systemd sac-api) # API: 127.0.0.1:8000 (systemd sac-api)
# #
# Hostname: только sac.example.com; остальные Host → 444 (см. default_server ниже). # Hostname: только sac.kalinamall.ru; остальные Host → 444 (см. default_server ниже).
# #
# Порядок: 1) этот конфиг (только :80) → nginx -t → reload # Порядок: 1) этот конфиг (только :80) → nginx -t → reload
# 2) certbot --nginx -d sac.example.com (добавит :443 и сертификаты) # 2) certbot --nginx -d sac.kalinamall.ru (добавит :443 и сертификаты)
upstream sac_api { upstream sac_api {
server 127.0.0.1:8000; server 127.0.0.1:8000;
keepalive 8; keepalive 8;
} }
# --- default: отклонить чужие Host (не sac.example.com) --- # --- default: отклонить чужие Host (не sac.kalinamall.ru) ---
server { server {
listen 80 default_server; listen 80 default_server;
listen [::]:80 default_server; listen [::]:80 default_server;
@@ -23,7 +23,7 @@ server {
server { server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
server_name sac.example.com; server_name sac.kalinamall.ru;
location /.well-known/acme-challenge/ { location /.well-known/acme-challenge/ {
root /var/www/html; root /var/www/html;
+5 -5
View File
@@ -1,8 +1,8 @@
# /etc/nginx/sites-available/sac # /etc/nginx/sites-available/sac
# HTTPS с корпоративным wildcard-сертификатом (например VeriSign / DigiCert). # HTTPS с корпоративным wildcard-сертификатом (например VeriSign / DigiCert).
# Покрывает sac.example.com при CN/SAN *.example.com или sac.example.com # Покрывает sac.kalinamall.ru при CN/SAN *.kalinamall.ru или sac.kalinamall.ru
# #
# Hostname: только sac.example.com. Любой другой Host (например ext.example.com # Hostname: только sac.kalinamall.ru. Любой другой Host (например ext.kalinamall.ru
# при ошибке DNS/маршрутизации на IP SAC) получает 444 / reject handshake — # при ошибке DNS/маршрутизации на IP SAC) получает 444 / reject handshake —
# форма входа SAC не отдаётся на «чужом» имени. # форма входа SAC не отдаётся на «чужом» имени.
# #
@@ -32,18 +32,18 @@ server {
ssl_reject_handshake on; ssl_reject_handshake on;
} }
# Редирект HTTP → HTTPS (только sac.example.com) # Редирект HTTP → HTTPS (только sac.kalinamall.ru)
server { server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
server_name sac.example.com; server_name sac.kalinamall.ru;
return 301 https://$host$request_uri; return 301 https://$host$request_uri;
} }
server { server {
listen 443 ssl http2; listen 443 ssl http2;
listen [::]:443 ssl http2; listen [::]:443 ssl http2;
server_name sac.example.com; server_name sac.kalinamall.ru;
# --- замените пути на ваши файлы wildcard --- # --- замените пути на ваши файлы wildcard ---
ssl_certificate /etc/ssl/sac/fullchain.pem; ssl_certificate /etc/ssl/sac/fullchain.pem;
+1 -1
View File
@@ -40,7 +40,7 @@ else
if [ -z "${UPSTREAM}" ]; then if [ -z "${UPSTREAM}" ]; then
UPSTREAM="origin/main" UPSTREAM="origin/main"
fi fi
log "WARN: fast-forward невозможен (часто после force-push на upstream) — reset --hard ${UPSTREAM}" log "WARN: fast-forward невозможен (часто после force-push на kalinamall) — reset --hard ${UPSTREAM}"
sudo -u "${APP_USER}" git -C "${APP_ROOT}" reset --hard "${UPSTREAM}" sudo -u "${APP_USER}" git -C "${APP_ROOT}" reset --hard "${UPSTREAM}"
fi fi
+1 -1
View File
@@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Security Alert Center API (FastAPI) Description=Security Alert Center API (FastAPI)
Documentation=https://github.com/PTah/security-alert-center Documentation=https://git.kalinamall.ru/PapaTramp/security-alert-center
After=network-online.target postgresql.service After=network-online.target postgresql.service
Wants=network-online.target Wants=network-online.target
Requires=postgresql.service Requires=postgresql.service
+1
View File
@@ -18,6 +18,7 @@
| [seaca-mobile.md](seaca-mobile.md) | **Seaca**: коды, устройства, сессия, API | | [seaca-mobile.md](seaca-mobile.md) | **Seaca**: коды, устройства, сессия, API |
| [seaca-fcm.md](seaca-fcm.md) | **Seaca push**: Firebase и `SAC_FCM_*` | | [seaca-fcm.md](seaca-fcm.md) | **Seaca push**: Firebase и `SAC_FCM_*` |
| [workspace-three-repos.md](workspace-three-repos.md) | Multi-root workspace (три репо) | | [workspace-three-repos.md](workspace-three-repos.md) | Multi-root workspace (три репо) |
| [agent-update-backlog.md](agent-update-backlog.md) | **ToDo:** версии агентов, удалённое обновление |
## Порядок чтения ## Порядок чтения
+5 -5
View File
@@ -14,7 +14,7 @@
```ini ```ini
# off | exclusive | dual | fallback # off | exclusive | dual | fallback
UseSAC="exclusive" UseSAC="exclusive"
SAC_URL="https://sac.example.com/api/v1/events" SAC_URL="https://sac.kalinamall.ru/api/v1/events"
SAC_API_KEY="sac_xxxxxxxx" SAC_API_KEY="sac_xxxxxxxx"
SAC_SPOOL_DIR="/var/lib/ssh-monitor/sac-spool" SAC_SPOOL_DIR="/var/lib/ssh-monitor/sac-spool"
SAC_SEND_HEARTBEAT="1" SAC_SEND_HEARTBEAT="1"
@@ -26,7 +26,7 @@ SAC_TIMEOUT_SEC="12"
```powershell ```powershell
$UseSAC = "exclusive" # off | exclusive | dual | fallback $UseSAC = "exclusive" # off | exclusive | dual | fallback
$SacUrl = "https://sac.example.com/api/v1/events" $SacUrl = "https://sac.kalinamall.ru/api/v1/events"
$SacApiKey = "sac_xxxxxxxx" $SacApiKey = "sac_xxxxxxxx"
$SacSpoolDir = "D:\Soft\Logs\sac-spool" $SacSpoolDir = "D:\Soft\Logs\sac-spool"
``` ```
@@ -74,7 +74,7 @@ UI **Настройки** (`/settings`) — правило + параметры
### 1.4. Версии и доставка обновлений ### 1.4. Версии и доставка обновлений
При **любом** изменении агента, влияющем на SAC или поведение на хосте, поднимайте версию и пушьте в **GitHub** (`github.com/PTah`): При **любом** изменении агента, влияющем на SAC или поведение на хосте, поднимайте версию и пушьте в **GitHub** (`github.com/PTah`); для зеркала kalinamall — `.\scripts\Push-Mirror.ps1 kalinamall`:
| Агент | Маркер версии | Как хост узнаёт о новой версии | | Агент | Маркер версии | Как хост узнаёт о новой версии |
|-------|---------------|--------------------------------| |-------|---------------|--------------------------------|
@@ -113,7 +113,7 @@ UI **Настройки** (`/settings`) — правило + параметры
```http ```http
POST /api/v1/events HTTP/1.1 POST /api/v1/events HTTP/1.1
Host: sac.example.com Host: sac.kalinamall.ru
Content-Type: application/json Content-Type: application/json
Authorization: Bearer sac_xxxxxxxx Authorization: Bearer sac_xxxxxxxx
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
@@ -136,7 +136,7 @@ Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
"event_id": "550e8400-e29b-41d4-a716-446655440000", "event_id": "550e8400-e29b-41d4-a716-446655440000",
"created": true, "created": true,
"sac_event_url": "https://sac.kalinamall.ru/api/v1/events/12345", "sac_event_url": "https://sac.kalinamall.ru/api/v1/events/12345",
"problem_id": null "problem_id": null
} }
``` ```
+58
View File
@@ -0,0 +1,58 @@
# ToDo — управление версиями агентов и удалённое обновление
**Статус:** идея, требует проработки архитектуры.
**Связано:** [roadmap.md](roadmap.md) (v0.7), [agent-integration.md](agent-integration.md), UI «Хосты».
---
## Цель
SAC должен:
1. **Отслеживать соответствие версий** агентов (`ssh-monitor`, `RDP-login-monitor`) ожидаемым/минимальным для текущей версии SAC.
2. **Инициировать обновление удалённо** с компьютеров/серверов из UI SAC (или по политике), без ручного захода на каждый хост.
Сейчас версии видны по `host.product_version` и heartbeat; обновление — вручную (`update_ssh_monitor.sh`, `Deploy-LoginMonitor.ps1` / NETLOGON, GPO).
---
## Вопросы для проработки
| # | Тема | Варианты / заметки |
|---|------|-------------------|
| 1 | **Источник истины версии** | Матрица в SAC (min/recommended per `product`); теги в репо; отдельный manifest API |
| 2 | **Сигнал «устарел»** | Problem `agent.version.stale`; severity на карточке хоста; дашборд «не на версии» |
| 3 | **Канал команд** | Pull-only (агент сам тянет по расписанию) vs push-команда от SAC (нужен обратный канал) |
| 4 | **Linux** | Вызов `update_ssh_monitor.sh` по systemd timer + флаг «pending update» из SAC ingest response? Отдельный `agent.command` event? SSH/Ansible с SAC — вне scope агента |
| 5 | **Windows** | GPO/NETLOGON уже есть; remote = триггер `Deploy-LoginMonitor.ps1` через scheduled task / WinRM / SCCM — что допустимо по безопасности |
| 6 | **Авторизация** | Только admin SAC; audit log; подтверждение массового обновления; rate limit |
| 7 | **Отчёт о результате** | `agent.update.started` / `agent.update.success` / `agent.update.failed` в ingest; привязка к `host_id` |
| 8 | **Сеть** | Агент за NAT только исходящий HTTPS — предпочтителен **pull**, не inbound на хост |
| 9 | **Совместимость** | Связка с `schema_version`, откат, окно maintenance |
---
## Черновые критерии приёмки (когда дойдём до реализации)
- [ ] В UI «Хосты»: колонка/бейдж «версия OK / устарела / неизвестна».
- [ ] Настройка минимальной/рекомендуемой версии по продукту в SAC (или env).
- [ ] Problem или оповещение при расхождении (с cooldown).
- [ ] Действие admin: «Запросить обновление» на одном хосте / группе (MVP).
- [ ] Агент подтверждает попытку обновления событием в SAC.
- [ ] Документация: Linux + Windows runbook, ограничения безопасности.
---
## Не в первой итерации (вероятно)
- Полная замена Ansible/GPO/SCCM.
- Обновление SAC с агентов.
- macOS-агент.
---
## См. также
- [analytics-backlog.md](analytics-backlog.md) — отложенная аналитика
- [todo-2026-05-29.md](todo-2026-05-29.md) — оповещения и настройки
- Репозитории агентов: `update_ssh_monitor.sh`, `Deploy-LoginMonitor.ps1`, `update-rdp-monitor.ps1`
+3 -3
View File
@@ -21,7 +21,7 @@ deploy/
sac-deploy.sh → /opt/sac-deploy.sh (полное обновление) sac-deploy.sh → /opt/sac-deploy.sh (полное обновление)
``` ```
**Production URL:** `https://sac.example.com` **Production URL:** `https://sac.kalinamall.ru`
**Конфиг:** `/opt/security-alert-center/config/sac-api.env` (владелец `sac:sac`, chmod 600). **Конфиг:** `/opt/security-alert-center/config/sac-api.env` (владелец `sac:sac`, chmod 600).
**UI:** статика `frontend/dist`, раздаётся через FastAPI (после `npm run build`). **UI:** статика `frontend/dist`, раздаётся через FastAPI (после `npm run build`).
@@ -42,7 +42,7 @@ deploy/
## 2. Сетевые правила ## 2. Сетевые правила
**Агенты →** `https://sac.example.com/api/v1/events` **Агенты →** `https://sac.kalinamall.ru/api/v1/events`
**SAC →** Telegram, SMTP (исходящие) **SAC →** Telegram, SMTP (исходящие)
**Админы →** 443 (UI/API через nginx) **Админы →** 443 (UI/API через nginx)
@@ -66,7 +66,7 @@ deploy/
```bash ```bash
systemctl status sac-api systemctl status sac-api
curl -sS http://127.0.0.1:8000/health curl -sS http://127.0.0.1:8000/health
curl -sS https://sac.example.com/health curl -sS https://sac.kalinamall.ru/health
``` ```
--- ---
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/PTah/security-alert-center/schemas/event-v1.json", "$id": "https://git.kalinamall.ru/PapaTramp/security-alert-center/schemas/event-v1.json",
"title": "Security Alert Center Event v1", "title": "Security Alert Center Event v1",
"description": "Каноническое событие от ssh-monitor или RDP-login-monitor", "description": "Каноническое событие от ssh-monitor или RDP-login-monitor",
"type": "object", "type": "object",
+1 -1
View File
@@ -24,7 +24,7 @@ Docker удобен для локальной отладки или изолир
```bash ```bash
sudo apt install -y git sudo apt install -y git
sudo git clone https://github.com/PTah/security-alert-center.git /opt/security-alert-center sudo git clone https://git.kalinamall.ru/PapaTramp/security-alert-center.git /opt/security-alert-center
sudo chown -R "$USER:$USER" /opt/security-alert-center sudo chown -R "$USER:$USER" /opt/security-alert-center
``` ```
+14 -14
View File
@@ -9,7 +9,7 @@
| Параметр | Пример | | Параметр | Пример |
|----------|--------| |----------|--------|
| FQDN | `sac.example.com` | | FQDN | `sac.kalinamall.ru` |
| IP | `10.0.0.50` | | IP | `10.0.0.50` |
| Каталог | `/opt/security-alert-center` | | Каталог | `/opt/security-alert-center` |
| Пользователь сервиса | `sac` | | Пользователь сервиса | `sac` |
@@ -71,7 +71,7 @@ nginx -v
```bash ```bash
sudo mkdir -p /opt sudo mkdir -p /opt
sudo git clone https://github.com/PTah/security-alert-center.git /opt/security-alert-center sudo git clone https://git.kalinamall.ru/PapaTramp/security-alert-center.git /opt/security-alert-center
``` ```
--- ---
@@ -144,7 +144,7 @@ sudo chmod 600 /opt/security-alert-center/config/sac-api.env
- `DATABASE_URL`с паролем PostgreSQL из шага 4 (если в пароле есть `#`, `@`, `%` — возьмите значение в **кавычки**: `DATABASE_URL="postgresql+psycopg2://..."`) - `DATABASE_URL`с паролем PostgreSQL из шага 4 (если в пароле есть `#`, `@`, `%` — возьмите значение в **кавычки**: `DATABASE_URL="postgresql+psycopg2://..."`)
- `JWT_SECRET``openssl rand -hex 32` - `JWT_SECRET``openssl rand -hex 32`
- `SAC_PUBLIC_URL``https://sac.example.com` - `SAC_PUBLIC_URL``https://sac.kalinamall.ru`
- `SAC_BOOTSTRAP_API_KEY``python3.12 -c "import secrets; print('sac_'+secrets.token_urlsafe(32))"` - `SAC_BOOTSTRAP_API_KEY``python3.12 -c "import secrets; print('sac_'+secrets.token_urlsafe(32))"`
- `SAC_ADMIN_PASSWORD` — пароль входа в веб-UI (отдельно от API key агентов) - `SAC_ADMIN_PASSWORD` — пароль входа в веб-UI (отдельно от API key агентов)
- `EVENT_SCHEMA_PATH=/opt/security-alert-center/schemas/event-schema-v1.json` - `EVENT_SCHEMA_PATH=/opt/security-alert-center/schemas/event-schema-v1.json`
@@ -195,7 +195,7 @@ journalctl -u sac-api -f
## 9. nginx и TLS ## 9. nginx и TLS
Для KalinaMall: **корпоративный wildcard VeriSign** (или DigiCert), не Let's Encrypt. Для KalinaMall: **корпоративный wildcard VeriSign** (или DigiCert), не Let's Encrypt.
Хост `sac.example.com` должен попадать под `*.example.com`. Хост `sac.kalinamall.ru` должен попадать под `*.kalinamall.ru`.
**Подробно:** форматы, scp с Windows/Linux, `.pfx` → PEM, права — **[ssl-certificate.md](ssl-certificate.md)**. **Подробно:** форматы, scp с Windows/Linux, `.pfx` → PEM, права — **[ssl-certificate.md](ssl-certificate.md)**.
@@ -223,7 +223,7 @@ sudo usermod -aG ssl-cert www-data
```bash ```bash
# с вашего компьютера: # с вашего компьютера:
scp fullchain.pem privkey.pem deploy@<IP_SAC>:/tmp/ scp fullchain.pem privkey.pem papatramp@<IP_SAC>:/tmp/
# на сервере: # на сервере:
sudo mv /tmp/fullchain.pem /tmp/privkey.pem /etc/ssl/sac/ sudo mv /tmp/fullchain.pem /tmp/privkey.pem /etc/ssl/sac/
@@ -263,28 +263,28 @@ sudo nginx -t
sudo systemctl reload nginx sudo systemctl reload nginx
``` ```
Шаблон `sac.conf.tls.example` содержит **default_server** с `return 444` (:80) и `ssl_reject_handshake` (:443) для любого Host, кроме `sac.example.com`. Это защищает от ситуации, когда на IP SAC (например `<SAC_SERVER_IP>`) по ошибке DNS попадает `ext.example.com` и открывается форма входа SAC. Шаблон `sac.conf.tls.example` содержит **default_server** с `return 444` (:80) и `ssl_reject_handshake` (:443) для любого Host, кроме `sac.kalinamall.ru`. Это защищает от ситуации, когда на IP SAC (например `192.168.160.145`) по ошибке DNS попадает `ext.kalinamall.ru` и открывается форма входа SAC.
Проверка после reload: Проверка после reload:
```bash ```bash
curl -sS -o /dev/null -w '%{http_code}\n' --resolve sac.example.com:443:127.0.0.1 https://sac.example.com/health curl -sS -o /dev/null -w '%{http_code}\n' --resolve sac.kalinamall.ru:443:127.0.0.1 https://sac.kalinamall.ru/health
# ожидается 200 # ожидается 200
curl -sS -o /dev/null -w '%{http_code}\n' --resolve ext.example.com:443:127.0.0.1 https://ext.example.com/ 2>/dev/null || echo "connection closed (444/reject OK)" curl -sS -o /dev/null -w '%{http_code}\n' --resolve ext.kalinamall.ru:443:127.0.0.1 https://ext.kalinamall.ru/ 2>/dev/null || echo "connection closed (444/reject OK)"
``` ```
Проверка: Проверка:
```bash ```bash
curl -sS https://sac.example.com/health | jq . curl -sS https://sac.kalinamall.ru/health | jq .
``` ```
В `config/sac-api.env` должно быть: `SAC_PUBLIC_URL=https://sac.example.com` В `config/sac-api.env` должно быть: `SAC_PUBLIC_URL=https://sac.kalinamall.ru`
### 9.3. Агенты (ssh-monitor, RDP) и доверие к CA ### 9.3. Агенты (ssh-monitor, RDP) и доверие к CA
- Агенты ходят на `https://sac.example.com/api/v1/events`. - Агенты ходят на `https://sac.kalinamall.ru/api/v1/events`.
- Если используется **внутренний корневой CA**, а не публичный VeriSign/DigiCert в системном store — на Linux/Windows может понадобиться установка корневого сертификата в доверенные (или корпоративный прокси с подменой TLS). - Если используется **внутренний корневой CA**, а не публичный VeriSign/DigiCert в системном store — на Linux/Windows может понадобиться установка корневого сертификата в доверенные (или корпоративный прокси с подменой TLS).
- Для публичного коммерческого wildcard от известного УЦ обычно **дополнительных действий на агентах не нужно**. - Для публичного коммерческого wildcard от известного УЦ обычно **дополнительных действий на агентах не нужно**.
@@ -299,7 +299,7 @@ curl -sS https://sac.example.com/health | jq .
```bash ```bash
sudo cp deploy/nginx/sac.conf.example /etc/nginx/sites-available/sac sudo cp deploy/nginx/sac.conf.example /etc/nginx/sites-available/sac
# ... nginx -t, reload, затем: # ... nginx -t, reload, затем:
sudo certbot --nginx -d sac.example.com sudo certbot --nginx -d sac.kalinamall.ru
``` ```
--- ---
@@ -317,7 +317,7 @@ ls -la dist/index.html
sudo systemctl restart sac-api sudo systemctl restart sac-api
``` ```
В `config/sac-api.env` задайте `SAC_ADMIN_PASSWORD`, затем откройте `https://sac.example.com/` (логин `SAC_ADMIN_USERNAME`, по умолчанию `admin`). В `config/sac-api.env` задайте `SAC_ADMIN_PASSWORD`, затем откройте `https://sac.kalinamall.ru/` (логин `SAC_ADMIN_USERNAME`, по умолчанию `admin`).
После обновления с multi-user (миграция `009_sac_users`): выполните `alembic upgrade head` в каталоге `backend/`. При первом старте API создаст пользователя **admin** из `SAC_ADMIN_*`, если таблица `sac_users` пуста. После обновления с multi-user (миграция `009_sac_users`): выполните `alembic upgrade head` в каталоге `backend/`. При первом старте API создаст пользователя **admin** из `SAC_ADMIN_*`, если таблица `sac_users` пуста.
@@ -343,7 +343,7 @@ curl -sS http://127.0.0.1:8000/health | jq .
Через nginx (после TLS): Через nginx (после TLS):
```bash ```bash
curl -sS https://sac.example.com/health | jq . curl -sS https://sac.kalinamall.ru/health | jq .
``` ```
Тест ingest (подставьте `SAC_BOOTSTRAP_API_KEY`): Тест ingest (подставьте `SAC_BOOTSTRAP_API_KEY`):
+1 -1
View File
@@ -14,7 +14,7 @@
```bash ```bash
# см. полный чеклист в native-руководстве # см. полный чеклист в native-руководстве
sudo apt update && sudo apt install -y git postgresql nginx python3.12 python3.12-venv sudo apt update && sudo apt install -y git postgresql nginx python3.12 python3.12-venv
sudo git clone https://github.com/PTah/security-alert-center.git /opt/security-alert-center sudo git clone https://git.kalinamall.ru/PapaTramp/security-alert-center.git /opt/security-alert-center
# … PostgreSQL, venv, config/sac-api.env, systemd, nginx # … PostgreSQL, venv, config/sac-api.env, systemd, nginx
``` ```
+3 -3
View File
@@ -1,8 +1,8 @@
# SAC production — пример статуса развёртывания # SAC production — текущий статус (KalinaMall)
Ориентир для продолжения работ. Обновляйте при смене этапа. Ориентир для продолжения работ. Обновляйте при смене этапа.
**Сервер:** Ubuntu 24.04, FQDN `sac.example.com` **Сервер:** Ubuntu 24.04, FQDN `sac.kalinamall.ru`
**Каталог:** `/opt/security-alert-center` **Каталог:** `/opt/security-alert-center`
**Пользователь сервиса:** `sac` (git, venv, npm — только от него) **Пользователь сервиса:** `sac` (git, venv, npm — только от него)
**Конфиг:** `/opt/security-alert-center/config/sac-api.env` **Конфиг:** `/opt/security-alert-center/config/sac-api.env`
@@ -39,7 +39,7 @@ sudo /opt/sac-deploy.sh
| Параметр | Значение | | Параметр | Значение |
|----------|----------| |----------|----------|
| Хост | `ubabuba` / `10.10.36.9` | | Хост | `ubabuba` / `10.10.36.9` |
| Репозиторий | `github.com/PTah/ssh-monitor` (`main`, есть `sac-client.sh`) | | Репозиторий | `git.kalinamall.ru/PapaTramp/ssh-monitor` (`main`, есть `sac-client.sh`) |
| `UseSAC` | пилот **exclusive** — см. [pilot-2.1-exclusive.md](pilot-2.1-exclusive.md) | | `UseSAC` | пилот **exclusive** — см. [pilot-2.1-exclusive.md](pilot-2.1-exclusive.md) |
| Сервис | `ssh-monitor.service`**active** | | Сервис | `ssh-monitor.service`**active** |
| `--check-sac` | OK (health + ingest `agent.test` 202) | | `--check-sac` | OK (health + ingest `agent.test` 202) |
+1 -1
View File
@@ -17,7 +17,7 @@ sudo /opt/scripts/update_ssh_monitor.sh
```ini ```ini
UseSAC=exclusive UseSAC=exclusive
SAC_URL=https://sac.example.com SAC_URL=https://sac.kalinamall.ru
SAC_API_KEY=sac_xxxxxxxx SAC_API_KEY=sac_xxxxxxxx
SAC_TLS_SKIP_VERIFY=0 SAC_TLS_SKIP_VERIFY=0
SAC_SPOOL_DIR=/var/lib/ssh-monitor/sac-spool SAC_SPOOL_DIR=/var/lib/ssh-monitor/sac-spool
+17 -3
View File
@@ -66,7 +66,7 @@
**Цель:** нативный Android-клиент и push по тем же правилам оповещений, что Telegram/email/webhook. **Цель:** нативный Android-клиент и push по тем же правилам оповещений, что Telegram/email/webhook.
Репозиторий клиента: [seaca](https://github.com/PTah/seaca) (Kotlin, Jetpack Compose, FCM). Репозиторий клиента: [seaca](https://git.kalinamall.ru/PapaTramp/seaca) (Kotlin, Jetpack Compose, FCM).
**Сервер SAC:** **Сервер SAC:**
@@ -77,7 +77,21 @@
**Не в мобильном клиенте:** пользователи SAC, каналы Telegram/SMTP/webhook, админ-настройки. **Не в мобильном клиенте:** пользователи SAC, каналы Telegram/SMTP/webhook, админ-настройки.
Подробный план клиента — [seaca/docs/ROADMAP.md](https://github.com/PTah/seaca/blob/main/docs/ROADMAP.md). Подробный план клиента — [seaca/docs/ROADMAP.md](https://git.kalinamall.ru/PapaTramp/seaca/src/branch/main/docs/ROADMAP.md).
---
## v0.7 — Версии агентов и удалённое обновление (ToDo)
**Цель:** SAC контролирует актуальность `ssh-monitor` и `RDP-login-monitor` на хостах и может **инициировать обновление** без обхода всех машин вручную.
- Сверка `product_version` с политикой SAC (min / recommended)
- UI: статус версии на карточке хоста, список отстающих
- Problems / оповещения при устаревании агента
- Команда обновления (предпочтительно **pull** с хоста: агент видит «нужно обновиться» и запускает свой updater)
- События `agent.update.*` в ingest для аудита
**Проработка:** [agent-update-backlog.md](agent-update-backlog.md).
--- ---
@@ -96,7 +110,7 @@
| v0.1 | — (не требуется) | — | | v0.1 | — (не требуется) | — |
| v0.2 | TBD (тег с UseSAC) | TBD | | v0.2 | TBD (тег с UseSAC) | TBD |
| v0.3+ | совместимость schema 1.0 | совместимость schema 1.0 | | v0.3+ | совместимость schema 1.0 | совместимость schema 1.0 |
| v0.6 | — | — (мобильный клиент [seaca](https://github.com/PTah/seaca), не агент) | | v0.6 | — | — (мобильный клиент [seaca](https://git.kalinamall.ru/PapaTramp/seaca), не агент) |
При breaking change схемы — `schema_version: 1.1` с поддержкой 1.0 на ingest. При breaking change схемы — `schema_version: 1.1` с поддержкой 1.0 на ingest.
+3 -3
View File
@@ -1,6 +1,6 @@
# Runbook: эксплуатация SAC # Runbook: эксплуатация SAC
Краткие процедуры для prod (`sac.example.com`, native install в `/opt/security-alert-center`). Краткие процедуры для prod (`sac.kalinamall.ru`, native install в `/opt/security-alert-center`).
## Деплой приложения ## Деплой приложения
@@ -13,7 +13,7 @@ sudo /opt/sac-deploy.sh
Проверка: Проверка:
```bash ```bash
curl -sS https://sac.example.com/health | jq . curl -sS https://sac.kalinamall.ru/health | jq .
``` ```
Ожидается `status: ok`, `database: ok`. При устаревших heartbeat агентов — `status: degraded`, поле `hosts_stale` > 0. Ожидается `status: ok`, `database: ok`. При устаревших heartbeat агентов — `status: degraded`, поле `hosts_stale` > 0.
@@ -101,4 +101,4 @@ sudo -u sac bash -lc 'cd /opt/security-alert-center/backend && .venv/bin/python
**Устаревший heartbeat** — увеличить `SAC_HEARTBEAT_STALE_MINUTES` или проверить `agent.heartbeat` на хосте. **Устаревший heartbeat** — увеличить `SAC_HEARTBEAT_STALE_MINUTES` или проверить `agent.heartbeat` на хосте.
**Чужой Host на IP SAC (форма SAC на ext.example.com)** — в nginx только `server_name sac.example.com`; default_server → 444 / `ssl_reject_handshake`. Обновить конфиг из `deploy/nginx/sac.conf.tls.example`, `sudo nginx -t && sudo systemctl reload nginx`. HAProxy на этом не решается — правка на **<SAC_SERVER_IP>** (nginx SAC). **Чужой Host на IP SAC (форма SAC на ext.kalinamall.ru)** — в nginx только `server_name sac.kalinamall.ru`; default_server → 444 / `ssl_reject_handshake`. Обновить конфиг из `deploy/nginx/sac.conf.tls.example`, `sudo nginx -t && sudo systemctl reload nginx`. HAProxy на этом не решается — правка на **192.168.160.145** (nginx SAC).
+5 -5
View File
@@ -1,6 +1,6 @@
# Seaca — мобильный клиент SAC # Seaca — мобильный клиент SAC
Репозиторий приложения: [seaca](https://github.com/PTah/seaca). Репозиторий приложения: [seaca](https://git.kalinamall.ru/PapaTramp/seaca).
Требуется SAC **≥ 0.9.0** с применённой миграцией `014`. Требуется SAC **≥ 0.9.0** с применённой миграцией `014`.
--- ---
@@ -40,7 +40,7 @@
| Способ входа | **Логин и пароль + код** — в приложении нужны учётные данные SAC; **Только код** — пароль не нужен, пользователь должен быть выбран в форме | | Способ входа | **Логин и пароль + код** — в приложении нужны учётные данные SAC; **Только код** — пароль не нужен, пользователь должен быть выбран в форме |
| Срок (часов) | Код перестаёт принимать **новые** привязки; уже подключённые телефоны не отключаются | | Срок (часов) | Код перестаёт принимать **новые** привязки; уже подключённые телефоны не отключаются |
Код показывается **один раз** после «Выдать код». Передайте оператору: URL сервера (**`https://sac-api.example.com`** для Seaca; веб-админка — `https://sac.example.com`), код `sacmob_…`, при режиме password — логин/пароль SAC. Код показывается **один раз** после «Выдать код». Передайте оператору: URL сервера (**`https://sac-api.kalinamall.ru`** для Seaca; веб-админка — `https://sac.kalinamall.ru`), код `sacmob_…`, при режиме password — логин/пароль SAC.
### Подключённые устройства ### Подключённые устройства
@@ -81,10 +81,10 @@ SAC_FCM_SERVICE_ACCOUNT_JSON=/etc/security-alert-center/fcm-service-account.json
| Hostname | Clients | IP allowlist | | Hostname | Clients | IP allowlist |
|----------|---------|--------------| |----------|---------|--------------|
| `sac.example.com` | Web UI | yes (`git-sac-allowed`) | | `sac.kalinamall.ru` | Web UI | yes (`git-sac-allowed`) |
| `sac-api.example.com` | Seaca, `/api/v1/mobile/*` | no (enroll + JWT) | | `sac-api.kalinamall.ru` | Seaca, `/api/v1/mobile/*` | no (enroll + JWT) |
Both resolve to the same SAC server (**<SAC_SERVER_IP>**). nginx must accept both names in `server_name`. See [reverse-proxy/docs/sac-access.md](https://github.com/PTah/reverse-proxy/blob/main/docs/sac-access.md). Both resolve to the same SAC server (**192.168.160.145**). nginx must accept both names in `server_name`. See [reverse-proxy/docs/sac-access.md](https://git.kalinamall.ru/PapaTramp/reverse-proxy/src/branch/main/docs/sac-access.md).
--- ---
+20 -20
View File
@@ -1,6 +1,6 @@
# TLS-сертификат для SAC (wildcard VeriSign / DigiCert) # TLS-сертификат для SAC (wildcard VeriSign / DigiCert)
Куда положить файлы, в каком формате, как скопировать на сервер `sac.example.com`. Куда положить файлы, в каком формате, как скопировать на сервер `sac.kalinamall.ru`.
Связано: [install-ubuntu-24.04-native.md](install-ubuntu-24.04-native.md) §9, шаблон `deploy/nginx/sac.conf.tls.example`. Связано: [install-ubuntu-24.04-native.md](install-ubuntu-24.04-native.md) §9, шаблон `deploy/nginx/sac.conf.tls.example`.
@@ -17,7 +17,7 @@ nginx читает **два PEM-файла** (текст, начинается
Имена файлов можно другие, если пути совпадают с `ssl_certificate` / `ssl_certificate_key` в `/etc/nginx/sites-available/sac`. Имена файлов можно другие, если пути совпадают с `ssl_certificate` / `ssl_certificate_key` в `/etc/nginx/sites-available/sac`.
**Покрытие имени:** в сертификате должен быть CN или SAN вида `*.example.com` (wildcard) или `sac.example.com`. **Покрытие имени:** в сертификате должен быть CN или SAN вида `*.kalinamall.ru` (wildcard) или `sac.kalinamall.ru`.
--- ---
@@ -85,7 +85,7 @@ sudo -u www-data test -r /etc/ssl/sac/privkey.pem && echo "key OK" || echo "key
Если уже есть `fullchain.pem` и `privkey.pem` на ПК: Если уже есть `fullchain.pem` и `privkey.pem` на ПК:
```bash ```bash
scp fullchain.pem privkey.pem deploy@SAC_SERVER:/tmp/ scp fullchain.pem privkey.pem papatramp@SAC_SERVER:/tmp/
``` ```
На сервере: На сервере:
@@ -103,11 +103,11 @@ sudo rm -f /tmp/fullchain.pem /tmp/privkey.pem
### 4.2. С Windows (PowerShell / pscp) ### 4.2. С Windows (PowerShell / pscp)
Файлы, например, в `D:\Certs\example\`: Файлы, например, в `D:\Certs\kalinamall\`:
```powershell ```powershell
scp D:\Certs\example\fullchain.pem deploy@SAC_SERVER:/tmp/ scp D:\Certs\kalinamall\fullchain.pem papatramp@SAC_SERVER:/tmp/
scp D:\Certs\example\privkey.pem deploy@SAC_SERVER:/tmp/ scp D:\Certs\kalinamall\privkey.pem papatramp@SAC_SERVER:/tmp/
``` ```
Дальше на сервере — те же `sudo mv` и `chown`, что в §4.1. Дальше на сервере — те же `sudo mv` и `chown`, что в §4.1.
@@ -115,7 +115,7 @@ scp D:\Certs\example\privkey.pem deploy@SAC_SERVER:/tmp/
Если есть только **`wildcard.pfx`**: Если есть только **`wildcard.pfx`**:
```powershell ```powershell
scp D:\Certs\example\wildcard.pfx deploy@SAC_SERVER:/tmp/ scp D:\Certs\kalinamall\wildcard.pfx papatramp@SAC_SERVER:/tmp/
``` ```
Распаковка на **сервере** (§4.3). Распаковка на **сервере** (§4.3).
@@ -128,19 +128,19 @@ scp D:\Certs\example\wildcard.pfx deploy@SAC_SERVER:/tmp/
mkdir -p /tmp/certs mkdir -p /tmp/certs
# один файл # один файл
scp user@certs-host.example.com:/path/to/wildcard_example_com.pfx /tmp/certs/ scp user@certs-host.example.com:/path/to/wildcard_kalinamall_ru.pfx /tmp/certs/
# несколько файлов # несколько файлов
scp user@certs-host:/path/to/fullchain.pem user@certs-host:/path/to/privkey.pem /tmp/certs/ scp user@certs-host:/path/to/fullchain.pem user@certs-host:/path/to/privkey.pem /tmp/certs/
# нестандартный SSH-порт (у scp заглавная P) # нестандартный SSH-порт (у scp заглавная P)
scp -P 2222 user@certs-host:/path/to/wildcard_example_com.pfx /tmp/certs/ scp -P 2222 user@certs-host:/path/to/wildcard_kalinamall_ru.pfx /tmp/certs/
``` ```
С другого сервера **отдать** на SAC (push — команда на сервере-источнике): С другого сервера **отдать** на SAC (push — команда на сервере-источнике):
```bash ```bash
scp /path/to/wildcard_example_com.pfx deploy@<IP_SAC>:/tmp/certs/ scp /path/to/wildcard_kalinamall_ru.pfx papatramp@<IP_SAC>:/tmp/certs/
``` ```
Нужен SSH-доступ и право **читать** файл на источнике. Нужен SSH-доступ и право **читать** файл на источнике.
@@ -162,13 +162,13 @@ sudo mkdir -p /tmp/certs
cd /tmp/certs cd /tmp/certs
# замените имя PFX; Enter Import Password: — пароль от PFX (не sudo) # замените имя PFX; Enter Import Password: — пароль от PFX (не sudo)
sudo openssl pkcs12 -legacy -in wildcard_example_com.pfx -nocerts -nodes \ sudo openssl pkcs12 -legacy -in wildcard_kalinamall_ru.pfx -nocerts -nodes \
-out privkey.pem -out privkey.pem
sudo openssl pkcs12 -legacy -in wildcard_example_com.pfx -clcerts -nokeys \ sudo openssl pkcs12 -legacy -in wildcard_kalinamall_ru.pfx -clcerts -nokeys \
-out server.crt -out server.crt
sudo openssl pkcs12 -legacy -in wildcard_example_com.pfx -cacerts -nokeys \ sudo openssl pkcs12 -legacy -in wildcard_kalinamall_ru.pfx -cacerts -nokeys \
-out ca-chain.crt -out ca-chain.crt
# fullchain = сервер + промежуточные (порядок важен) # fullchain = сервер + промежуточные (порядок важен)
@@ -183,20 +183,20 @@ sudo chmod 750 /etc/ssl/sac
# очистка временных файлов # очистка временных файлов
sudo shred -u server.crt ca-chain.crt privkey.pem 2>/dev/null sudo shred -u server.crt ca-chain.crt privkey.pem 2>/dev/null
sudo rm -f /tmp/certs/wildcard_example_com.pfx sudo rm -f /tmp/certs/wildcard_kalinamall_ru.pfx
``` ```
Если `-legacy` недостаточно: Если `-legacy` недостаточно:
```bash ```bash
sudo openssl pkcs12 -provider legacy -provider default \ sudo openssl pkcs12 -provider legacy -provider default \
-in wildcard_example_com.pfx -nocerts -nodes -out privkey.pem -in wildcard_kalinamall_ru.pfx -nocerts -nodes -out privkey.pem
``` ```
Проверить содержимое PFX: Проверить содержимое PFX:
```bash ```bash
sudo openssl pkcs12 -legacy -in wildcard_example_com.pfx -info -noout sudo openssl pkcs12 -legacy -in wildcard_kalinamall_ru.pfx -info -noout
``` ```
Пароль PFX вводится интерактивно (`Enter Import Password`). **Не** коммитьте PFX и ключ в git. Пароль PFX вводится интерактивно (`Enter Import Password`). **Не** коммитьте PFX и ключ в git.
@@ -207,13 +207,13 @@ sudo openssl pkcs12 -legacy -in wildcard_example_com.pfx -info -noout
Типичный набор от VeriSign / DigiCert: Типичный набор от VeriSign / DigiCert:
- `star_example_com.crt` — ваш wildcard - `star_kalinamall_ru.crt` — ваш wildcard
- `DigiCertCA.crt` / `intermediate.crt` — промежуточный - `DigiCertCA.crt` / `intermediate.crt` — промежуточный
- (иногда) `TrustedRoot.crt` — корневой **не** добавляйте в fullchain для nginx - (иногда) `TrustedRoot.crt` — корневой **не** добавляйте в fullchain для nginx
```bash ```bash
# на ПК или на сервере, в каталоге с исходниками: # на ПК или на сервере, в каталоге с исходниками:
cat star_example_com.crt intermediate.crt > fullchain.pem cat star_kalinamall_ru.crt intermediate.crt > fullchain.pem
# ключ переименовать: # ключ переименовать:
cp private.key privkey.pem cp private.key privkey.pem
``` ```
@@ -269,13 +269,13 @@ ssl_certificate_key /etc/ssl/sac/privkey.pem;
sudo ln -sf /etc/nginx/sites-available/sac /etc/nginx/sites-enabled/sac sudo ln -sf /etc/nginx/sites-available/sac /etc/nginx/sites-enabled/sac
sudo nginx -t sudo nginx -t
sudo systemctl reload nginx sudo systemctl reload nginx
curl -sS https://sac.example.com/health | jq . curl -sS https://sac.kalinamall.ru/health | jq .
``` ```
В приложении SAC (`/opt/security-alert-center/config/sac-api.env`): В приложении SAC (`/opt/security-alert-center/config/sac-api.env`):
```ini ```ini
SAC_PUBLIC_URL=https://sac.example.com SAC_PUBLIC_URL=https://sac.kalinamall.ru
``` ```
--- ---
+1 -1
View File
@@ -45,7 +45,7 @@
```bash ```bash
API_KEY="sac_…" API_KEY="sac_…"
BASE="https://sac.example.com" BASE="https://sac.kalinamall.ru"
# warning → должен уйти в TG при min=warning # warning → должен уйти в TG при min=warning
curl -sS -X POST "$BASE/api/v1/events" \ curl -sS -X POST "$BASE/api/v1/events" \
+1
View File
@@ -84,5 +84,6 @@ UseSAC=exclusive→ только SAC ingest; TG/email = настройки SAC (
- [TZ.md](TZ.md) §4.6 F-NOT-01 … F-NOT-05 - [TZ.md](TZ.md) §4.6 F-NOT-01 … F-NOT-05
- [agent-integration.md](agent-integration.md) — режимы UseSAC - [agent-integration.md](agent-integration.md) — режимы UseSAC
- [agent-update-backlog.md](agent-update-backlog.md) — **ToDo:** версии агентов и удалённое обновление из SAC
- `backend/app/config.py``telegram_*` - `backend/app/config.py``telegram_*`
- `backend/app/services/telegram_notify.py` - `backend/app/services/telegram_notify.py`
+4 -4
View File
@@ -9,7 +9,7 @@
| # | Задача | Статус | | # | Задача | Статус |
|---|--------|--------| |---|--------|--------|
| 0.1–0.3 | ТЗ, архитектура, схема, deployment | ✅ | | 0.1–0.3 | ТЗ, архитектура, схема, deployment | ✅ |
| 0.5 | Репозиторий GitHub + prod deployment | ✅ | | 0.5 | Репозиторий GitHub + зеркало kalinamall | ✅ |
| 0.6 | Multi-root workspace | ✅ | | 0.6 | Multi-root workspace | ✅ |
--- ---
@@ -37,7 +37,7 @@
|---|--------|--------| |---|--------|--------|
| 1B.1 | Scaffold backend FastAPI | ✅ | | 1B.1 | Scaffold backend FastAPI | ✅ |
| 1B.2 | PostgreSQL + Alembic (hosts, events, api_keys) | ✅ | | 1B.2 | PostgreSQL + Alembic (hosts, events, api_keys) | ✅ |
| 1B.3 | `POST /api/v1/events`, `GET /health` | ✅ prod `sac.example.com` | | 1B.3 | `POST /api/v1/events`, `GET /health` | ✅ prod `sac.kalinamall.ru` |
| 1B.4 | systemd + nginx + TLS | ✅ | | 1B.4 | systemd + nginx + TLS | ✅ |
| 1B.5 | Валидация JSON Schema v1 | ✅ | | 1B.5 | Валидация JSON Schema v1 | ✅ |
@@ -90,7 +90,7 @@
- [x] `d2-1` UI Problems: список, фильтры, `Ack/Resolve`, карточка с таймлайном - [x] `d2-1` UI Problems: список, фильтры, `Ack/Resolve`, карточка с таймлайном
- [x] `d2-2` Dashboard: top hosts/types, `open vs resolved 24h`, drill-down - [x] `d2-2` Dashboard: top hosts/types, `open vs resolved 24h`, drill-down
- [x] `d2-3` Ops: retention, health checks, runbook backup/restore/deploy - [x] `d2-3` Ops: retention, health checks, runbook backup/restore/deploy
- [x] `dod` DoD: нет дублей `event_id`, Problems e2e, 3 правила, UI MVP, docs, push в prod - [x] `dod` DoD: нет дублей `event_id`, Problems e2e, 3 правила, UI MVP, docs, push в kalinamall
### Отображение хостов (`display_name`, как `SERVER_DISPLAY_NAME` у ssh) ### Отображение хостов (`display_name`, как `SERVER_DISPLAY_NAME` у ssh)
@@ -122,7 +122,7 @@
- [x] `14:3016:00` Dashboard: `open/resolved 24h` + drill-down - [x] `14:3016:00` Dashboard: `open/resolved 24h` + drill-down
- [x] `16:0017:00` retention job (`events 3090d`, `problems 180d+`) - [x] `16:0017:00` retention job (`events 3090d`, `problems 180d+`)
- [x] `17:0018:00` health checks (DB/worker/heartbeat stale) - [x] `17:0018:00` health checks (DB/worker/heartbeat stale)
- [x] `18:0019:00` runbook + финальный push в prod + freeze dev - [x] `18:0019:00` runbook + финальный push в kalinamall + freeze dev
### Неделя после freeze (только тестирование) ### Неделя после freeze (только тестирование)
+40 -14
View File
@@ -6,22 +6,26 @@
## 1. Расположение репозиториев (пример) ## 1. Расположение репозиториев (пример)
| Имя в workspace | Публичный remote | | Имя в workspace | Путь (пример Windows) | Remote |
|-----------------|------------------| |-----------------|----------------------|--------|
| `ssh-monitor` | github.com/PTah/ssh-monitor | | `ssh-monitor` | `D:\Soft\Git\ssh-monitor` | git.kalinamall.ru/PapaTramp/ssh-monitor |
| `rdp-monitor` | github.com/PTah/RDP-login-monitor | | `rdp-monitor` | `D:\Soft\Git\RDP-login-monitor` | git.kalinamall.ru/PapaTramp/RDP-login-monitor |
| `security-alert-center` | github.com/PTah/security-alert-center | | `security-alert-center` | `D:\Soft\Git\security-alert-center` | git.kalinamall.ru/PapaTramp/security-alert-center |
Пути подставьте свои.
--- ---
## 2. Файл workspace ## 2. Файл workspace
Создайте `security-monitors.code-workspace`:
```json ```json
{ {
"folders": [ "folders": [
{ "path": "ssh-monitor", "name": "ssh-monitor" }, { "path": "D:/Soft/Git/ssh-monitor", "name": "ssh-monitor" },
{ "path": "RDP-login-monitor", "name": "rdp-monitor" }, { "path": "D:/Soft/Git/RDP-login-monitor", "name": "rdp-monitor" },
{ "path": "security-alert-center", "name": "security-alert-center" } { "path": "D:/Soft/Git/security-alert-center", "name": "security-alert-center" }
], ],
"settings": { "settings": {
"files.eol": "\n" "files.eol": "\n"
@@ -29,22 +33,44 @@
} }
``` ```
Открыть: **File → Open Workspace from File**.
--- ---
## 3. Соглашения ## 3. Порядок работ по фазам
| Фаза | Где править |
|------|-------------|
| 0 — ТЗ | `security-alert-center/docs/` |
| 1 — MVP SAC | `security-alert-center/backend`, `frontend` |
| 2 — UseSAC | `ssh-monitor`, `RDP-login-monitor` + мелкие правки SAC при необходимости |
---
## 4. Соглашения
- Контракт событий: `security-alert-center/schemas/event-schema-v1.json`**единственный источник истины**. - Контракт событий: `security-alert-center/schemas/event-schema-v1.json`**единственный источник истины**.
- Изменение схемы → обновить ТЗ + версию `schema_version` + оба агента. - Изменение схемы → обновить ТЗ + версию `schema_version` + оба агента.
- Коммиты в три репо **раздельные**; связь через теги/версии в README. - Коммиты в три репо **раздельные**; связь через теги/версии в README (см. [roadmap.md](roadmap.md)).
```bash ```bash
git clone https://github.com/PTah/security-alert-center.git git add …
git clone https://github.com/PTah/ssh-monitor.git git commit -m "…"
git clone https://github.com/PTah/RDP-login-monitor.git git push kalinamall main
``` ```
--- ---
## 5. URL репозиториев
| Проект | URL |
|--------|-----|
| security-alert-center | `https://git.kalinamall.ru/PapaTramp/security-alert-center.git` |
| ssh-monitor | `https://git.kalinamall.ru/PapaTramp/ssh-monitor.git` |
| RDP-login-monitor | `https://git.kalinamall.ru/PapaTramp/RDP-login-monitor.git` |
---
## См. также ## См. также
- [work-plan.md](work-plan.md) - [work-plan.md](work-plan.md) фаза 0.6
+1 -1
View File
@@ -1,4 +1,4 @@
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */ /** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
export const APP_NAME = "Security Alert Center"; export const APP_NAME = "Security Alert Center";
export const APP_VERSION = "0.9.10"; export const APP_VERSION = "0.9.11";
export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`; export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/PTah/security-alert-center/schemas/event-v1.json", "$id": "https://git.kalinamall.ru/PapaTramp/security-alert-center/schemas/event-v1.json",
"title": "Security Alert Center Event v1", "title": "Security Alert Center Event v1",
"description": "Каноническое событие от ssh-monitor или RDP-login-monitor", "description": "Каноническое событие от ssh-monitor или RDP-login-monitor",
"type": "object", "type": "object",