Files
bongo/contrib/testing/dkim-material-check.py
2026-07-22 17:18:36 +02:00

90 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Exercise real OpenDKIM generation and Bongo's GnuTLS key import path."""
from __future__ import annotations
import os
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from types import SimpleNamespace
from unittest import mock
source = Path(os.environ.get(
"BONGO_SOURCE_ROOT", Path(__file__).resolve().parents[2])).resolve()
sys.path.insert(0, str(source / "src" / "libs" / "python"))
from bongo.configuration.model import Scenario # noqa: E402
from bongo.configuration.security_material import ( # noqa: E402
_private_key_data, provision_security_material,
)
generator = shutil.which("opendkim-genkey")
certtool = shutil.which("certtool")
if not generator or not certtool:
raise SystemExit("opendkim-genkey and GnuTLS certtool are required")
with tempfile.TemporaryDirectory(prefix="bongo-dkim-import-", dir="/tmp") as name:
root = Path(name)
source_key = root / "source" / "bongo.private"
source_key.parent.mkdir(mode=0o700)
subprocess.run([
generator, "-b", "2048", "-d", "example.test",
"-D", str(source_key.parent), "-s", "bongo",
], check=True, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, timeout=120)
source_key.chmod(0o600)
private_key, public_key = _private_key_data(source_key)
generated_txt = (source_key.parent / "bongo.txt").read_text(
encoding="ascii")
published = "".join(re.findall(r'"([^"]*)"', generated_txt))
match = re.search(r"(?:^|;)\s*p\s*=\s*([^;\s]+)", published,
re.IGNORECASE)
if match is None or match.group(1) != public_key:
raise SystemExit("GnuTLS public-key derivation differs from OpenDKIM")
paths = SimpleNamespace(
ssl_dir=root / "etc/bongo/ssl.d",
dkim_dir=root / "etc/bongo/dkim.d",
dmarc_dir=root / "etc/bongo/dmarc.d")
scenario = Scenario(
hostname="mail.example.test", bind_address="192.0.2.5",
domains=["example.test"], admin_password="not-used-by-this-test",
dkim_selector="bongo", dkim_key_directory=str(paths.dkim_dir),
dkim_import_keys={"example.test": str(source_key)},
mail_dns={"example.test": {
"spf": ["v=spf1 mx -all"],
"dkim": [f"v=DKIM1; k=rsa; p={public_key}"],
"dmarc": ["v=DMARC1; p=reject; "
"rua=mailto:dmarc-reports@example.test"],
"errors": [],
}})
with mock.patch(
"bongo.configuration.security_material.os.geteuid",
return_value=0), mock.patch(
"bongo.configuration.security_material._bongo_ids",
return_value=(os.getuid(), os.getgid())), mock.patch(
"bongo.configuration.security_material.os.chown"), mock.patch(
"bongo.configuration.security_material.os.fchown"):
result = provision_security_material(paths, scenario)
repeated = provision_security_material(paths, scenario)
target = paths.dkim_dir / "example.test" / "bongo.private"
if target.read_bytes() != private_key:
raise SystemExit("imported DKIM key differs from its source")
if target.stat().st_mode & 0o777 != 0o640:
raise SystemExit("imported DKIM key does not use mode 0640")
bongo_txt = (target.parent / "bongo.txt").read_text(encoding="ascii")
bongo_published = "".join(re.findall(r'"([^"]*)"', bongo_txt))
if public_key not in bongo_published:
raise SystemExit("Bongo DKIM DNS hint does not match the imported key")
if result[0].actions or repeated[0].actions:
raise SystemExit("matching published DNS unexpectedly requires an action")
print("PASS OpenDKIM RSA-2048 generation and GnuTLS DKIM import/reuse")