Реализация на отдельных двух машинах с протестированным взаимодействием по проверке сообщений
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
"""Validate public deployment config separately from runtime secrets."""
|
||||
from __future__ import annotations
|
||||
|
||||
import ipaddress
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
@@ -16,15 +17,16 @@ REQUIRED_CONFIG = {
|
||||
"PUBLIC_API_URL", "PUBLIC_AUTH_URL", "KEYCLOAK_PUBLIC_URL",
|
||||
"KEYCLOAK_INTERNAL_URL", "KEYCLOAK_REALM", "KEYCLOAK_SMS_SERVICE_URL",
|
||||
"IDGTL_SMS_BASE_URL", "IDGTL_SMS_CALLBACK_PUBLIC_URL",
|
||||
"MESSAGE_SAFETY_URL", "MESSAGE_SAFETY_EXTRA_HOST",
|
||||
"MESSAGE_SAFETY_CA_HOST_PATH", "MESSAGE_SAFETY_API_PREFIX",
|
||||
}
|
||||
REQUIRED_RUNTIME = {
|
||||
"DATABASE_URL", "BITRIX_DATABASE_URL", "BITRIX_SYNC_DATABASE_URL",
|
||||
"DATABASE_URL", "BITRIX_DATABASE_URL",
|
||||
"SMS_DATABASE_URL", "KEYCLOAK_DB_PASSWORD", "REDIS_URL",
|
||||
"REDIS_REALTIME_URL", "MESSAGE_SAFETY_REDIS_URL", "REDIS_API_PASSWORD",
|
||||
"REDIS_SAFETY_PASSWORD", "REDIS_HEALTH_PASSWORD",
|
||||
"REDIS_REALTIME_URL", "REDIS_API_PASSWORD", "REDIS_HEALTH_PASSWORD",
|
||||
"MESSAGE_SAFETY_SERVICE_TOKEN", "BITRIX_LOCAL_APP_INTERNAL_TOKEN",
|
||||
"BITRIX_INTERNAL_API_TOKEN", "BITRIX_API_FORWARD_TOKEN",
|
||||
"BITRIX_API_INBOX_TOKEN", "BITRIX_SYNC_SERVICE_TOKEN",
|
||||
"BITRIX_API_INBOX_TOKEN",
|
||||
"KEYCLOAK_SETTINGS_BRIDGE_TOKEN", "KEYCLOAK_OTP_HMAC_KEY",
|
||||
"KEYCLOAK_SMS_SERVICE_TOKEN", "SMS_SERVICE_TOKEN",
|
||||
"IDGTL_SMS_API_KEY", "IDGTL_SMS_CALLBACK_USERNAME",
|
||||
@@ -34,18 +36,15 @@ REQUIRED_RUNTIME = {
|
||||
}
|
||||
OPTIONAL_SECRET_KEYS = {
|
||||
"BITRIX_CLIENT_SECRET", "BITRIX_APPLICATION_TOKEN",
|
||||
"BITRIX_SYNC_CRM_WEBHOOK_URL", "BITRIX_SYNC_WEBHOOK_TOKEN",
|
||||
"KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY",
|
||||
"NOTIFICATIONS_TOKEN_PRODUCER_TEST",
|
||||
"OTEL_REMOTE_AUTH_HEADER", "KEYCLOAK_OTP_MOCK_CODE",
|
||||
}
|
||||
FORBIDDEN_CONFIG_KEYS = REQUIRED_RUNTIME | OPTIONAL_SECRET_KEYS | {
|
||||
"BITRIX_SYNC_APP_DATABASE_URL", "PG_BACKUP_DSN",
|
||||
"PG_BACKUP_DSN",
|
||||
}
|
||||
DSN_KEYS = {
|
||||
"DATABASE_URL", "BITRIX_DATABASE_URL", "BITRIX_SYNC_APP_DATABASE_URL",
|
||||
"BITRIX_SYNC_DATABASE_URL", "MESSAGE_SAFETY_DATABASE_URL",
|
||||
"SMS_DATABASE_URL", "PG_BACKUP_DSN",
|
||||
"DATABASE_URL", "BITRIX_DATABASE_URL", "SMS_DATABASE_URL", "PG_BACKUP_DSN",
|
||||
}
|
||||
PLACEHOLDER = re.compile(r"(change-me|example\.(com|ru|invalid)|<[^>]+>)", re.I)
|
||||
|
||||
@@ -148,7 +147,6 @@ def validate_runtime(config: dict[str, str], runtime: dict[str, str], errors: li
|
||||
redis_contract = (
|
||||
("REDIS_URL", "api_backend", "REDIS_API_PASSWORD", "/0"),
|
||||
("REDIS_REALTIME_URL", "api_backend", "REDIS_API_PASSWORD", "/1"),
|
||||
("MESSAGE_SAFETY_REDIS_URL", "message_safety", "REDIS_SAFETY_PASSWORD", "/2"),
|
||||
)
|
||||
for key, username, password_key, database in redis_contract:
|
||||
parsed = urlparse(runtime.get(key, ""))
|
||||
@@ -180,23 +178,47 @@ def validate_shared(env: dict[str, str], errors: list[str]) -> None:
|
||||
):
|
||||
if env.get(key) and urlparse(env[key]).scheme != "https":
|
||||
errors.append(f"{key}: публичный URL должен использовать https")
|
||||
public_origin = f"https://{env.get('PUBLIC_HOST', '')}"
|
||||
expected_public_urls = {
|
||||
"PUBLIC_WEB_URL": public_origin,
|
||||
"PUBLIC_API_URL": f"{public_origin}/api",
|
||||
"PUBLIC_AUTH_URL": f"{public_origin}/auth",
|
||||
"KEYCLOAK_PUBLIC_URL": f"{public_origin}/auth",
|
||||
}
|
||||
for key, expected in expected_public_urls.items():
|
||||
if env.get(key, "").rstrip("/") != expected:
|
||||
errors.append(f"{key}: должен быть производным от PUBLIC_HOST ({expected})")
|
||||
for key in (
|
||||
"KEYCLOAK_INTERNAL_URL", "KEYCLOAK_SMS_SERVICE_URL",
|
||||
"MESSAGE_SAFETY_URL", "BITRIX_LOCAL_APP_BASE_URL",
|
||||
"BITRIX_LOCAL_APP_BASE_URL",
|
||||
):
|
||||
parsed = urlparse(env.get(key, ""))
|
||||
if parsed.scheme != "http" or "." in (parsed.hostname or ""):
|
||||
errors.append(f"{key}: ожидается http URL с Docker DNS service name")
|
||||
if not env.get("KEYCLOAK_INTERNAL_URL", "").rstrip("/").endswith("/auth"):
|
||||
errors.append("KEYCLOAK_INTERNAL_URL: внутренний URL должен заканчиваться на /auth")
|
||||
if env.get("MESSAGE_SAFETY_URL", "").rstrip("/") != "https://processing.internal:8443":
|
||||
errors.append(
|
||||
"MESSAGE_SAFETY_URL: ожидается remote TLS endpoint "
|
||||
"https://processing.internal:8443"
|
||||
)
|
||||
try:
|
||||
extra_host, extra_ip = env.get("MESSAGE_SAFETY_EXTRA_HOST", "").rsplit("=", 1)
|
||||
safety_host = urlparse(env.get("MESSAGE_SAFETY_URL", "")).hostname
|
||||
address = ipaddress.ip_address(extra_ip)
|
||||
if extra_host != safety_host or not address.is_private:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
errors.append(
|
||||
"MESSAGE_SAFETY_EXTRA_HOST: ожидается <Safety hostname>=<private VM2 IP>"
|
||||
)
|
||||
if env.get("MESSAGE_SAFETY_API_PREFIX") != "/internal/safety/v2":
|
||||
errors.append("MESSAGE_SAFETY_API_PREFIX: ожидается /internal/safety/v2")
|
||||
try:
|
||||
poll = int(env.get("MESSAGE_SAFETY_TASK_POLL_MAX_SEC", "0"))
|
||||
nginx = int(env.get("NGINX_MESSAGE_READ_TIMEOUT_SEC", "0"))
|
||||
ttl = int(env.get("MESSAGE_SAFETY_TASK_TTL_SEC", "0"))
|
||||
if nginx < poll + 30:
|
||||
errors.append("NGINX_MESSAGE_READ_TIMEOUT_SEC должен быть >= poll max + 30")
|
||||
if ttl <= poll + 60:
|
||||
errors.append("MESSAGE_SAFETY_TASK_TTL_SEC должен превышать poll max + recovery margin")
|
||||
except ValueError:
|
||||
errors.append("Safety/NGINX timeout values должны быть целыми числами")
|
||||
if production and env.get("KEYCLOAK_OTP_MOCK_ENABLED", "").lower() == "true":
|
||||
|
||||
Reference in New Issue
Block a user