refactor(ssl): centralize SSL trust management, remove LLM_SSL_VERIFY
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
This commit is contained in:
@@ -16,9 +16,9 @@ FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system deps for pdfplumber + psycopg (v3 needs libpq)
|
||||
# Install system deps for pdfplumber + psycopg (v3 needs libpq) + certs
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libgl1 libglib2.0-0 libpq5 && rm -rf /var/lib/apt/lists/*
|
||||
libgl1 libglib2.0-0 libpq5 ca-certificates openssl && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Python dependencies — agent runtime (gradio, langgraph, httpx, pdfplumber, ...)
|
||||
# Без sentence-transformers — embedding router безопасно отключается при ImportError.
|
||||
@@ -47,10 +47,15 @@ COPY backend/src/core/logger.py /app/backend/src/core/logger.py
|
||||
COPY backend/src/core/ws_log_handler.py /app/backend/src/core/ws_log_handler.py
|
||||
COPY backend/src/agent/ /app/backend/src/agent/
|
||||
|
||||
# Entrypoint for corporate CA cert installation
|
||||
COPY docker/certs.sh /app/docker/certs.sh
|
||||
COPY docker/agent.entrypoint.sh /app/docker/agent.entrypoint.sh
|
||||
RUN chmod +x /app/docker/agent.entrypoint.sh /app/docker/certs.sh
|
||||
|
||||
# Gradio server
|
||||
ENV GRADIO_SERVER_NAME=0.0.0.0
|
||||
ENV GRADIO_SERVER_PORT=7860
|
||||
|
||||
WORKDIR /app/backend
|
||||
CMD ["python", "-m", "src.agent.run"]
|
||||
ENTRYPOINT ["/app/docker/agent.entrypoint.sh"]
|
||||
# #endregion docker.agent.Dockerfile
|
||||
|
||||
18
docker/agent.entrypoint.sh
Normal file
18
docker/agent.entrypoint.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# #region docker.agent.entrypoint [C:3] [TYPE Module] [SEMANTICS docker,agent,entrypoint,certs]
|
||||
# @BRIEF Agent container entrypoint — install corporate CA certs, then start agent.
|
||||
# @LAYER Infrastructure
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CERTS_PATH="${CERTS_PATH:-/opt/certs}"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/certs.sh" ]; then
|
||||
source "$SCRIPT_DIR/certs.sh"
|
||||
install_all_certs
|
||||
else
|
||||
echo "[agent-entry] certs.sh not found — skipping corporate CA installation"
|
||||
fi
|
||||
|
||||
exec python -m src.agent.run
|
||||
# #endregion docker.agent.entrypoint
|
||||
@@ -497,8 +497,9 @@ except Exception as exc:
|
||||
# MAIN
|
||||
# ======================================================================
|
||||
|
||||
install_certificates
|
||||
install_llm_ca_certs
|
||||
source "$(dirname "$0")/certs.sh"
|
||||
|
||||
install_all_certs
|
||||
install_ca_to_nss
|
||||
|
||||
# ── C1: Wait for database before any migration logic ──
|
||||
|
||||
158
docker/certs.sh
Normal file
158
docker/certs.sh
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user