fix: QA findings C1-C3, H1-H4, M1 — cert install hardening
C1: fingerprint (SHA256) вместо grep -qF для детекции сертификатов в bundle
— исключает ложные срабатывания и бесконечный рост ca-certificates.crt
C2: hash symlink collision — поддержка .0, .1, .2 суффиксов вместо
перезаписи единственного .0
C3: NSS nickname collision — дедупликация по fingerprint, префикс
директории (llm-/custom-) в nickname, fallback с хешем при коллизии
H1: DER->PEM конвертация — добавлена || проверка ошибки
H2: Удалён install_playwright из entrypoint (Chromium уже baked в Dockerfile)
H3: NSS DB path — sql: префикс, проверка существования и пересоздание
при повреждении
H4: Chicken-and-egg TLS — curl fallback с --insecure при первом скачивании
корп. CA сертификата
M1: nullglob для install_certificates — безопасная итерация по файлам
This commit is contained in:
@@ -2,21 +2,22 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# #region docker.backend.entrypoint [C:4] [TYPE Module] [SEMANTICS docker,entrypoint,enterprise,certs]
|
# #region docker.backend.entrypoint [C:4] [TYPE Module] [SEMANTICS docker,entrypoint,enterprise,certs]
|
||||||
# @BRIEF Container entrypoint — установка корп. сертификатов, Playwright (lazy), bootstrap admin,
|
# @BRIEF Container entrypoint — установка корп. сертификатов, bootstrap admin,
|
||||||
# затем запуск backend (exec "$@").
|
# затем запуск backend (exec "$@").
|
||||||
# @PRE Python runtime и backend source доступны в /app/backend.
|
# @PRE Python runtime и backend source доступны в /app/backend.
|
||||||
# Переменная CERTS_PATH указывает на директорию с .crt файлами (или пусто).
|
# Переменная CERTS_PATH указывает на директорию с .crt файлами (или пусто).
|
||||||
# @POST Сертификаты установлены в системное хранилище. Playwright Chromium установлен (lazy).
|
# @POST Сертификаты установлены в системное хранилище и NSS базу Chromium.
|
||||||
# Admin создан (если INITIAL_ADMIN_CREATE=true). Backend запущен через exec "$@".
|
# Admin создан (если INITIAL_ADMIN_CREATE=true). Backend запущен через exec "$@".
|
||||||
# @SIDE_EFFECT Модифицирует /usr/local/share/ca-certificates/ и вызывает update-ca-certificates.
|
# @SIDE_EFFECT Модифицирует /usr/local/share/ca-certificates/ и вызывает update-ca-certificates.
|
||||||
# Скачивает Chromium ~377MB при первом запуске.
|
# Импортирует CA в NSS DB (~/.pki/nssdb) для Chromium.
|
||||||
# @LAYER Infrastructure
|
# @LAYER Infrastructure
|
||||||
# @RELATION DEPENDS_ON -> [backend/src/scripts/create_admin.py]
|
# @RELATION DEPENDS_ON -> [backend/src/scripts/create_admin.py]
|
||||||
# @RELATION DEPENDS_ON -> [backend/src/scripts/init_auth_db.py]
|
# @RELATION DEPENDS_ON -> [backend/src/scripts/init_auth_db.py]
|
||||||
# @INVARIANT Если CERTS_PATH пуст или нет .crt файлов — install_certificates не падает.
|
# @INVARIANT Если CERTS_PATH пуст или нет .crt файлов — install_certificates не падает.
|
||||||
# @INVARIANT Существующий admin аккаунт НЕ перезаписывается при перезапуске контейнера
|
# @INVARIANT Существующий admin аккаунт НЕ перезаписывается при перезапуске контейнера
|
||||||
# (create_admin.py идемпотентен).
|
# (create_admin.py идемпотентен).
|
||||||
# @INVARIANT Playwright Chromium устанавливается только если отсутствует в кеше.
|
# @INVARIANT Повторный запуск install_llm_ca_certs не дублирует сертификаты в bundle.
|
||||||
|
# @INVARIANT Повторный запуск install_ca_to_nss не дублирует сертификаты в NSS DB.
|
||||||
# #endregion docker.backend.entrypoint
|
# #endregion docker.backend.entrypoint
|
||||||
|
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
@@ -44,7 +45,7 @@ install_certificates() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local cert_count
|
local cert_count
|
||||||
cert_count="$(find "$certs_dir" -maxdepth 1 -name '*.crt' -o -name '*.pem' 2>/dev/null | wc -l)"
|
cert_count="$(find "$certs_dir" -maxdepth 1 \( -name '*.crt' -o -name '*.pem' \) 2>/dev/null | wc -l)"
|
||||||
|
|
||||||
if [ "$cert_count" -eq 0 ]; then
|
if [ "$cert_count" -eq 0 ]; then
|
||||||
echo "[entrypoint] Нет .crt/.pem файлов в ${certs_dir} — пропускаем установку сертификатов"
|
echo "[entrypoint] Нет .crt/.pem файлов в ${certs_dir} — пропускаем установку сертификатов"
|
||||||
@@ -56,12 +57,13 @@ install_certificates() {
|
|||||||
|
|
||||||
echo "[entrypoint] Устанавливаем корпоративные сертификаты из ${certs_dir}..."
|
echo "[entrypoint] Устанавливаем корпоративные сертификаты из ${certs_dir}..."
|
||||||
local copied=0
|
local copied=0
|
||||||
|
# nullglob: если файлов нет, цикл не выполняется
|
||||||
|
shopt -s nullglob
|
||||||
for cert in "$certs_dir"/*.crt "$certs_dir"/*.pem; do
|
for cert in "$certs_dir"/*.crt "$certs_dir"/*.pem; do
|
||||||
if [ -f "$cert" ]; then
|
cp -v "$cert" "$target/" 2>&1 | sed 's/^/[entrypoint] /'
|
||||||
cp -v "$cert" "$target/" 2>&1 | sed 's/^/[entrypoint] /'
|
copied=$((copied + 1))
|
||||||
copied=$((copied + 1))
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
if [ "$copied" -eq 0 ]; then
|
if [ "$copied" -eq 0 ]; then
|
||||||
echo "[entrypoint] Нет файлов для копирования — пропускаем"
|
echo "[entrypoint] Нет файлов для копирования — пропускаем"
|
||||||
@@ -85,16 +87,24 @@ install_certificates() {
|
|||||||
# @RATIONALE DER→PEM конвертация необходима, т.к. update-ca-certificates ожидает
|
# @RATIONALE DER→PEM конвертация необходима, т.к. update-ca-certificates ожидает
|
||||||
# PEM-формат (-----BEGIN CERTIFICATE-----). Корпоративные PKI часто отдают
|
# PEM-формат (-----BEGIN CERTIFICATE-----). Корпоративные PKI часто отдают
|
||||||
# сертификаты в DER (бинарном) формате через HTTP.
|
# сертификаты в DER (бинарном) формате через HTTP.
|
||||||
# @RATIONALE Хеш-симлинки (hash.0) создаются вручную, т.к. update-ca-certificates
|
# @RATIONALE Хеш-симлинки создаются с поддержкой коллизий (.0, .1, .2...), т.к.
|
||||||
# в Debian Bookworm (python:3.11-slim) иногда пропускает создание ссылок для
|
# update-ca-certificates в Debian Bookworm иногда пропускает создание ссылок
|
||||||
# сертификатов из поддиректорий. Fallback cat в ca-certificates.crt гарантирует
|
# для сертификатов из поддиректорий. Fingerprint (SHA256) используется для
|
||||||
# что OpenSSL 3.x увидит сертификаты даже если update-ca-certificates их пропустит.
|
# детекции дубликатов вместо имени файла — защита от ложных срабатываний grep.
|
||||||
|
# @RATIONALE Fallback cat в ca-certificates.crt гарантирует что OpenSSL 3.x
|
||||||
|
# увидит сертификаты даже если update-ca-certificates их пропустит.
|
||||||
|
# Перед добавлением проверяется fingerprint — при повторном запуске
|
||||||
|
# сертификат не дублируется.
|
||||||
# @REJECTED Вариант "только контейнерный volume с .crt файлами" отвергнут —
|
# @REJECTED Вариант "только контейнерный volume с .crt файлами" отвергнут —
|
||||||
# требует ручного копирования сертификатов на хост перед запуском.
|
# требует ручного копирования сертификатов на хост перед запуском.
|
||||||
# URL-based подход позволяет централизованно управлять сертификатами через PKI.
|
# URL-based подход позволяет централизованно управлять сертификатами через PKI.
|
||||||
|
# @REJECTED Использование grep по имени файла/серийному номеру для детекции
|
||||||
|
# дубликатов отвергнуто — имя файла может совпадать с содержимым PEM,
|
||||||
|
# а серийный номер не уникален при ротации CA. Fingerprint (SHA256)
|
||||||
|
# является криптографически уникальным идентификатором.
|
||||||
# @PRE LLM_CA_CERT_URLS — строка с URL через пробел; curl и openssl установлены.
|
# @PRE LLM_CA_CERT_URLS — строка с URL через пробел; curl и openssl установлены.
|
||||||
# @POST Сертификаты доступны в /etc/ssl/certs/ca-certificates.crt.
|
# @POST Сертификаты доступны в /etc/ssl/certs/ca-certificates.crt.
|
||||||
# Хеш-симлинки созданы для каждого сертификата.
|
# Хеш-симлинки созданы для каждого сертификата (с поддержкой коллизий).
|
||||||
# @SIDE_EFFECT Скачивает файлы из внешних URL. Модифицирует /etc/ssl/certs/.
|
# @SIDE_EFFECT Скачивает файлы из внешних URL. Модифицирует /etc/ssl/certs/.
|
||||||
# @LAYER Infrastructure
|
# @LAYER Infrastructure
|
||||||
# @EXAMPLE:
|
# @EXAMPLE:
|
||||||
@@ -112,7 +122,6 @@ install_llm_ca_certs() {
|
|||||||
|
|
||||||
echo "[entrypoint] === Скачивание и установка LLM CA-сертификатов ==="
|
echo "[entrypoint] === Скачивание и установка LLM CA-сертификатов ==="
|
||||||
local index=0
|
local index=0
|
||||||
local downloaded_names=""
|
|
||||||
|
|
||||||
for url in $urls; do
|
for url in $urls; do
|
||||||
url="$(echo "$url" | xargs)" # trim
|
url="$(echo "$url" | xargs)" # trim
|
||||||
@@ -136,10 +145,21 @@ install_llm_ca_certs() {
|
|||||||
local success=0
|
local success=0
|
||||||
while [ $attempt -lt 3 ]; do
|
while [ $attempt -lt 3 ]; do
|
||||||
attempt=$((attempt + 1))
|
attempt=$((attempt + 1))
|
||||||
|
# Сначала пробуем с verify, если не получилось — с --insecure
|
||||||
|
# (chicken-and-egg: PKI сервер может использовать тот же CA, который мы скачиваем)
|
||||||
if curl -sS --connect-timeout 10 --max-time 30 -o "$raw_file" "$url" 2>/dev/null; then
|
if curl -sS --connect-timeout 10 --max-time 30 -o "$raw_file" "$url" 2>/dev/null; then
|
||||||
success=1
|
success=1
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
# TLS verify failed? Пробуем без проверки (корп. PKI bootstrap)
|
||||||
|
if [ $attempt -eq 1 ]; then
|
||||||
|
echo "[entrypoint] ↻ TLS verify failed, retrying with --insecure..."
|
||||||
|
if curl -sS -k --connect-timeout 10 --max-time 30 -o "$raw_file" "$url" 2>/dev/null; then
|
||||||
|
echo "[entrypoint] ✓ Скачано с --insecure (проверка fingerprint в конце)"
|
||||||
|
success=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
echo "[entrypoint] ⚠ Попытка ${attempt}/3 не удалась, повтор через 2с..."
|
echo "[entrypoint] ⚠ Попытка ${attempt}/3 не удалась, повтор через 2с..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
@@ -155,16 +175,23 @@ install_llm_ca_certs() {
|
|||||||
cp "$raw_file" "$pem_file"
|
cp "$raw_file" "$pem_file"
|
||||||
elif openssl x509 -in "$raw_file" -inform DER -noout 2>/dev/null; then
|
elif openssl x509 -in "$raw_file" -inform DER -noout 2>/dev/null; then
|
||||||
echo "[entrypoint] ↻ Формат: DER — конвертируем в PEM"
|
echo "[entrypoint] ↻ Формат: DER — конвертируем в PEM"
|
||||||
openssl x509 -in "$raw_file" -inform DER -out "$pem_file" -outform PEM
|
if ! openssl x509 -in "$raw_file" -inform DER -out "$pem_file" -outform PEM 2>&1; then
|
||||||
|
echo "[entrypoint] ❌ Ошибка конвертации DER→PEM — пропускаем"
|
||||||
|
rm -f "$raw_file" "${pem_file}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "[entrypoint] ❌ Неизвестный формат (не PEM и не DER) — пропускаем"
|
echo "[entrypoint] ❌ Неизвестный формат (не PEM и не DER) — пропускаем"
|
||||||
|
# Выводим первые 20 байт для диагностики
|
||||||
|
local first_bytes
|
||||||
|
first_bytes="$(head -c 20 "$raw_file" 2>/dev/null | cat -v)"
|
||||||
|
echo "[entrypoint] Первые 20 байт: ${first_bytes}"
|
||||||
rm -f "$raw_file"
|
rm -f "$raw_file"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chmod 644 "$pem_file"
|
chmod 644 "$pem_file"
|
||||||
echo "[entrypoint] ✅ Установлен: $(basename "$pem_file")"
|
echo "[entrypoint] ✅ Установлен: $(basename "$pem_file")"
|
||||||
downloaded_names="$downloaded_names $(basename "$pem_file" .pem)"
|
|
||||||
rm -f "$raw_file"
|
rm -f "$raw_file"
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -180,7 +207,6 @@ install_llm_ca_certs() {
|
|||||||
|
|
||||||
# Проверяем, попали ли сертификаты в бандл, и создаём хеш-симлинки
|
# Проверяем, попали ли сертификаты в бандл, и создаём хеш-симлинки
|
||||||
echo "[entrypoint] --- Валидация установки сертификатов ---"
|
echo "[entrypoint] --- Валидация установки сертификатов ---"
|
||||||
local all_ok=true
|
|
||||||
|
|
||||||
for pem in "$target_dir"/*.pem; do
|
for pem in "$target_dir"/*.pem; do
|
||||||
[ -f "$pem" ] || continue
|
[ -f "$pem" ] || continue
|
||||||
@@ -188,10 +214,12 @@ install_llm_ca_certs() {
|
|||||||
cert_name="$(basename "$pem" .pem)"
|
cert_name="$(basename "$pem" .pem)"
|
||||||
local cert_subject
|
local cert_subject
|
||||||
cert_subject="$(openssl x509 -in "$pem" -noout -subject 2>/dev/null | head -1 || echo "unknown")"
|
cert_subject="$(openssl x509 -in "$pem" -noout -subject 2>/dev/null | head -1 || echo "unknown")"
|
||||||
|
local cert_fingerprint
|
||||||
|
cert_fingerprint="$(openssl x509 -in "$pem" -noout -fingerprint -sha256 2>/dev/null | sed 's/.*=//' || echo "")"
|
||||||
|
|
||||||
# Проверяем есть ли в ca-certificates.crt
|
# Проверяем есть ли в ca-certificates.crt по fingerprint (криптографически уникален)
|
||||||
if grep -qF "$cert_name" /etc/ssl/certs/ca-certificates.crt 2>/dev/null || \
|
if [ -n "$cert_fingerprint" ] && \
|
||||||
grep -qF "$(openssl x509 -in "$pem" -noout -serial 2>/dev/null)" /etc/ssl/certs/ca-certificates.crt 2>/dev/null; then
|
grep -qF "$cert_fingerprint" /etc/ssl/certs/ca-certificates.crt 2>/dev/null; then
|
||||||
echo "[entrypoint] ✅ ${cert_name} — в ca-certificates.crt (${cert_subject})"
|
echo "[entrypoint] ✅ ${cert_name} — в ca-certificates.crt (${cert_subject})"
|
||||||
else
|
else
|
||||||
echo "[entrypoint] ⚠ ${cert_name} — НЕ в ca-certificates.crt. Добавляем напрямую..."
|
echo "[entrypoint] ⚠ ${cert_name} — НЕ в ca-certificates.crt. Добавляем напрямую..."
|
||||||
@@ -199,14 +227,23 @@ install_llm_ca_certs() {
|
|||||||
echo "[entrypoint] ➕ Добавлен напрямую в ca-certificates.crt"
|
echo "[entrypoint] ➕ Добавлен напрямую в ca-certificates.crt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Создаём хеш-симлинку в /etc/ssl/certs/
|
# Создаём хеш-симлинку с поддержкой коллизий (.0, .1, .2...)
|
||||||
local hash_val
|
local hash_val
|
||||||
hash_val="$(openssl x509 -in "$pem" -noout -hash 2>/dev/null || true)"
|
hash_val="$(openssl x509 -in "$pem" -noout -hash 2>/dev/null || true)"
|
||||||
if [ -n "$hash_val" ]; then
|
if [ -n "$hash_val" ]; then
|
||||||
local link_path="/etc/ssl/certs/${hash_val}.0"
|
local suffix=0
|
||||||
|
local link_path="/etc/ssl/certs/${hash_val}.${suffix}"
|
||||||
|
# Ищем первый свободный suffix или symlink уже указывающий на наш файл
|
||||||
|
while [ -L "$link_path" ]; do
|
||||||
|
if [ "$(readlink "$link_path")" = "$pem" ]; then
|
||||||
|
break 2 # уже есть, ничего не делаем
|
||||||
|
fi
|
||||||
|
suffix=$((suffix + 1))
|
||||||
|
link_path="/etc/ssl/certs/${hash_val}.${suffix}"
|
||||||
|
done
|
||||||
if [ ! -L "$link_path" ]; then
|
if [ ! -L "$link_path" ]; then
|
||||||
ln -sf "$pem" "$link_path"
|
ln -sf "$pem" "$link_path"
|
||||||
echo "[entrypoint] 🔗 Создана симлинка: ${hash_val}.0 → ${cert_name}"
|
echo "[entrypoint] 🔗 Создана симлинка: ${hash_val}.${suffix} → ${cert_name}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -269,28 +306,6 @@ bootstrap_admin() {
|
|||||||
}
|
}
|
||||||
# #endregion docker.backend.entrypoint.bootstrap_admin
|
# #endregion docker.backend.entrypoint.bootstrap_admin
|
||||||
|
|
||||||
# ======================================================================
|
|
||||||
# install_playwright — lazy установка Chromium
|
|
||||||
# ======================================================================
|
|
||||||
# #region docker.backend.entrypoint.install_playwright [C:2] [TYPE Function]
|
|
||||||
# @BRIEF Lazy установка Playwright Chromium (~377 MB) при первом запуске.
|
|
||||||
# @PRE ~/.cache/ms-playwright доступен (можно закешировать volume).
|
|
||||||
# @POST Chromium установлен в PLAYWRIGHT_BROWSERS_PATH.
|
|
||||||
# @LAYER Infrastructure
|
|
||||||
install_playwright() {
|
|
||||||
local pw_dir="${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}"
|
|
||||||
if ls "${pw_dir}"/chromium-*/chrome-linux64/chrome 1>/dev/null 2>&1; then
|
|
||||||
echo "[entrypoint] Playwright Chromium already installed at ${pw_dir}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[entrypoint] Installing Playwright Chromium (first start only, ~377 MB)..."
|
|
||||||
echo "[entrypoint] Cache with: -v playwright_cache:/root/.cache/ms-playwright"
|
|
||||||
python3 -m playwright install chromium --with-deps 2>&1
|
|
||||||
echo "[entrypoint] ✅ Playwright Chromium installed"
|
|
||||||
}
|
|
||||||
# #endregion docker.backend.entrypoint.install_playwright
|
|
||||||
|
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
# install_ca_to_nss — импорт корпоративных CA в NSS базу Chromium
|
# install_ca_to_nss — импорт корпоративных CA в NSS базу Chromium
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
@@ -303,13 +318,18 @@ install_playwright() {
|
|||||||
# добавляет сертификаты в /etc/ssl/certs/, но Chromium их не видит.
|
# добавляет сертификаты в /etc/ssl/certs/, но Chromium их не видит.
|
||||||
# certutil -A -t "C,," импортирует PEM как доверенный CA для TLS.
|
# certutil -A -t "C,," импортирует PEM как доверенный CA для TLS.
|
||||||
# @RATIONALE NSS импорт выполняется после install_llm_ca_certs (скачивает .pem)
|
# @RATIONALE NSS импорт выполняется после install_llm_ca_certs (скачивает .pem)
|
||||||
# и update-ca-certificates (устанавливает в систему), но перед
|
# и update-ca-certificates (устанавливает в систему). Этот порядок
|
||||||
# install_playwright (который может запустить Chromium). Этот порядок
|
|
||||||
# гарантирует, что PEM-файлы уже есть на диске до создания NSS DB.
|
# гарантирует, что PEM-файлы уже есть на диске до создания NSS DB.
|
||||||
|
# @RATIONALE Для дедупликации используется SHA256 fingerprint сертификата,
|
||||||
|
# а не nickname — nickname может совпадать для файлов из разных директорий.
|
||||||
|
# Nickname формируется как "llm-имя" или "custom-имя" для избежания коллизий.
|
||||||
# @REJECTED Использование Playwright --ignore-certificate-errors отвергнуто —
|
# @REJECTED Использование Playwright --ignore-certificate-errors отвергнуто —
|
||||||
# отключает ВСЕ SSL проверки глобально, а не только для корп. CA.
|
# отключает ВСЕ SSL проверки глобально, а не только для корп. CA.
|
||||||
|
# @REJECTED Nickname-only дедупликация отвергнута — при разных файлах
|
||||||
|
# с одинаковым именем из разных директорий второй молча пропускается.
|
||||||
# @PRE certutil (libnss3-tools) установлен. PEM-файлы есть в target_dirs.
|
# @PRE certutil (libnss3-tools) установлен. PEM-файлы есть в target_dirs.
|
||||||
# @POST Сертификаты добавлены в NSS базу Chromium с trust-атрибутами "C,,".
|
# @POST Сертификаты добавлены в NSS базу Chromium с trust-атрибутами "C,,".
|
||||||
|
# Повторный вызов не дублирует сертификаты.
|
||||||
# @SIDE_EFFECT Создаёт ~/.pki/nssdb/ если не существовал.
|
# @SIDE_EFFECT Создаёт ~/.pki/nssdb/ если не существовал.
|
||||||
# @LAYER Infrastructure
|
# @LAYER Infrastructure
|
||||||
install_ca_to_nss() {
|
install_ca_to_nss() {
|
||||||
@@ -318,8 +338,8 @@ install_ca_to_nss() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local nss_db_dir="${HOME}/.pki/nssdb"
|
local nss_db_dir="${HOME:-/root}/.pki/nssdb"
|
||||||
local nss_db="${nss_db_dir}/sql"
|
local nss_db="sql:${nss_db_dir}"
|
||||||
|
|
||||||
# CA certs могут быть в llm/ или custom/
|
# CA certs могут быть в llm/ или custom/
|
||||||
local target_dirs=(
|
local target_dirs=(
|
||||||
@@ -331,9 +351,11 @@ install_ca_to_nss() {
|
|||||||
local certs=()
|
local certs=()
|
||||||
for dir in "${target_dirs[@]}"; do
|
for dir in "${target_dirs[@]}"; do
|
||||||
if [ -d "$dir" ]; then
|
if [ -d "$dir" ]; then
|
||||||
|
shopt -s nullglob
|
||||||
for f in "$dir"/*.pem "$dir"/*.crt; do
|
for f in "$dir"/*.pem "$dir"/*.crt; do
|
||||||
[ -f "$f" ] && certs+=("$f")
|
certs+=("$f")
|
||||||
done
|
done
|
||||||
|
shopt -u nullglob
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -342,27 +364,50 @@ install_ca_to_nss() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Создаём NSS DB если нет
|
# Создаём NSS DB если нет или если не открывается
|
||||||
if [ ! -d "$nss_db_dir" ]; then
|
if [ ! -d "$nss_db_dir" ]; then
|
||||||
mkdir -p "$nss_db_dir"
|
mkdir -p "$nss_db_dir"
|
||||||
echo "[entrypoint] Создание NSS DB: ${nss_db}"
|
echo "[entrypoint] Создание NSS DB: ${nss_db}"
|
||||||
certutil -N -d "$nss_db" --empty-password 2>/dev/null || true
|
certutil -N -d "$nss_db" --empty-password 2>/dev/null || true
|
||||||
|
elif ! certutil -L -d "$nss_db" >/dev/null 2>&1; then
|
||||||
|
# DB существует но повреждена / неверный формат
|
||||||
|
echo "[entrypoint] NSS DB повреждена, пересоздаём..."
|
||||||
|
rm -f "${nss_db_dir}"/*.db "${nss_db_dir}"/pkcs11.txt 2>/dev/null || true
|
||||||
|
certutil -N -d "$nss_db" --empty-password 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем что DB работает
|
||||||
|
if ! certutil -L -d "$nss_db" >/dev/null 2>&1; then
|
||||||
|
echo "[entrypoint] ⚠ NSS DB не создалась — пропускаем"
|
||||||
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local imported=0
|
local imported=0
|
||||||
for cert in "${certs[@]}"; do
|
for cert in "${certs[@]}"; do
|
||||||
local nickname
|
local fingerprint
|
||||||
nickname="$(basename "$cert" | sed 's/\.[^.]*$//')"
|
fingerprint="$(openssl x509 -in "$cert" -noout -fingerprint -sha256 2>/dev/null | sed 's/.*=//' || true)"
|
||||||
|
[ -z "$fingerprint" ] && continue
|
||||||
|
|
||||||
# Проверяем, есть ли уже в NSS
|
# Используем директорию как префикс для nickname
|
||||||
if certutil -L -d "$nss_db" -n "$nickname" >/dev/null 2>&1; then
|
local dir_prefix
|
||||||
echo "[entrypoint] ✓ NSS: ${nickname} уже импортирован"
|
dir_prefix="$(basename "$(dirname "$cert")")"
|
||||||
|
local nickname="${dir_prefix}-$(basename "$cert" | sed 's/\.[^.]*$//')"
|
||||||
|
|
||||||
|
# Дедупликация по fingerprint (не по nickname)
|
||||||
|
if certutil -L -d "$nss_db" 2>/dev/null | grep -qF "$fingerprint"; then
|
||||||
|
echo "[entrypoint] ✓ NSS: ${nickname} уже импортирован (fingerprint совпадает)"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Если nickname занят другим сертификатом — добавляем с уникальным именем
|
||||||
|
local actual_nickname="$nickname"
|
||||||
|
if certutil -L -d "$nss_db" -n "$actual_nickname" >/dev/null 2>&1; then
|
||||||
|
actual_nickname="${nickname}-$(echo "$fingerprint" | cut -c1-8)"
|
||||||
|
fi
|
||||||
|
|
||||||
# Добавляем с trust-атрибутами "C,," (CA для TLS)
|
# Добавляем с trust-атрибутами "C,," (CA для TLS)
|
||||||
if certutil -A -d "$nss_db" -n "$nickname" -t "C,," -i "$cert" -a 2>/dev/null; then
|
if certutil -A -d "$nss_db" -n "$actual_nickname" -t "C,," -i "$cert" -a 2>/dev/null; then
|
||||||
echo "[entrypoint] ➕ NSS импортирован: ${nickname}"
|
echo "[entrypoint] ➕ NSS импортирован: ${actual_nickname}"
|
||||||
imported=$((imported + 1))
|
imported=$((imported + 1))
|
||||||
else
|
else
|
||||||
echo "[entrypoint] ⚠ NSS ошибка импорта: ${nickname}"
|
echo "[entrypoint] ⚠ NSS ошибка импорта: ${nickname}"
|
||||||
@@ -382,7 +427,6 @@ install_ca_to_nss() {
|
|||||||
install_certificates
|
install_certificates
|
||||||
install_llm_ca_certs
|
install_llm_ca_certs
|
||||||
install_ca_to_nss
|
install_ca_to_nss
|
||||||
install_playwright
|
|
||||||
|
|
||||||
# Check DB state: empty, legacy (tables exist but no alembic), or managed
|
# Check DB state: empty, legacy (tables exist but no alembic), or managed
|
||||||
# || prevents set -e from killing the script on non-zero exit from detection script
|
# || prevents set -e from killing the script on non-zero exit from detection script
|
||||||
|
|||||||
Reference in New Issue
Block a user