Add real DKIM material integration check
This commit is contained in:
@@ -35,6 +35,9 @@ one-off files in `/tmp`:
|
||||
- `mail-dns-live-check.py` logs in through the real Web and IMAPS interfaces
|
||||
and verifies that setup produced both an actionable SPF/DKIM/DMARC task and
|
||||
a forwardable administrator message without private-key material.
|
||||
- `dkim-material-check.py` compares a real OpenDKIM-generated RSA-2048 key
|
||||
with Bongo's GnuTLS-derived public value, imports it, checks permissions and
|
||||
DNS output, then verifies byte-identical reuse.
|
||||
|
||||
The complete mapping from the original one-off `/tmp` names to maintained
|
||||
scripts is recorded in `tmp-script-map.md`.
|
||||
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user