Coverage for lambda / tls-shared / backend_tls.py: 100.00%
78 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-14 22:07 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-14 22:07 +0000
1"""Strict private-root TLS transport shared by GCO backend callers.
3Callers connect to dynamic Global Accelerator or internal-ALB DNS names while
4presenting and verifying the stable deployment-local identity configured in
5``BACKEND_TLS_SERVER_NAME``. The trust bundle is public material retrieved from
6the project-scoped SSM parameter; no root private key is available to callers.
7"""
9from __future__ import annotations
11import logging
12import os
13import re
14import ssl
15import threading
16import time
18import boto3
19import urllib3
21# <pyflowchart-code-diagram> BEGIN - auto-inserted, do not edit
22# Generated at (UTC): 2026-09-01T14:42:56Z
23# Generated from Git commit: 89b000378ed5a912a38c06f4feab2b029936ebcc
24# Flowchart(s) generated from this file:
25# * ``get_backend_http_pool`` -> ``diagrams/code_diagrams/lambda/tls-shared/backend_tls.get_backend_http_pool.html``
26# (PNG: ``diagrams/code_diagrams/lambda/tls-shared/backend_tls.get_backend_http_pool.png``)
27# Regenerate with ``SOURCE_DATE_EPOCH=<unix-seconds> GCO_DIAGRAM_SOURCE_COMMIT=<40-char-sha> python diagrams/generate.py --code-only``.
28# <pyflowchart-code-diagram> END
31LOGGER = logging.getLogger(__name__)
33_DNS_RE = re.compile(
34 r"(?=.{1,253}\Z)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+"
35 r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?",
36 re.IGNORECASE,
37)
38_pool_lock = threading.Lock()
39_cached_pool: urllib3.PoolManager | None = None
40_last_successful_refresh = 0.0
41_last_refresh_attempt = 0.0
44def _bounded_env_float(name: str, default: float, minimum: float, maximum: float) -> float:
45 try:
46 value = float(os.getenv(name, str(default)))
47 except ValueError:
48 return default
49 return value if minimum <= value <= maximum else default
52def _tls_settings() -> tuple[str, str, str, float, float, float]:
53 server_name = os.getenv("BACKEND_TLS_SERVER_NAME", "").strip().rstrip(".")
54 parameter_name = os.getenv("BACKEND_TLS_ROOT_CA_PARAMETER", "").strip()
55 parameter_region = os.getenv("BACKEND_TLS_ROOT_CA_REGION", "").strip()
56 if _DNS_RE.fullmatch(server_name) is None:
57 raise RuntimeError("Backend TLS server identity is not configured")
58 if not parameter_name.startswith("/") or not parameter_region:
59 raise RuntimeError("Backend TLS trust parameter is not configured")
60 ttl = _bounded_env_float("BACKEND_TLS_CA_CACHE_TTL_SECONDS", 300.0, 1.0, 3600.0)
61 max_stale = max(
62 ttl,
63 _bounded_env_float(
64 "BACKEND_TLS_CA_MAX_STALE_SECONDS",
65 3600.0,
66 1.0,
67 86400.0,
68 ),
69 )
70 retry = _bounded_env_float("BACKEND_TLS_CA_RETRY_SECONDS", 5.0, 0.1, 60.0)
71 return server_name, parameter_name, parameter_region, ttl, max_stale, retry
74def _new_pool(server_name: str, trust_bundle: str) -> urllib3.PoolManager:
75 if "PRIVATE KEY" in trust_bundle or "-----BEGIN CERTIFICATE-----" not in trust_bundle:
76 raise RuntimeError("Backend TLS trust parameter contains invalid public material")
77 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
78 context.minimum_version = ssl.TLSVersion.TLSv1_2
79 context.verify_mode = ssl.CERT_REQUIRED
80 context.check_hostname = True
81 try:
82 context.load_verify_locations(cadata=trust_bundle)
83 except ssl.SSLError as exc:
84 raise RuntimeError("Backend TLS trust parameter contains malformed certificates") from exc
85 return urllib3.PoolManager(
86 num_pools=4,
87 maxsize=10,
88 retries=False,
89 ssl_context=context,
90 server_hostname=server_name,
91 assert_hostname=server_name,
92 )
95def get_backend_http_pool() -> urllib3.PoolManager:
96 """Return a verified HTTPS pool, refreshing its public trust bundle safely."""
97 global _cached_pool, _last_successful_refresh, _last_refresh_attempt
99 server_name, parameter_name, parameter_region, ttl, max_stale, retry = _tls_settings()
100 now = time.monotonic()
101 age = now - _last_successful_refresh
102 if _cached_pool is not None and age < ttl:
103 return _cached_pool
104 if _cached_pool is not None and age <= max_stale and now - _last_refresh_attempt < retry:
105 return _cached_pool
107 with _pool_lock:
108 now = time.monotonic()
109 age = now - _last_successful_refresh
110 if _cached_pool is not None and age < ttl:
111 return _cached_pool
112 if _cached_pool is not None and age <= max_stale and now - _last_refresh_attempt < retry:
113 return _cached_pool
115 _last_refresh_attempt = now
116 try:
117 response = boto3.client("ssm", region_name=parameter_region).get_parameter(
118 Name=parameter_name
119 )
120 trust_bundle = str(response.get("Parameter", {}).get("Value", ""))
121 refreshed_pool = _new_pool(server_name, trust_bundle)
122 except Exception as exc:
123 if _cached_pool is not None and age <= max_stale:
124 LOGGER.warning("Backend TLS trust refresh failed; using bounded stale trust bundle")
125 return _cached_pool
126 raise RuntimeError("Backend TLS trust bundle is unavailable") from exc
128 _cached_pool = refreshed_pool
129 _last_successful_refresh = now
130 return refreshed_pool
133def reset_backend_tls_cache() -> None:
134 """Clear process-local trust state for deterministic tests and cold-start simulation."""
135 global _cached_pool, _last_successful_refresh, _last_refresh_attempt
136 with _pool_lock:
137 _cached_pool = None
138 _last_successful_refresh = 0.0
139 _last_refresh_attempt = 0.0