#!/usr/bin/env sh # # mtls-certs.sh — генерация dev-CA и сертификатов mTLS внутреннего gRPC «Дейла» (этап 7, Ruling 6, Task 13). # # Требуется openssl. Вывод — deploy/certs/: # ca.pem / ca.key — dev-CA (ca.pem — runtime всех процессов, ca.key — только подпись новых # сертификатов; хранится в deploy/certs, в репозиторий/образ не попадает, # см. .dockerignore; права 600); # -server.pfx — серверный сертификат процесса: core, telegram-service, ai-service, # ml-service (SAN: localhost + имя compose-сервиса + host.docker.internal); # deal-client.pfx — общий клиентский сертификат исходящих каналов (core → сервисы, # telegram-service → core-ингресс); # deal-client.crt/.key — тот же клиентский сертификат в PEM (без пароля): нужен docker # healthcheck'ам compose.prod при mTLS — grpc_health_probe принимает # только PEM (флаги -tls-client-cert/-tls-client-key; см. Task 14). # # Сертификаты dev (срок 825 дней — как Let's Encrypt; CA — 10 лет). Пароль PFX — env DEAL_MTLS_CERT_PASSWORD # (по умолчанию deal_mtls_dev_password — dev); в PROD задайте свой и пропишите его же в # DEAL_MTLS_SERVER_CERT_PASSWORD / DEAL_MTLS_CLIENT_CERT_PASSWORD процессов (compose-prod — Task 14). # Повторный запуск без -f отказывается перезаписывать существующую CA (смена CA ломает доверие всех # сертификатов — перегенерация нужна осознанная). Subject-имена задаются config-файлами openssl, а не # -subj, — скрипт работает и в Git Bash (MSYS не «съедает» аргументы вида /CN=...). # # Использование: # scripts/mtls-certs.sh [-f] [DEAL_MTLS_CERT_PASSWORD=...] # # После генерации процессы поднимаются с env (пути — как смонтировано в compose-prod, Task 14): # DEAL_MTLS_ENABLED=1 # DEAL_MTLS_CA_PEM=/etc/deal/certs/ca.pem # DEAL_MTLS_SERVER_CERT_PFX=/etc/deal/certs/-server.pfx # DEAL_MTLS_SERVER_CERT_PASSWORD=<тот же пароль> # DEAL_MTLS_CLIENT_CERT_PFX=/etc/deal/certs/deal-client.pfx # DEAL_MTLS_CLIENT_CERT_PASSWORD=<тот же пароль> # # Dev-стек (deploy/compose.dev.yml) остаётся plaintext + service-token — DEAL_MTLS_ENABLED не задаётся. set -eu # Корень репозитория (каталог скрипта/..). SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) CERT_DIR="$ROOT_DIR/deploy/certs" # Пароль PFX: env DEAL_MTLS_CERT_PASSWORD (dev-дефолт — фиксированное значение; PROD — свой). CERT_PASSWORD="${DEAL_MTLS_CERT_PASSWORD:-deal_mtls_dev_password}" # Сроки сертификатов (дней): CA — 10 лет, листовые — 825 (как Let's Encrypt). CA_DAYS=3650 LEAF_DAYS=825 # Пространство имён сертификатов: SAN листовых — localhost + имя compose-сервиса + host.docker.internal # (dev-прогон сервиса в контейнере против core на хосте, Ruling 12) + loopback-IP. SERVICES="core telegram-service ai-service ml-service" FORCE=0 for arg in "$@"; do case "$arg" in -f|--force) FORCE=1 ;; *) echo "mtls-certs: неизвестный аргумент: $arg (ожидалось -f/--force)" >&2 exit 2 ;; esac done if ! command -v openssl >/dev/null 2>&1; then echo "mtls-certs: openssl не найден — установите openssl (например: apt install openssl / brew install openssl)." >&2 exit 1 fi mkdir -p "$CERT_DIR" if [ -f "$CERT_DIR/ca.pem" ]; then if [ "$FORCE" -eq 0 ]; then echo "mtls-certs: $CERT_DIR/ca.pem уже существует — повторный запуск не перезаписывает CA" >&2 echo "mtls-certs: (смена CA ломает доверие выданных сертификатов). Перегенерировать всё: $0 -f" >&2 exit 1 fi # Принудительная перегенерация: старые сертификаты удаляются целиком (их доверие утеряно вместе с CA). rm -f "$CERT_DIR/ca.pem" "$CERT_DIR/ca.key" "$CERT_DIR/ca.srl" "$CERT_DIR"/*-server.pfx \ "$CERT_DIR/deal-client.pfx" "$CERT_DIR/deal-client.crt" "$CERT_DIR/deal-client.key" fi # Временный каталог для CSR/конфигов/ключей (всегда чистится, в т.ч. при ошибке). TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/deal-mtls.XXXXXX") trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM echo "mtls-certs: генерирую dev-CA (CN=Deal mTLS Dev CA, ${CA_DAYS} дн.) в $CERT_DIR ..." cat > "$TMP_DIR/ca.cnf" <<'EOF' [req] distinguished_name = ca_dn prompt = no x509_extensions = ca_ext [ca_dn] CN = Deal mTLS Dev CA [ca_ext] basicConstraints = critical,CA:TRUE keyUsage = critical,keyCertSign,cRLSign,digitalSignature subjectKeyIdentifier = hash EOF openssl req -x509 -newkey rsa:2048 -sha256 -days "$CA_DAYS" -nodes \ -config "$TMP_DIR/ca.cnf" \ -keyout "$CERT_DIR/ca.key" \ -out "$CERT_DIR/ca.pem" chmod 600 "$CERT_DIR/ca.key" chmod 644 "$CERT_DIR/ca.pem" # ── Серверные сертификаты процессов (SAN: localhost + compose-имя + host.docker.internal) ── for service in $SERVICES; do echo "mtls-certs: серверный сертификат $service (${LEAF_DAYS} дн., SAN: localhost,$service,host.docker.internal) ..." cat > "$TMP_DIR/server-req.cnf" < "$TMP_DIR/server-ext.cnf" < "$TMP_DIR/client-req.cnf" <<'EOF' [req] distinguished_name = client_dn prompt = no [client_dn] CN = deal-client EOF cat > "$TMP_DIR/client-ext.cnf" <<'EOF' [client] basicConstraints = critical,CA:FALSE keyUsage = critical,digitalSignature extendedKeyUsage = clientAuth EOF openssl req -new -newkey rsa:2048 -nodes \ -config "$TMP_DIR/client-req.cnf" \ -keyout "$TMP_DIR/client.key" \ -out "$TMP_DIR/client.csr" openssl x509 -req \ -in "$TMP_DIR/client.csr" \ -CA "$CERT_DIR/ca.pem" \ -CAkey "$CERT_DIR/ca.key" \ -CAcreateserial \ -out "$TMP_DIR/client.crt" \ -days "$LEAF_DAYS" -sha256 \ -extfile "$TMP_DIR/client-ext.cnf" \ -extensions client openssl pkcs12 -export \ -out "$CERT_DIR/deal-client.pfx" \ -inkey "$TMP_DIR/client.key" \ -in "$TMP_DIR/client.crt" \ -name "deal-client" \ -passout "pass:$CERT_PASSWORD" # PEM-копии клиентского сертификата для grpc_health_probe compose.prod при mTLS (Task 14): утилита # принимает только PEM и без пароля. Сертификат публичный; приватный ключ — права 600, как ca.key. openssl pkcs12 -in "$CERT_DIR/deal-client.pfx" -clcerts -nokeys -passin "pass:$CERT_PASSWORD" \ -out "$CERT_DIR/deal-client.crt" openssl pkcs12 -in "$CERT_DIR/deal-client.pfx" -nocerts -nodes -passin "pass:$CERT_PASSWORD" \ -out "$CERT_DIR/deal-client.key" chmod 644 "$CERT_DIR/deal-client.crt" chmod 600 "$CERT_DIR/deal-client.key" echo echo "mtls-certs: готово. Файлы в $CERT_DIR:" ls -1 "$CERT_DIR" echo echo "Пароль PFX (DEAL_MTLS_CERT_PASSWORD): $CERT_PASSWORD" echo "Проверка (SAN/подпись): openssl pkcs12 -in $CERT_DIR/-server.pfx -nokeys | openssl verify -CAfile $CERT_DIR/ca.pem" echo echo "Включение mTLS (dev остаётся plaintext — флаг не задаётся; PROD env передаёт compose-prod, Task 14):" echo " DEAL_MTLS_ENABLED=1" echo " DEAL_MTLS_CA_PEM=$CERT_DIR/ca.pem" echo " DEAL_MTLS_SERVER_CERT_PFX=$CERT_DIR/-server.pfx # для каждого процесса свой" echo " DEAL_MTLS_SERVER_CERT_PASSWORD=$CERT_PASSWORD" echo " DEAL_MTLS_CLIENT_CERT_PFX=$CERT_DIR/deal-client.pfx" echo " DEAL_MTLS_CLIENT_CERT_PASSWORD=$CERT_PASSWORD"