85 lines
3.8 KiB
Python
Executable File
85 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Verify the setup-created mail-DNS task and administrator message."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import http.cookiejar
|
|
import imaplib
|
|
import json
|
|
import os
|
|
import ssl
|
|
import urllib.request
|
|
from email import policy
|
|
from email.parser import BytesParser
|
|
from urllib.parse import urlsplit
|
|
|
|
|
|
base_url = os.environ.get("BONGO_TEST_WEB_URL", "http://127.0.0.1:8080").rstrip("/")
|
|
user = os.environ.get("BONGO_TEST_USER", "admin")
|
|
password = os.environ.get("BONGO_TEST_PASSWORD", "")
|
|
domain = os.environ.get("BONGO_TEST_DOMAIN", "bongo.test")
|
|
timeout = int(os.environ.get("BONGO_TEST_TIMEOUT", "90"))
|
|
if not password:
|
|
raise SystemExit("set BONGO_TEST_PASSWORD for the disposable administrator")
|
|
|
|
cookies = http.cookiejar.CookieJar()
|
|
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookies))
|
|
request = urllib.request.Request(
|
|
base_url + "/api/session",
|
|
data=json.dumps({"user": user, "password": password}).encode("utf-8"),
|
|
headers={"Content-Type": "application/json"}, method="POST")
|
|
with opener.open(request, timeout=timeout) as response:
|
|
if response.status != 201:
|
|
raise SystemExit(f"Web login returned HTTP {response.status}")
|
|
|
|
with opener.open(base_url + "/api/tasks?include_closed=1",
|
|
timeout=timeout) as response:
|
|
tasks = json.load(response).get("tasks", [])
|
|
matching = [task for task in tasks
|
|
if task.get("type") == "mail-dns" and task.get("key") == domain]
|
|
if len(matching) != 1:
|
|
raise SystemExit(f"expected one mail-dns task for {domain}, got {len(matching)}")
|
|
task = matching[0]
|
|
if task.get("severity") != "warning" or not task.get("dismissible"):
|
|
raise SystemExit("mail-dns task does not have the expected actionable policy")
|
|
details = task.get("details", {})
|
|
if details.get("domain") != domain or not details.get("actions"):
|
|
raise SystemExit("mail-dns task has no actionable DNS details")
|
|
if set(details.get("suggested", {})) != {"spf", "dkim", "dmarc"}:
|
|
raise SystemExit("mail-dns task lacks an SPF, DKIM, or DMARC suggestion")
|
|
|
|
host = os.environ.get("BONGO_TEST_IMAP_HOST") or urlsplit(base_url).hostname
|
|
context = ssl.create_default_context()
|
|
if os.environ.get("BONGO_TEST_INSECURE_TLS") == "1":
|
|
context.check_hostname = False
|
|
context.verify_mode = ssl.CERT_NONE
|
|
with imaplib.IMAP4_SSL(
|
|
host, int(os.environ.get("BONGO_TEST_IMAPS_PORT", "993")),
|
|
ssl_context=context, timeout=timeout) as client:
|
|
client.login(user, password)
|
|
status, _count = client.select("INBOX", readonly=True)
|
|
if status != "OK":
|
|
raise SystemExit("cannot select administrator INBOX")
|
|
status, identifiers = client.search(
|
|
None, "HEADER", "X-Bongo-Notification", "mail-dns")
|
|
if status != "OK" or not identifiers or not identifiers[0].split():
|
|
raise SystemExit("administrator mail-DNS notification was not found")
|
|
identifier = identifiers[0].split()[-1]
|
|
status, data = client.fetch(identifier, "(BODY.PEEK[])")
|
|
if status != "OK":
|
|
raise SystemExit("cannot read administrator mail-DNS notification")
|
|
payload = next((item[1] for item in data
|
|
if isinstance(item, tuple) and isinstance(item[1], bytes)), b"")
|
|
|
|
message = BytesParser(policy=policy.default).parsebytes(payload)
|
|
if message.get("X-Bongo-Notification") != "mail-dns":
|
|
raise SystemExit("administrator message has the wrong notification marker")
|
|
if domain not in str(message.get("To", "")):
|
|
raise SystemExit("administrator message has the wrong recipient domain")
|
|
upper_payload = payload.upper()
|
|
if (b"-----BEGIN PRIVATE KEY-----" in upper_payload or
|
|
b"-----BEGIN RSA PRIVATE KEY-----" in upper_payload or
|
|
b"-----BEGIN ENCRYPTED PRIVATE KEY-----" in upper_payload):
|
|
raise SystemExit("administrator message contains private-key material")
|
|
print("PASS actionable mail-DNS task and administrator notification")
|