Centralized SSL via one contract: CERTS_PATH=/opt/certs mounted into all containers.
Backend:
- NEW: backend/src/core/ssl.py — system_ssl_context(), httpx_verify(),
cert_dir_inventory()
- LLMClient._get_ssl_verify() → delegates to core.ssl
- _llm_async_http._get_verify() → delegates to core.ssl
- Removed LLM_SSL_VERIFY env reading from all runtime code
Docker:
- NEW: docker/certs.sh — shared cert installer (PEM/DER/cer to .crt conversion,
update-ca-certificates, hash symlinks, NSS import)
- NEW: docker/agent.entrypoint.sh — agent entrypoint with cert installation
- backend.entrypoint.sh → uses certs.sh instead of install_llm_ca_certs
- Dockerfile.agent → adds ca-certificates, openssl, entrypoint
Compose:
- Removed LLM_CA_CERT_URLS and LLM_SSL_VERIFY from all compose files
- Added CERTS_PATH volume mount to agent (dev + enterprise)
- Added certs volume mount to backend/agent in dev compose
Env examples:
- Removed LLM_SSL_VERIFY, LLM_CA_CERT_URLS from .env.example,
.env.enterprise-clean.example, .env.current.example, .env.master.example,
backend/.env.example
- Enhanced CERTS_PATH comments with accepted formats
Diagnostics:
- diag_container.py: removed LLM_* checks, added CERTS_PATH inventory,
uses core.ssl for context creation
Tests:
- Updated test_llm_analysis_service, test_llm_async_http,
test_client_headers to verify centralized ssl context (no env disable)
- 4/4 SSL tests pass
159 lines
5.3 KiB
Bash
159 lines
5.3 KiB
Bash
#!/usr/bin/env bash
|
||
# #region docker.certs [C:3] [TYPE Module] [SEMANTICS docker,certs,ssl,ca,trust]
|
||
# @BRIEF Shared certificate installation functions for all containers.
|
||
# @LAYER Infrastructure
|
||
# @PRE CERTS_PATH points to mounted /opt/certs directory (may be empty or absent).
|
||
# @POST Corporate CA certificates installed into system trust store and NSS DB.
|
||
# @INVARIANT server.crt, server.key, server.p12, *.key, *.p12, *.pfx are never imported as CA.
|
||
|
||
set -euo pipefail
|
||
|
||
# ── normalize_cert – PEM/DER detection + conversion ──────────────────
|
||
|
||
normalize_cert() {
|
||
local input="$1"
|
||
local output="$2"
|
||
if openssl x509 -in "$input" -inform PEM -noout 2>/dev/null; then
|
||
cp "$input" "$output"
|
||
return 0
|
||
fi
|
||
if openssl x509 -in "$input" -inform DER -noout 2>/dev/null; then
|
||
openssl x509 -in "$input" -inform DER -out "$output" -outform PEM 2>/dev/null
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# ── install_all_certs – single unified entry point ───────────────────
|
||
|
||
install_all_certs() {
|
||
local certs_dir="${CERTS_PATH:-/opt/certs}"
|
||
if [ ! -d "$certs_dir" ]; then
|
||
echo "[certs] CERTS_PATH=${certs_dir} не существует – пропускаем"
|
||
return 0
|
||
fi
|
||
|
||
local target="/usr/local/share/ca-certificates/custom"
|
||
mkdir -p "$target"
|
||
|
||
echo "[certs] Устанавливаем корпоративные сертификаты из ${certs_dir}..."
|
||
local installed=0 skipped=0 invalid=0
|
||
|
||
for f in "$certs_dir"/*; do
|
||
[ -f "$f" ] || continue
|
||
local name
|
||
name="$(basename "$f")"
|
||
|
||
# Skip non-CA server/private files
|
||
case "$(echo "$name" | tr '[:upper:]' '[:lower:]')" in
|
||
server.crt|server.key|server.pem|*.key|*.p12|*.pfx)
|
||
echo "[certs] ⏭️ пропускаем: ${name} (server/private файл)"
|
||
skipped=$((skipped + 1))
|
||
continue
|
||
;;
|
||
esac
|
||
|
||
# Accept .crt, .pem, .cer, .der
|
||
case "$(echo "$name" | tr '[:upper:]' '[:lower:]')" in
|
||
*.crt|*.pem|*.cer|*.der)
|
||
# valid cert extension
|
||
;;
|
||
*)
|
||
echo "[certs] ⚠️ неизвестный формат: ${name} – пропускаем"
|
||
skipped=$((skipped + 1))
|
||
continue
|
||
;;
|
||
esac
|
||
|
||
local out_name="${name%.*}.crt"
|
||
local out_file="${target}/${out_name}"
|
||
|
||
if normalize_cert "$f" "$out_file"; then
|
||
echo "[certs] ✅ ${name} (→ ${out_name})"
|
||
installed=$((installed + 1))
|
||
else
|
||
echo "[certs] ❌ невалидный сертификат: ${name}"
|
||
invalid=$((invalid + 1))
|
||
fi
|
||
done
|
||
|
||
if [ "$installed" -eq 0 ]; then
|
||
echo "[certs] Нет CA-сертификатов для установки"
|
||
return 0
|
||
fi
|
||
|
||
echo "[certs] Установлено: ${installed}, пропущено: ${skipped}, невалидно: ${invalid}"
|
||
|
||
# Update system CA store
|
||
if command -v update-ca-certificates &>/dev/null; then
|
||
echo "[certs] Обновляем системное хранилище CA-сертификатов..."
|
||
update-ca-certificates --fresh 2>&1 | sed 's/^/[certs] /'
|
||
fi
|
||
|
||
# Create hash symlinks for OpenSSL capath
|
||
echo "[certs] Создаём хеш-симлинки..."
|
||
local sym_count=0
|
||
for cert_file in "$target"/*.crt; do
|
||
[ -f "$cert_file" ] || continue
|
||
local cert_name
|
||
cert_name="$(basename "$cert_file" .crt)"
|
||
local hash_val
|
||
hash_val="$(openssl x509 -in "$cert_file" -noout -hash 2>/dev/null || true)"
|
||
if [ -n "$hash_val" ]; then
|
||
local suffix=0
|
||
local link_path="/etc/ssl/certs/${hash_val}.${suffix}"
|
||
while [ -L "$link_path" ]; do
|
||
if [ "$(readlink "$link_path")" = "$cert_file" ]; then
|
||
break 2
|
||
fi
|
||
suffix=$((suffix + 1))
|
||
link_path="/etc/ssl/certs/${hash_val}.${suffix}"
|
||
done
|
||
if [ ! -L "$link_path" ]; then
|
||
ln -sf "$cert_file" "$link_path"
|
||
sym_count=$((sym_count + 1))
|
||
fi
|
||
fi
|
||
done
|
||
echo "[certs] Создано хеш-симлинок: ${sym_count}"
|
||
}
|
||
|
||
# ── install_ca_to_nss – NSS DB for Chromium/Playwright ──────────────
|
||
|
||
install_ca_to_nss() {
|
||
if ! command -v certutil &>/dev/null; then
|
||
return 0
|
||
fi
|
||
local target="/usr/local/share/ca-certificates/custom"
|
||
if [ ! -d "$target" ] || [ -z "$(ls -A "$target" 2>/dev/null)" ]; then
|
||
return 0
|
||
fi
|
||
local nssdb="${HOME}/.pki/nssdb"
|
||
mkdir -p "$nssdb"
|
||
local nss_count=0
|
||
echo "[certs] Импортируем сертификаты в NSS DB..."
|
||
for cert_file in "$target"/*.crt; do
|
||
[ -f "$cert_file" ] || continue
|
||
local cert_name
|
||
cert_name="$(basename "$cert_file" .crt)"
|
||
certutil -A -d "sql:${nssdb}" -t "C,," -n "custom-${cert_name}" -i "$cert_file" 2>/dev/null && nss_count=$((nss_count + 1)) || true
|
||
done
|
||
echo "[certs] Импортировано в NSS: ${nss_count}"
|
||
}
|
||
|
||
# ── skip_server_files – remove server certs from CA dir ──────────────
|
||
|
||
skip_server_files_filter() {
|
||
# Used by frontend to skip server.crt from being installed as CA
|
||
local certs_dir="${1:-/opt/certs}"
|
||
[ -d "$certs_dir" ] || return 0
|
||
for f in "$certs_dir"/*.crt "$certs_dir"/*.pem; do
|
||
[ -f "$f" ] || continue
|
||
case "$(basename "$f" | tr '[:upper:]' '[:lower:]')" in
|
||
server.crt|server.key) continue ;;
|
||
esac
|
||
echo "$f"
|
||
done
|
||
}
|
||
# #endregion docker.certs
|