Files
Deal/scripts/mtls-certs.sh
T
Rustam Khalimov 713d554dc2 Нормализовать переводы строк в LF
Решение по TD-STYLE-ANALYZERS: LF — инструменты проекта (Python/Node) пишут LF,
CRLF-.sh не работают на Linux CI (sh scripts/ci.sh), большинство файлов уже были
LF. Добавлен .gitattributes (* text=auto eol=lf, бинарные исключения),
.editorconfig переведён на lf, 1029 файлов конвертированы, git add --renormalize.
Из индекса убраны закравшиеся archive/**/__pycache__/*.pyc.
2026-09-11 19:01:42 +03:00

211 lines
9.8 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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);
# <service>-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/<service>-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" <<EOF
[req]
distinguished_name = server_dn
prompt = no
[server_dn]
CN = $service
EOF
cat > "$TMP_DIR/server-ext.cnf" <<EOF
[server]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:localhost,DNS:${service},DNS:host.docker.internal,IP:127.0.0.1
EOF
openssl req -new -newkey rsa:2048 -nodes \
-config "$TMP_DIR/server-req.cnf" \
-keyout "$TMP_DIR/server.key" \
-out "$TMP_DIR/server.csr"
openssl x509 -req \
-in "$TMP_DIR/server.csr" \
-CA "$CERT_DIR/ca.pem" \
-CAkey "$CERT_DIR/ca.key" \
-CAcreateserial \
-out "$TMP_DIR/server.crt" \
-days "$LEAF_DAYS" -sha256 \
-extfile "$TMP_DIR/server-ext.cnf" \
-extensions server
openssl pkcs12 -export \
-out "$CERT_DIR/$service-server.pfx" \
-inkey "$TMP_DIR/server.key" \
-in "$TMP_DIR/server.crt" \
-name "$service-server" \
-passout "pass:$CERT_PASSWORD"
rm -f "$TMP_DIR/server.key" "$TMP_DIR/server.csr" "$TMP_DIR/server.crt"
done
# ── Общий клиентский сертификат deal-client (исходящие каналы core и telegram-service) ──
echo "mtls-certs: клиентский сертификат deal-client (${LEAF_DAYS} дн.) ..."
cat > "$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/<service>-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/<service>-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"