mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
fix(cache): install CA anchors where every update-ca-trust family reads them
The Fedora and CentOS Stream workspace jobs failed TLS to pypi.org, registry.npmjs.org and repo.packagist.org (CERTIFICATE_VERIFY_FAILED, curl error 7). package-frontend/ca.sh put the frontend CA into the Arch anchor dir /etc/ca-certificates/trust-source/anchors, which p11-kit on the RedHat family never reads. Probing for the anchor dir does not fix it: with-ca-trust.sh creates both /etc/pki/ca-trust/source/anchors and /etc/ca-certificates/trust-source/anchors on every update-ca-trust host, so the probe in registry-ca.sh already picks the RedHat dir on a reused Arch runner. Both scripts now install into both dirs, rebuild only when one of them changed, and take CA_ROOT so unit tests run them against a fake root. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ec2a4b96f0
commit
2756fd56f1
7 files changed
+277
-23
No files matched your search
+21
-7
@@ -1,15 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install the frontend CA into the runner's trust store. Idempotent.
|
||||
# See docs/contributing/environment/cache.md.
|
||||
# Param: CA_ROOT prefix for every absolute path below; empty in production.
|
||||
set -eu
|
||||
|
||||
CA_SRC="/opt/package-frontend-ca.crt"
|
||||
CA_ROOT="${CA_ROOT:-}"
|
||||
CA_SRC="${CA_ROOT}/opt/package-frontend-ca.crt"
|
||||
CA_NAME="infinito-package-frontend.crt"
|
||||
|
||||
if command -v update-ca-certificates >/dev/null 2>&1; then
|
||||
CA_DST="/usr/local/share/ca-certificates/infinito-package-frontend.crt"
|
||||
CA_DSTS=("${CA_ROOT}/usr/local/share/ca-certificates/${CA_NAME}")
|
||||
CA_REBUILD=(update-ca-certificates)
|
||||
elif command -v update-ca-trust >/dev/null 2>&1; then
|
||||
CA_DST="/etc/ca-certificates/trust-source/anchors/infinito-package-frontend.crt"
|
||||
# Exception: Arch and the RedHat family both run update-ca-trust but read different anchor dirs, and with-ca-trust.sh creates both dirs on both families, so the anchor goes into each instead of a probed one.
|
||||
CA_DSTS=(
|
||||
"${CA_ROOT}/etc/pki/ca-trust/source/anchors/${CA_NAME}"
|
||||
"${CA_ROOT}/etc/ca-certificates/trust-source/anchors/${CA_NAME}"
|
||||
)
|
||||
CA_REBUILD=(update-ca-trust extract)
|
||||
else
|
||||
echo "[package-frontend-ca] no supported CA trust tool" >&2
|
||||
@@ -21,11 +28,18 @@ if [ ! -s "${CA_SRC}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if cmp -s "${CA_SRC}" "${CA_DST}" 2>/dev/null; then
|
||||
changed=0
|
||||
for dst in "${CA_DSTS[@]}"; do
|
||||
if ! cmp -s "${CA_SRC}" "${dst}" 2>/dev/null; then
|
||||
install -d -m 0755 "$(dirname "${dst}")"
|
||||
install -m 0644 "${CA_SRC}" "${dst}"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${changed}" -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
install -d -m 0755 "$(dirname "${CA_DST}")"
|
||||
install -m 0644 "${CA_SRC}" "${CA_DST}"
|
||||
"${CA_REBUILD[@]}" >/dev/null 2>&1
|
||||
echo "[package-frontend-ca] installed ${CA_DST}" >&2
|
||||
echo "[package-frontend-ca] installed ${CA_DSTS[*]}" >&2
|
||||
Vendored
+22
-16
@@ -19,25 +19,24 @@
|
||||
# * Debian/Ubuntu: /usr/local/share/ca-certificates (name must end in .crt),
|
||||
# rebuilt by `update-ca-certificates`.
|
||||
# * Arch and Fedora/CentOS both rebuild with `update-ca-trust extract` but
|
||||
# read different anchor dirs -- Arch has no /etc/pki, the RedHat family
|
||||
# has no /etc/ca-certificates -- so the dir is probed, not assumed.
|
||||
# read different anchor dirs, and with-ca-trust.sh creates both dirs on
|
||||
# both families, so the anchor goes into each.
|
||||
# Param: CA_ROOT prefix for every absolute path below; empty in production.
|
||||
set -eu
|
||||
|
||||
CA_DIR="/opt/registry-cache-ca"
|
||||
CA_ROOT="${CA_ROOT:-}"
|
||||
CA_DIR="${CA_ROOT}/opt/registry-cache-ca"
|
||||
CA_SRC="${CA_DIR}/ca.crt"
|
||||
CA_NAME="infinito-registry-cache.crt"
|
||||
|
||||
if command -v update-ca-certificates >/dev/null 2>&1; then
|
||||
CA_DST="/usr/local/share/ca-certificates/infinito-registry-cache.crt"
|
||||
CA_DSTS=("${CA_ROOT}/usr/local/share/ca-certificates/${CA_NAME}")
|
||||
CA_REBUILD=(update-ca-certificates)
|
||||
elif command -v update-ca-trust >/dev/null 2>&1; then
|
||||
if [ -d /etc/pki/ca-trust/source/anchors ]; then
|
||||
CA_DST="/etc/pki/ca-trust/source/anchors/infinito-registry-cache.crt"
|
||||
elif [ -d /etc/ca-certificates/trust-source/anchors ]; then
|
||||
CA_DST="/etc/ca-certificates/trust-source/anchors/infinito-registry-cache.crt"
|
||||
else
|
||||
echo "[registry-cache-ca] update-ca-trust present but no known anchor dir (/etc/pki/ca-trust/source/anchors, /etc/ca-certificates/trust-source/anchors)" >&2
|
||||
exit 1
|
||||
fi
|
||||
CA_DSTS=(
|
||||
"${CA_ROOT}/etc/pki/ca-trust/source/anchors/${CA_NAME}"
|
||||
"${CA_ROOT}/etc/ca-certificates/trust-source/anchors/${CA_NAME}"
|
||||
)
|
||||
CA_REBUILD=(update-ca-trust extract)
|
||||
else
|
||||
echo "[registry-cache-ca] no supported CA trust tool (update-ca-certificates or update-ca-trust)" >&2
|
||||
@@ -56,11 +55,18 @@ if [ ! -s "${CA_SRC}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if cmp -s "${CA_SRC}" "${CA_DST}" 2>/dev/null; then
|
||||
changed=0
|
||||
for dst in "${CA_DSTS[@]}"; do
|
||||
if ! cmp -s "${CA_SRC}" "${dst}" 2>/dev/null; then
|
||||
install -d -m 0755 "$(dirname "${dst}")"
|
||||
install -m 0644 "${CA_SRC}" "${dst}"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${changed}" -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
install -d -m 0755 "$(dirname "${CA_DST}")"
|
||||
install -m 0644 "${CA_SRC}" "${CA_DST}"
|
||||
"${CA_REBUILD[@]}" >/dev/null
|
||||
echo "[registry-cache-ca] installed ${CA_DST}" >&2
|
||||
echo "[registry-cache-ca] installed ${CA_DSTS[*]}" >&2
|
||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,115 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from utils.cache.files import PROJECT_ROOT
|
||||
|
||||
SCRIPT = PROJECT_ROOT / "scripts" / "docker" / "cache" / "package-frontend" / "ca.sh"
|
||||
NAME = "infinito-package-frontend.crt"
|
||||
STUB = '#!/usr/bin/env bash\nprintf "%s\\n" "$0 $*" >>"${STUB_LOG}"\n'
|
||||
PKI = "etc/pki/ca-trust/source/anchors"
|
||||
ARCH = "etc/ca-certificates/trust-source/anchors"
|
||||
TOOLS = ("bash", "cmp", "dirname", "install")
|
||||
|
||||
|
||||
class TestPackageFrontendCa(unittest.TestCase):
|
||||
def fake_root(self, tool: str, anchor_dirs: tuple[str, ...]) -> Path:
|
||||
"""Build a fake root that offers one trust tool and a frontend CA.
|
||||
|
||||
Args:
|
||||
tool: the trust rebuild command the fake system provides.
|
||||
anchor_dirs: anchor directories that already exist in the fake root.
|
||||
|
||||
Returns:
|
||||
The fake root.
|
||||
"""
|
||||
root = Path(self.enterContext(tempfile.TemporaryDirectory()))
|
||||
(root / "opt").mkdir()
|
||||
(root / "opt" / "package-frontend-ca.crt").write_text("CA\n", encoding="utf-8")
|
||||
for anchor in anchor_dirs:
|
||||
(root / anchor).mkdir(parents=True)
|
||||
(root / "bin").mkdir()
|
||||
for needed in TOOLS:
|
||||
(root / "bin" / needed).symlink_to(shutil.which(needed))
|
||||
(root / "bin" / tool).write_text(STUB, encoding="utf-8")
|
||||
(root / "bin" / tool).chmod(0o755)
|
||||
return root
|
||||
|
||||
def run_script(self, root: Path) -> subprocess.CompletedProcess:
|
||||
"""Run ca.sh against a fake root.
|
||||
|
||||
Args:
|
||||
root: fake root from ``fake_root``.
|
||||
|
||||
Returns:
|
||||
The finished process.
|
||||
"""
|
||||
env = {k: v for k, v in os.environ.items() if k not in ("BASH_ENV", "CA_ROOT")}
|
||||
env.update(
|
||||
CA_ROOT=str(root),
|
||||
PATH=str(root / "bin"),
|
||||
STUB_LOG=str(root / "stub.log"),
|
||||
)
|
||||
return subprocess.run(
|
||||
[str(root / "bin" / "bash"), str(SCRIPT)],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
def rebuilds(self, root: Path) -> int:
|
||||
log = root / "stub.log"
|
||||
if not log.exists():
|
||||
return 0
|
||||
text = log.read_text(encoding="utf-8") # nocheck: cache-read per-run tempdir
|
||||
return len(text.splitlines())
|
||||
|
||||
def test_the_redhat_family_gets_the_pki_anchor(self) -> None:
|
||||
root = self.fake_root("update-ca-trust", (PKI,))
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertTrue((root / PKI / NAME).is_file())
|
||||
self.assertEqual(self.rebuilds(root), 1)
|
||||
|
||||
def test_arch_with_both_anchor_dirs_gets_its_trust_source_anchor(self) -> None:
|
||||
root = self.fake_root("update-ca-trust", (PKI, ARCH))
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertTrue((root / ARCH / NAME).is_file())
|
||||
|
||||
def test_a_fresh_update_ca_trust_root_gets_both_anchors(self) -> None:
|
||||
root = self.fake_root("update-ca-trust", ())
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertTrue((root / PKI / NAME).is_file())
|
||||
self.assertTrue((root / ARCH / NAME).is_file())
|
||||
|
||||
def test_debian_uses_update_ca_certificates(self) -> None:
|
||||
root = self.fake_root("update-ca-certificates", ())
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertTrue((root / "usr/local/share/ca-certificates" / NAME).is_file())
|
||||
self.assertFalse((root / "etc").exists())
|
||||
|
||||
def test_a_second_run_does_not_rebuild(self) -> None:
|
||||
root = self.fake_root("update-ca-trust", (PKI,))
|
||||
self.run_script(root)
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertEqual(self.rebuilds(root), 1)
|
||||
|
||||
def test_no_trust_tool_fails_loudly(self) -> None:
|
||||
root = self.fake_root("unrelated-tool", ())
|
||||
done = self.run_script(root)
|
||||
self.assertNotEqual(done.returncode, 0)
|
||||
self.assertFalse(any(root.glob(f"**/{NAME}")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,119 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from utils.cache.files import PROJECT_ROOT
|
||||
|
||||
SCRIPT = PROJECT_ROOT / "scripts" / "docker" / "cache" / "registry-ca.sh"
|
||||
NAME = "infinito-registry-cache.crt"
|
||||
STUB = '#!/usr/bin/env bash\nprintf "%s\\n" "$0 $*" >>"${STUB_LOG}"\n'
|
||||
PKI = "etc/pki/ca-trust/source/anchors"
|
||||
ARCH = "etc/ca-certificates/trust-source/anchors"
|
||||
TOOLS = ("bash", "cmp", "dirname", "install")
|
||||
|
||||
|
||||
class TestRegistryCacheCa(unittest.TestCase):
|
||||
def fake_root(
|
||||
self, tool: str, anchor_dirs: tuple[str, ...], ca: str | None
|
||||
) -> Path:
|
||||
"""Build a fake root that offers one trust tool.
|
||||
|
||||
Args:
|
||||
tool: the trust rebuild command the fake system provides.
|
||||
anchor_dirs: anchor directories that already exist in the fake root.
|
||||
ca: content of the mounted ``ca.crt``; ``None`` leaves the CA dir unmounted.
|
||||
|
||||
Returns:
|
||||
The fake root.
|
||||
"""
|
||||
root = Path(self.enterContext(tempfile.TemporaryDirectory()))
|
||||
if ca is not None:
|
||||
(root / "opt" / "registry-cache-ca").mkdir(parents=True)
|
||||
(root / "opt" / "registry-cache-ca" / "ca.crt").write_text(
|
||||
ca, encoding="utf-8"
|
||||
)
|
||||
for anchor in anchor_dirs:
|
||||
(root / anchor).mkdir(parents=True)
|
||||
(root / "bin").mkdir()
|
||||
for needed in TOOLS:
|
||||
(root / "bin" / needed).symlink_to(shutil.which(needed))
|
||||
(root / "bin" / tool).write_text(STUB, encoding="utf-8")
|
||||
(root / "bin" / tool).chmod(0o755)
|
||||
return root
|
||||
|
||||
def run_script(self, root: Path) -> subprocess.CompletedProcess:
|
||||
"""Run registry-ca.sh against a fake root.
|
||||
|
||||
Args:
|
||||
root: fake root from ``fake_root``.
|
||||
|
||||
Returns:
|
||||
The finished process.
|
||||
"""
|
||||
env = {k: v for k, v in os.environ.items() if k not in ("BASH_ENV", "CA_ROOT")}
|
||||
env.update(
|
||||
CA_ROOT=str(root),
|
||||
PATH=str(root / "bin"),
|
||||
STUB_LOG=str(root / "stub.log"),
|
||||
)
|
||||
return subprocess.run(
|
||||
[str(root / "bin" / "bash"), str(SCRIPT)],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
def rebuilds(self, root: Path) -> int:
|
||||
log = root / "stub.log"
|
||||
if not log.exists():
|
||||
return 0
|
||||
text = log.read_text(encoding="utf-8") # nocheck: cache-read per-run tempdir
|
||||
return len(text.splitlines())
|
||||
|
||||
def test_the_redhat_family_gets_the_pki_anchor(self) -> None:
|
||||
root = self.fake_root("update-ca-trust", (PKI,), "CA\n")
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertTrue((root / PKI / NAME).is_file())
|
||||
self.assertEqual(self.rebuilds(root), 1)
|
||||
|
||||
def test_arch_with_both_anchor_dirs_gets_its_trust_source_anchor(self) -> None:
|
||||
root = self.fake_root("update-ca-trust", (PKI, ARCH), "CA\n")
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertTrue((root / ARCH / NAME).is_file())
|
||||
|
||||
def test_debian_uses_update_ca_certificates(self) -> None:
|
||||
root = self.fake_root("update-ca-certificates", (), "CA\n")
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertTrue((root / "usr/local/share/ca-certificates" / NAME).is_file())
|
||||
|
||||
def test_a_second_run_does_not_rebuild(self) -> None:
|
||||
root = self.fake_root("update-ca-trust", (ARCH,), "CA\n")
|
||||
self.run_script(root)
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertEqual(self.rebuilds(root), 1)
|
||||
|
||||
def test_an_unmounted_ca_dir_is_skipped(self) -> None:
|
||||
root = self.fake_root("update-ca-trust", (PKI,), None)
|
||||
done = self.run_script(root)
|
||||
self.assertEqual(done.returncode, 0, done.stderr)
|
||||
self.assertEqual(self.rebuilds(root), 0)
|
||||
|
||||
def test_a_mounted_dir_without_ca_is_refused(self) -> None:
|
||||
root = self.fake_root("update-ca-trust", (PKI,), "")
|
||||
done = self.run_script(root)
|
||||
self.assertNotEqual(done.returncode, 0)
|
||||
self.assertFalse(any(root.glob(f"**/{NAME}")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user