mirror of
https://github.com/d9pouces/id_configurator.git
synced 2026-11-09 01:47:21 +01:00
e3b2e8a95a
Signed-off-by: d9pouces <github@19pouces.net>
274 lines
10 KiB
Python
274 lines
10 KiB
Python
from id_configurator.config import Configuration
|
|
from id_configurator.context import Context
|
|
from id_configurator.modules.certificates import (
|
|
CertificatesModule,
|
|
_pem_name,
|
|
_resolve_target_dir,
|
|
)
|
|
|
|
CERT_CONTENT = "fake-cert-bytes"
|
|
|
|
|
|
def make_config(with_certificate: bool) -> Configuration:
|
|
data = {
|
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
|
"organisation": "InterDiode",
|
|
}
|
|
if with_certificate:
|
|
data["configurations"] = {
|
|
"certificates": [{"name": "new-root-ca", "content": CERT_CONTENT}]
|
|
}
|
|
return Configuration.model_validate(data)
|
|
|
|
|
|
def test_is_relevant_true_when_certificates_configured(tmp_path) -> None:
|
|
context = Context(config=make_config(True), dst=tmp_path / "dst", src=None)
|
|
assert CertificatesModule().is_relevant(context) is True
|
|
|
|
|
|
def test_is_relevant_false_when_no_certificates(tmp_path) -> None:
|
|
context = Context(config=make_config(False), dst=tmp_path / "dst", src=None)
|
|
assert CertificatesModule().is_relevant(context) is False
|
|
|
|
|
|
def test_apply_writes_certificate_and_rebuilds_bundle(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config(True), dst=dst, src=None)
|
|
|
|
CertificatesModule().apply(context)
|
|
|
|
local_cert = dst / "usr/local/share/ca-certificates/new-root-ca.crt"
|
|
assert local_cert.read_bytes() == b"fake-cert-bytes"
|
|
|
|
bundle = dst / "etc/ssl/certs/ca-certificates.crt"
|
|
assert bundle.read_bytes() == b"fake-cert-bytes\n"
|
|
|
|
pem = dst / "etc/ssl/certs/new-root-ca.pem"
|
|
assert pem.is_symlink()
|
|
assert (
|
|
pem.readlink().as_posix() == "/usr/local/share/ca-certificates/new-root-ca.crt"
|
|
)
|
|
|
|
openssl_default = dst / "usr/lib/ssl/cert.pem"
|
|
assert openssl_default.is_symlink()
|
|
assert openssl_default.readlink().as_posix() == "/etc/ssl/certs/ca-certificates.crt"
|
|
|
|
|
|
def test_apply_merges_conf_listed_and_local_certificates(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
local_certs_dir = src / "usr/local/share/ca-certificates"
|
|
local_certs_dir.mkdir(parents=True)
|
|
(local_certs_dir / "existing.crt").write_bytes(b"existing-bytes")
|
|
(local_certs_dir / "new-root-ca.crt").write_bytes(
|
|
b"stale-bytes-should-be-overwritten"
|
|
)
|
|
system_certs_dir = src / "usr/share/ca-certificates"
|
|
system_certs_dir.mkdir(parents=True)
|
|
# No trailing newline, to exercise the newline-normalization on append.
|
|
(system_certs_dir / "system-ca.crt").write_bytes(b"system-ca-bytes")
|
|
(src / "etc/ca-certificates.conf").parent.mkdir(parents=True, exist_ok=True)
|
|
(src / "etc/ca-certificates.conf").write_text(
|
|
"# a comment\n\n!disabled-ca.crt\nsystem-ca.crt\n", encoding="utf-8"
|
|
)
|
|
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config(True), dst=dst, src=src)
|
|
|
|
CertificatesModule().apply(context)
|
|
|
|
local_dst = dst / "usr/local/share/ca-certificates"
|
|
assert (local_dst / "existing.crt").read_bytes() == b"existing-bytes"
|
|
assert (local_dst / "new-root-ca.crt").read_bytes() == b"fake-cert-bytes"
|
|
|
|
bundle = dst / "etc/ssl/certs/ca-certificates.crt"
|
|
# ca-certificates.conf entries first, then local certificates sorted by name.
|
|
assert bundle.read_bytes() == (
|
|
b"system-ca-bytes\nexisting-bytes\nfake-cert-bytes\n"
|
|
)
|
|
|
|
etc_certs = dst / "etc/ssl/certs"
|
|
assert (
|
|
etc_certs / "system-ca.pem"
|
|
).readlink().as_posix() == "/usr/share/ca-certificates/system-ca.crt"
|
|
assert (
|
|
etc_certs / "existing.pem"
|
|
).readlink().as_posix() == "/usr/local/share/ca-certificates/existing.crt"
|
|
assert (
|
|
etc_certs / "new-root-ca.pem"
|
|
).readlink().as_posix() == "/usr/local/share/ca-certificates/new-root-ca.crt"
|
|
# The disabled entry must not produce a symlink or appear in the bundle.
|
|
assert not (etc_certs / "disabled-ca.pem").exists()
|
|
|
|
|
|
def test_apply_warns_and_skips_missing_conf_entry(tmp_path, caplog) -> None:
|
|
src = tmp_path / "src"
|
|
(src / "usr/share/ca-certificates").mkdir(parents=True)
|
|
(src / "etc/ca-certificates.conf").parent.mkdir(parents=True, exist_ok=True)
|
|
(src / "etc/ca-certificates.conf").write_text("missing.crt\n", encoding="utf-8")
|
|
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config(True), dst=dst, src=src)
|
|
|
|
with caplog.at_level("WARNING"):
|
|
CertificatesModule().apply(context)
|
|
|
|
assert "missing.crt" in caplog.text
|
|
bundle = dst / "etc/ssl/certs/ca-certificates.crt"
|
|
assert bundle.read_bytes() == b"fake-cert-bytes\n"
|
|
|
|
|
|
def test_pem_name_sanitizes_special_characters() -> None:
|
|
assert _pem_name("usr/share/ca-certificates/New Root, CA.crt") == "New_Root__CA.pem"
|
|
assert _pem_name("Vendor (Test).crt") == "Vendor_=Test=.pem"
|
|
|
|
|
|
def test_apply_skips_rhel_trust_store_when_absent(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config(True), dst=dst, src=None)
|
|
|
|
CertificatesModule().apply(context)
|
|
|
|
assert not (dst / "etc/pki").exists()
|
|
|
|
|
|
def test_apply_updates_rhel_trust_store_when_present(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
anchors_dir = src / "etc/pki/ca-trust/source/anchors"
|
|
anchors_dir.mkdir(parents=True)
|
|
(anchors_dir / "existing-anchor.pem").write_bytes(b"existing-anchor-bytes")
|
|
(anchors_dir / "new-root-ca.crt").write_bytes(b"stale-should-be-overwritten")
|
|
extracted_bundle = src / "etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
|
|
extracted_bundle.parent.mkdir(parents=True)
|
|
# No trailing newline, to exercise the newline-normalization on append.
|
|
extracted_bundle.write_bytes(b"vendor-bundle-bytes")
|
|
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config(True), dst=dst, src=src)
|
|
|
|
CertificatesModule().apply(context)
|
|
|
|
anchors_dst = dst / "etc/pki/ca-trust/source/anchors"
|
|
assert (
|
|
anchors_dst / "existing-anchor.pem"
|
|
).read_bytes() == b"existing-anchor-bytes"
|
|
assert (anchors_dst / "new-root-ca.crt").read_bytes() == b"fake-cert-bytes"
|
|
|
|
bundle_dst = dst / "etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
|
|
assert bundle_dst.read_bytes() == (
|
|
b"vendor-bundle-bytes\nexisting-anchor-bytes\nfake-cert-bytes\n"
|
|
)
|
|
|
|
|
|
def test_apply_rebuilds_rhel_trust_store_without_preexisting_bundle(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
(src / "etc/pki/ca-trust/source/anchors").mkdir(parents=True)
|
|
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config(True), dst=dst, src=src)
|
|
|
|
CertificatesModule().apply(context)
|
|
|
|
bundle_dst = dst / "etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
|
|
assert bundle_dst.read_bytes() == b"fake-cert-bytes\n"
|
|
|
|
|
|
def test_resolve_target_dir_returns_unchanged_when_not_a_symlink(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
(src / "etc/ssl/certs").mkdir(parents=True)
|
|
context = Context(config=make_config(True), dst=tmp_path / "dst", src=src)
|
|
|
|
assert _resolve_target_dir(context, "etc/ssl/certs") == "etc/ssl/certs"
|
|
|
|
|
|
def test_resolve_target_dir_returns_unchanged_without_src(tmp_path) -> None:
|
|
context = Context(config=make_config(True), dst=tmp_path / "dst", src=None)
|
|
|
|
assert _resolve_target_dir(context, "etc/ssl/certs") == "etc/ssl/certs"
|
|
|
|
|
|
def test_resolve_target_dir_follows_absolute_symlink(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
(src / "etc/pki/tls/certs").mkdir(parents=True)
|
|
(src / "etc/ssl").mkdir(parents=True)
|
|
(src / "etc/ssl/certs").symlink_to("/etc/pki/tls/certs")
|
|
context = Context(config=make_config(True), dst=tmp_path / "dst", src=src)
|
|
|
|
assert _resolve_target_dir(context, "etc/ssl/certs") == "etc/pki/tls/certs"
|
|
|
|
|
|
def test_resolve_target_dir_follows_relative_symlink(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
(src / "etc/pki/tls/certs").mkdir(parents=True)
|
|
(src / "etc/ssl").mkdir(parents=True)
|
|
(src / "etc/ssl/certs").symlink_to("../pki/tls/certs")
|
|
context = Context(config=make_config(True), dst=tmp_path / "dst", src=src)
|
|
|
|
assert _resolve_target_dir(context, "etc/ssl/certs") == "etc/pki/tls/certs"
|
|
|
|
|
|
def test_resolve_target_dir_falls_back_when_relative_symlink_escapes_src(
|
|
tmp_path,
|
|
) -> None:
|
|
src = tmp_path / "src"
|
|
(src / "etc/ssl").mkdir(parents=True)
|
|
# Escapes past <src> itself via enough ".." components.
|
|
(src / "etc/ssl/certs").symlink_to("../../../../../outside")
|
|
context = Context(config=make_config(True), dst=tmp_path / "dst", src=src)
|
|
|
|
assert _resolve_target_dir(context, "etc/ssl/certs") == "etc/ssl/certs"
|
|
|
|
|
|
def test_apply_writes_bundle_to_rhel_style_symlinked_etccertsdir(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
(src / "etc/pki/tls/certs").mkdir(parents=True)
|
|
(src / "etc/ssl").mkdir(parents=True)
|
|
(src / "etc/ssl/certs").symlink_to("/etc/pki/tls/certs")
|
|
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config(True), dst=dst, src=src)
|
|
|
|
CertificatesModule().apply(context)
|
|
|
|
assert not (dst / "etc/ssl/certs").exists()
|
|
bundle = dst / "etc/pki/tls/certs/ca-certificates.crt"
|
|
assert bundle.read_bytes() == b"fake-cert-bytes\n"
|
|
pem = dst / "etc/pki/tls/certs/new-root-ca.pem"
|
|
assert pem.is_symlink()
|
|
# /usr/lib/ssl/cert.pem is Debian/Ubuntu-specific: not OpenSSL's default
|
|
# CAfile on this (RHEL-style) system, so it must not be created.
|
|
assert not (dst / "usr/lib/ssl/cert.pem").exists()
|
|
|
|
|
|
def test_apply_is_idempotent_when_src_and_dst_are_the_same_live_root(
|
|
tmp_path,
|
|
) -> None:
|
|
# Regression test: applying directly to a live root (--src / --dst /,
|
|
# as the Ansible role does) where /usr/lib/ssl/cert.pem already exists
|
|
# (as it would on any host with ca-certificates already installed) must
|
|
# not resolve that pre-existing symlink away when recreating it - doing
|
|
# so would unlink/replace the bundle it points to instead, corrupting it
|
|
# into a symlink to itself and breaking every subsequent run.
|
|
root = tmp_path / "root"
|
|
etc_ssl_certs = root / "etc/ssl/certs"
|
|
etc_ssl_certs.mkdir(parents=True)
|
|
(etc_ssl_certs / "ca-certificates.crt").write_bytes(b"preexisting-bundle-bytes")
|
|
usr_lib_ssl = root / "usr/lib/ssl"
|
|
usr_lib_ssl.mkdir(parents=True)
|
|
(usr_lib_ssl / "cert.pem").symlink_to("/etc/ssl/certs/ca-certificates.crt")
|
|
|
|
context = Context(config=make_config(True), dst=root, src=root)
|
|
|
|
for _ in range(2):
|
|
CertificatesModule().apply(context)
|
|
|
|
bundle = etc_ssl_certs / "ca-certificates.crt"
|
|
assert not bundle.is_symlink()
|
|
assert bundle.read_bytes() == b"fake-cert-bytes\n"
|
|
|
|
openssl_default = usr_lib_ssl / "cert.pem"
|
|
assert openssl_default.is_symlink()
|
|
assert (
|
|
openssl_default.readlink().as_posix()
|
|
== "/etc/ssl/certs/ca-certificates.crt"
|
|
)
|