fix(env): point python at the OS trust store

The package-cache frontend terminates TLS for package traffic and signs
with its own CA: probed from inside the test container, pypi.org arrives
as subject CN=pypi.org issued by CN=Infinito Package-Cache Frontend CA.
package-frontend-ca.sh installs that CA into the OS store, which is why
apt, pip, curl and urllib work — but requests reads certifi, which has
never heard of it, so test-external failed its reachability precondition
with CERTIFICATE_VERIFY_FAILED. Upgrading certifi does not help; the
issuer is not a public root.

load.sh is where the runner puts the venv on PATH, so it is also where the
interpreter carrying the pip certifi is chosen; pointing REQUESTS_CA_BUNDLE
and SSL_CERT_FILE at the store the stack deliberately fills keeps one trust
decision instead of two. A caller-set value still wins.

The bundle location was about to become a fourth copy: dev-nix and the
network diagnose CLI each carried their own candidate list, and the two had
already drifted apart in their fourth entry. All of them now read
group_vars CA_BUNDLE_CANDIDATES — Ansible directly, the shell through a
generated env key, python through read_group_paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-22 18:55:58 +02:00
1 parent 3e56117548
commit fc0881ff33
8 files changed
+76 -12

No files matched your search

+3 -6
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from utils.paths import read_group_paths
DEFAULT_HOSTS: tuple[str, ...] = (
"github.com",
"objects.githubusercontent.com",
@@ -58,12 +60,7 @@ PROXY_ENV_KEYS: tuple[str, ...] = (
"CA_TRUST_NAME",
)
CA_BUNDLE_CANDIDATES: tuple[str, ...] = (
"/etc/ssl/certs/ca-certificates.crt",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/ssl/ca-bundle.pem",
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
)
CA_BUNDLE_CANDIDATES: tuple[str, ...] = tuple(read_group_paths("CA_BUNDLE_CANDIDATES"))
PMTU_PROBE_SIZES: tuple[int, ...] = (1472, 1452, 1400, 1300, 1200, 1024, 576, 256)
+7
View File
@@ -30,3 +30,10 @@ DIR_RESCUE_DIAGNOSTICS: "/tmp/infinito-rescue-diagnostics"
FILE_DNS53_SAMPLER_LOG: "/var/log/infinito/dns53-sampler.log"
FILE_ENVIRONMENT: "/etc/environment" # File to safe environment variables in
CA_BUNDLE_CANDIDATES: # Where the distributions keep the extracted CA bundle, most likely first
- "/etc/ssl/certs/ca-certificates.crt"
- "/etc/pki/tls/certs/ca-bundle.crt"
- "/etc/ssl/ca-bundle.pem"
- "/etc/pki/tls/cacert.pem"
- "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
+1 -5
View File
@@ -38,8 +38,4 @@ dev_nix_experimental_features:
- nix-command
- flakes
DEV_NIX_SSL_CERT_FILE_CANDIDATES:
- /etc/ssl/certs/ca-certificates.crt
- /etc/pki/tls/certs/ca-bundle.crt
- /etc/ssl/ca-bundle.pem
- /etc/pki/tls/cacert.pem
DEV_NIX_SSL_CERT_FILE_CANDIDATES: "{{ CA_BUNDLE_CANDIDATES }}"
+1 -1
View File
@@ -26,7 +26,7 @@
| map(attribute="item")
| list
| first
) | default("/etc/ssl/certs/ca-certificates.crt", true)
) | default(CA_BUNDLE_CANDIDATES | first, true)
}}
- name: 📦 Install Nix via package manager on Arch-based systems
+11
View File
@@ -86,6 +86,17 @@ for _infinito_env_key in "${!_infinito_env_preserved[@]}"; do
done
unset _infinito_env_key _infinito_env_preserved
if [[ -z "${REQUESTS_CA_BUNDLE:-}" ]]; then
for _infinito_env_ca in ${INFINITO_CA_BUNDLE_CANDIDATES}; do
if [[ -r "${_infinito_env_ca}" ]]; then
export REQUESTS_CA_BUNDLE="${_infinito_env_ca}"
export SSL_CERT_FILE="${SSL_CERT_FILE:-${_infinito_env_ca}}"
break
fi
done
unset _infinito_env_ca
fi
export INFINITO_ENV_LOADED="1"
_infinito_env_mtime="$(stat -c %Y "${_infinito_env_dotenv}" 2>/dev/null || echo 0)"
export INFINITO_ENV_LOADED_MTIME="${_infinito_env_mtime}"
+2
View File
@@ -15,6 +15,7 @@ from . import (
passthrough,
)
from .infinito import (
ca_bundle_candidates,
ca_cert_host,
container,
distros,
@@ -70,6 +71,7 @@ ORDERED_HANDLERS = [
running_on_github,
tor_socks_port,
is_wsl2,
ca_bundle_candidates,
ca_cert_host,
outer_network_mtu,
inventory,
+21
View File
@@ -0,0 +1,21 @@
"""INFINITO_CA_BUNDLE_CANDIDATES: where the distributions keep the extracted CA
bundle, read from the group_vars paths SPOT (group_vars/all/05_paths.yml
CA_BUNDLE_CANDIDATES)."""
from __future__ import annotations
from typing import TYPE_CHECKING
from utils.paths import read_group_paths
if TYPE_CHECKING:
from utils.env.builder import BuildContext, EnvBuilder
KEY = "INFINITO_CA_BUNDLE_CANDIDATES"
COMMENT = "Extracted CA bundle per distribution layout, most likely first; consumers take the first readable one (SPOT: group_vars/all/05_paths.yml CA_BUNDLE_CANDIDATES)."
def apply(eb: EnvBuilder, ctx: BuildContext) -> None:
eb.setdefault(
KEY, " ".join(read_group_paths("CA_BUNDLE_CANDIDATES")), comment=COMMENT
)
+30
View File
@@ -27,6 +27,7 @@ _GROUP_PATHS_FILE = str(PROJECT_ROOT / "group_vars" / "all" / "05_paths.yml")
_ENTRY_RE = re.compile(
r'^(?P<key>[A-Za-z_][A-Za-z0-9_]*):\s*"?(?P<value>[^"#]*)"?\s*(?:#.*)?$'
)
_ITEM_RE = re.compile(r'^\s+-\s*"?(?P<value>[^"#]*)"?\s*(?:#.*)?$')
def read_group_path(key: str) -> str:
@@ -56,6 +57,35 @@ def read_group_path(key: str) -> str:
raise KeyError(f"{key} not defined in {_GROUP_PATHS_FILE}")
def read_group_paths(key: str) -> list[str]:
"""Plain-string list value from the group_vars paths SPOT.
Args:
key: top-level variable name whose value is a YAML block sequence.
Returns:
The literal string entries, in file order.
Raises:
KeyError: the key is not defined in the SPOT or carries no entry.
"""
entries: list[str] = []
collecting = False
for line in read_text(_GROUP_PATHS_FILE).splitlines():
if line.startswith(f"{key}:"):
collecting = True
continue
if not collecting:
continue
item = _ITEM_RE.match(line)
if item is None:
break
entries.append(item.group("value").strip())
if not entries:
raise KeyError(f"{key} not defined as a list in {_GROUP_PATHS_FILE}")
return entries
def _dir_var_lib() -> str:
env = os.environ.get("INFINITO_DIR_VAR_LIB")
if env: