Files
id_configurator/id_configurator/modules/certificates.py
T
2026-08-28 16:52:07 +02:00

82 lines
3.1 KiB
Python

"""Install root certificates into the system's CA store.
Certificates from the configuration are dropped into the "local" CA
directory, then the real `update-ca-certificates` binary is invoked to
rebuild the trust store (bundle + hashed symlinks) exactly like it would on
a real Debian/Ubuntu system, instead of reimplementing that logic here.
"""
from __future__ import annotations
import base64
import logging
import subprocess
from id_configurator.context import Context
from id_configurator.modules.base import Module, register
logger = logging.getLogger(__name__)
_LOCAL_CERTS_DIR = "usr/local/share/ca-certificates"
_CERTSCONF = "etc/ca-certificates.conf"
_CERTSDIR = "usr/share/ca-certificates"
_ETCCERTSDIR = "etc/ssl/certs"
@register
class CertificatesModule(Module):
name = "certificates"
def is_relevant(self, context: Context) -> bool:
return bool(context.config.configurations.certificates)
def apply(self, context: Context) -> None:
configured_names = {
certificate.name
for certificate in context.config.configurations.certificates
}
for existing in context.iter_src_dir(_LOCAL_CERTS_DIR):
if existing.stem not in configured_names:
context.write_file(
f"{_LOCAL_CERTS_DIR}/{existing.name}", existing.read_bytes()
)
for certificate in context.config.configurations.certificates:
content = base64.b64decode(certificate.content)
context.write_file(f"{_LOCAL_CERTS_DIR}/{certificate.name}.crt", content)
logger.info("installed root certificate %s", certificate.name)
self._update_ca_certificates_conf(context)
@staticmethod
def _update_ca_certificates_conf(context: Context) -> None:
etccertsdir = context.dst_path(_ETCCERTSDIR)
localcertsdir = context.dst_path(_LOCAL_CERTS_DIR)
etccertsdir.mkdir(parents=True, exist_ok=True)
localcertsdir.mkdir(parents=True, exist_ok=True)
args = [
"update-ca-certificates",
"--etccertsdir",
str(etccertsdir),
"--localcertsdir",
str(localcertsdir),
]
certsconf = context.src_path(_CERTSCONF)
if certsconf is not None and certsconf.is_file():
args += ["--certsconf", str(certsconf)]
certsdir = context.src_path(_CERTSDIR)
if certsdir is not None and certsdir.is_dir():
args += ["--certsdir", str(certsdir)]
logger.debug("running: %s", " ".join(args))
try:
result = subprocess.run(args, check=True, capture_output=True, text=True)
except FileNotFoundError as exc:
raise RuntimeError(
"update-ca-certificates is not available; the ca-certificates package must be installed "
"in the environment running id-configurator"
) from exc
except subprocess.CalledProcessError as exc:
raise RuntimeError(f"update-ca-certificates failed:\n{exc.stderr}") from exc
if result.stderr:
logger.debug("%s", result.stderr.strip())