DKIM selectors where queried separatedly
This commit is contained in:
+26
-7
@@ -32,6 +32,7 @@ class ParsedSpfRecord:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class DkimRecord:
|
class DkimRecord:
|
||||||
selector: str
|
selector: str
|
||||||
|
domain: str
|
||||||
query_name: str
|
query_name: str
|
||||||
record: str | None = None
|
record: str | None = None
|
||||||
error: str | None = None
|
error: str | None = None
|
||||||
@@ -138,7 +139,7 @@ def parse_spf_records(records: list[str]) -> tuple[ParsedSpfRecord, list[str]]:
|
|||||||
def collect_domain_dns_policy(
|
def collect_domain_dns_policy(
|
||||||
domain: str,
|
domain: str,
|
||||||
*,
|
*,
|
||||||
selectors: list[str] | None = None,
|
selectors: list[str | tuple[str, str]] | None = None,
|
||||||
txt_lookup: TxtLookup | None = None,
|
txt_lookup: TxtLookup | None = None,
|
||||||
mx_lookup: MxLookup | None = None,
|
mx_lookup: MxLookup | None = None,
|
||||||
) -> DomainDnsPolicy:
|
) -> DomainDnsPolicy:
|
||||||
@@ -164,16 +165,34 @@ def collect_domain_dns_policy(
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
policy.errors.append(f"MX lookup failed: {exc}")
|
policy.errors.append(f"MX lookup failed: {exc}")
|
||||||
|
|
||||||
for selector in sorted({item.strip().lower() for item in selectors or [] if item and item.strip()}):
|
selector_domains: set[tuple[str, str]] = set()
|
||||||
query_name = f"{selector}._domainkey.{domain}"
|
for item in selectors or []:
|
||||||
|
if isinstance(item, tuple):
|
||||||
|
selector, dkim_domain = item
|
||||||
|
else:
|
||||||
|
selector, dkim_domain = item, domain
|
||||||
|
selector = (selector or "").strip().lower()
|
||||||
|
dkim_domain = (dkim_domain or domain).strip().lower().rstrip(".")
|
||||||
|
if selector and dkim_domain:
|
||||||
|
selector_domains.add((selector, dkim_domain))
|
||||||
|
|
||||||
|
for selector, dkim_domain in sorted(selector_domains):
|
||||||
|
query_name = f"{selector}._domainkey.{dkim_domain}"
|
||||||
try:
|
try:
|
||||||
records = txt_lookup(query_name)
|
records = txt_lookup(query_name)
|
||||||
dkim_records = [record for record in records if record.strip().lower().startswith("v=dkim1")]
|
dkim_records = [record for record in records if record.strip().lower().startswith("v=dkim1")]
|
||||||
policy.dkim.append(DkimRecord(selector=selector, query_name=query_name, record=dkim_records[0] if dkim_records else None))
|
policy.dkim.append(
|
||||||
|
DkimRecord(
|
||||||
|
selector=selector,
|
||||||
|
domain=dkim_domain,
|
||||||
|
query_name=query_name,
|
||||||
|
record=dkim_records[0] if dkim_records else None,
|
||||||
|
)
|
||||||
|
)
|
||||||
if not dkim_records:
|
if not dkim_records:
|
||||||
policy.errors.append(f"DKIM record not found for selector {selector}")
|
policy.errors.append(f"DKIM record not found for selector {selector} on {dkim_domain}")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
policy.dkim.append(DkimRecord(selector=selector, query_name=query_name, error=str(exc)))
|
policy.dkim.append(DkimRecord(selector=selector, domain=dkim_domain, query_name=query_name, error=str(exc)))
|
||||||
policy.errors.append(f"DKIM lookup failed for selector {selector}: {exc}")
|
policy.errors.append(f"DKIM lookup failed for selector {selector} on {dkim_domain}: {exc}")
|
||||||
|
|
||||||
return policy
|
return policy
|
||||||
|
|||||||
+18
-7
@@ -400,9 +400,9 @@ def _json_list(value: str | None) -> list:
|
|||||||
return data if isinstance(data, list) else []
|
return data if isinstance(data, list) else []
|
||||||
|
|
||||||
|
|
||||||
def _observed_dkim_selectors(session: Session, domain: str) -> list[str]:
|
def _observed_dkim_selectors(session: Session, domain: str) -> list[tuple[str, str]]:
|
||||||
rows = session.execute(
|
rows = session.execute(
|
||||||
select(AuthResult.selector)
|
select(AuthResult.selector, AuthResult.domain)
|
||||||
.select_from(AuthResult)
|
.select_from(AuthResult)
|
||||||
.join(Record)
|
.join(Record)
|
||||||
.join(Report)
|
.join(Report)
|
||||||
@@ -410,11 +410,12 @@ def _observed_dkim_selectors(session: Session, domain: str) -> list[str]:
|
|||||||
Report.domain == domain,
|
Report.domain == domain,
|
||||||
AuthResult.auth_type == "dkim",
|
AuthResult.auth_type == "dkim",
|
||||||
AuthResult.selector.is_not(None),
|
AuthResult.selector.is_not(None),
|
||||||
|
AuthResult.domain.is_not(None),
|
||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
.order_by(AuthResult.selector)
|
.order_by(AuthResult.domain, AuthResult.selector)
|
||||||
).scalars().all()
|
).all()
|
||||||
return [row for row in rows if row]
|
return [(selector, auth_domain) for selector, auth_domain in rows if selector and auth_domain]
|
||||||
|
|
||||||
|
|
||||||
def _snapshot_model(domain: str, policy: DomainDnsPolicy) -> DomainDnsSnapshot:
|
def _snapshot_model(domain: str, policy: DomainDnsPolicy) -> DomainDnsSnapshot:
|
||||||
@@ -447,6 +448,14 @@ def _latest_dns_snapshot(session: Session, domain: str) -> SimpleNamespace | Non
|
|||||||
)
|
)
|
||||||
if not snapshot:
|
if not snapshot:
|
||||||
return None
|
return None
|
||||||
|
dkim_records = _json_list(snapshot.dkim_records_json)
|
||||||
|
dkim_found = [item for item in dkim_records if isinstance(item, dict) and item.get("record")]
|
||||||
|
dkim_missing = [item for item in dkim_records if isinstance(item, dict) and not item.get("record")]
|
||||||
|
dns_errors = [
|
||||||
|
item
|
||||||
|
for item in _json_list(snapshot.errors_json)
|
||||||
|
if isinstance(item, str) and not item.startswith("DKIM lookup failed") and not item.startswith("DKIM record not found")
|
||||||
|
]
|
||||||
return SimpleNamespace(
|
return SimpleNamespace(
|
||||||
id=snapshot.id,
|
id=snapshot.id,
|
||||||
domain=snapshot.domain,
|
domain=snapshot.domain,
|
||||||
@@ -463,9 +472,11 @@ def _latest_dns_snapshot(session: Session, domain: str) -> SimpleNamespace | Non
|
|||||||
spf_record=snapshot.spf_record,
|
spf_record=snapshot.spf_record,
|
||||||
spf_all=snapshot.spf_all,
|
spf_all=snapshot.spf_all,
|
||||||
spf_includes=_json_list(snapshot.spf_includes_json),
|
spf_includes=_json_list(snapshot.spf_includes_json),
|
||||||
dkim_records=_json_list(snapshot.dkim_records_json),
|
dkim_records=dkim_records,
|
||||||
|
dkim_found=dkim_found,
|
||||||
|
dkim_missing=dkim_missing,
|
||||||
mx_records=_json_list(snapshot.mx_records_json),
|
mx_records=_json_list(snapshot.mx_records_json),
|
||||||
errors=_json_list(snapshot.errors_json),
|
errors=dns_errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1212,6 +1212,26 @@ button {
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dw-dns-missing {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dw-dns-missing summary {
|
||||||
|
color: var(--dw-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dw-dns-missing-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 8px;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.dw-panel-title {
|
.dw-panel-title {
|
||||||
color: var(--dw-text);
|
color: var(--dw-text);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
|||||||
@@ -67,18 +67,28 @@
|
|||||||
<article class="dw-dns-panel">
|
<article class="dw-dns-panel">
|
||||||
<div class="dw-list-row">
|
<div class="dw-list-row">
|
||||||
<span>DKIM</span>
|
<span>DKIM</span>
|
||||||
<span class="dw-chip dw-chip-info">{{ dns_snapshot.dkim_records | length }} selectors</span>
|
<span class="dw-chip dw-chip-info">{{ dns_snapshot.dkim_found | length }} found{% if dns_snapshot.dkim_missing %}, {{ dns_snapshot.dkim_missing | length }} missing{% endif %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dw-dns-record-list">
|
<div class="dw-dns-record-list">
|
||||||
{% for item in dns_snapshot.dkim_records %}
|
{% for item in dns_snapshot.dkim_found %}
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ item.selector }}</strong>
|
<strong>{{ item.selector }}._domainkey.{{ item.domain or domain }}</strong>
|
||||||
<code>{{ item.record or item.error or "No DKIM record found." }}</code>
|
<code>{{ item.record }}</code>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<code>No observed DKIM selectors yet.</code>
|
<code>No currently resolvable observed DKIM selectors.</code>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if dns_snapshot.dkim_missing %}
|
||||||
|
<details class="dw-dns-missing">
|
||||||
|
<summary>{{ dns_snapshot.dkim_missing | length }} observed selector lookups did not resolve</summary>
|
||||||
|
<div class="dw-dns-missing-list">
|
||||||
|
{% for item in dns_snapshot.dkim_missing %}
|
||||||
|
<code>{{ item.query_name }}{% if item.error %}: {{ item.error }}{% endif %}</code>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
</article>
|
</article>
|
||||||
<article class="dw-dns-panel">
|
<article class="dw-dns-panel">
|
||||||
<div class="dw-list-row">
|
<div class="dw-list-row">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def test_collect_domain_dns_policy_uses_observed_dkim_selectors():
|
|||||||
txt_records = {
|
txt_records = {
|
||||||
"_dmarc.example.com": ["v=DMARC1; p=reject; pct=100"],
|
"_dmarc.example.com": ["v=DMARC1; p=reject; pct=100"],
|
||||||
"example.com": ["v=spf1 include:_spf.example.net -all"],
|
"example.com": ["v=spf1 include:_spf.example.net -all"],
|
||||||
"s1._domainkey.example.com": ["v=DKIM1; k=rsa; p=abc"],
|
"s1._domainkey.mail.example.net": ["v=DKIM1; k=rsa; p=abc"],
|
||||||
}
|
}
|
||||||
|
|
||||||
def txt_lookup(name: str) -> list[str]:
|
def txt_lookup(name: str) -> list[str]:
|
||||||
@@ -34,7 +34,7 @@ def test_collect_domain_dns_policy_uses_observed_dkim_selectors():
|
|||||||
|
|
||||||
policy = collect_domain_dns_policy(
|
policy = collect_domain_dns_policy(
|
||||||
"example.com",
|
"example.com",
|
||||||
selectors=["s1"],
|
selectors=[("s1", "mail.example.net")],
|
||||||
txt_lookup=txt_lookup,
|
txt_lookup=txt_lookup,
|
||||||
mx_lookup=lambda name: ["10 mail.example.com"],
|
mx_lookup=lambda name: ["10 mail.example.com"],
|
||||||
)
|
)
|
||||||
@@ -43,5 +43,7 @@ def test_collect_domain_dns_policy_uses_observed_dkim_selectors():
|
|||||||
assert policy.spf.all_mechanism == "-all"
|
assert policy.spf.all_mechanism == "-all"
|
||||||
assert policy.mx_records == ["10 mail.example.com"]
|
assert policy.mx_records == ["10 mail.example.com"]
|
||||||
assert policy.dkim[0].selector == "s1"
|
assert policy.dkim[0].selector == "s1"
|
||||||
|
assert policy.dkim[0].domain == "mail.example.net"
|
||||||
|
assert policy.dkim[0].query_name == "s1._domainkey.mail.example.net"
|
||||||
assert policy.dkim[0].record == "v=DKIM1; k=rsa; p=abc"
|
assert policy.dkim[0].record == "v=DKIM1; k=rsa; p=abc"
|
||||||
assert policy.errors == []
|
assert policy.errors == []
|
||||||
|
|||||||
Reference in New Issue
Block a user