phishing defense

Performing DMARC Policy Enforcement Rollout

Execute a phased DMARC rollout from p=none monitoring through p=quarantine to p=reject enforcement, ensuring all legitimate email sources are authenticated before blocking unauthorized senders.

anti-spoofingdkimdmarcdnsemail-authenticationemail-securityphishingspf
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Domain-based Message Authentication, Reporting and Conformance (DMARC) is the cornerstone of email anti-spoofing protection. A DMARC rollout progresses through three phases: monitoring (p=none), quarantine (p=quarantine), and full enforcement (p=reject). When configured at p=reject, any email that fails both SPF and DKIM checks is outright rejected. Google and Yahoo now require DMARC for bulk senders (5,000+ emails), driving a 65% reduction in unauthenticated messages. The rollout typically takes 3-6 months for safe deployment.

When to Use

  • When conducting security assessments that involve performing dmarc policy enforcement rollout
  • When following incident response procedures for related security events
  • When performing scheduled security testing or auditing activities
  • When validating security controls through hands-on testing

Prerequisites

  • Administrative access to DNS management for the domain
  • Understanding of SPF, DKIM, and DMARC protocols (RFC 7208, 6376, 7489)
  • Complete inventory of all legitimate email sending sources
  • DMARC reporting analysis tool (EasyDMARC, DMARCLY, Valimail, or dmarcian)
  • Email gateway with DMARC enforcement capability

Key Concepts

DMARC Policy Levels

Policy Behavior Use Case
p=none Monitor only, no action on failures Discovery phase
p=quarantine Send failing messages to spam/junk Transition phase
p=reject Block failing messages entirely Full enforcement

DMARC Record Anatomy

v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc-agg@company.com; ruf=mailto:dmarc-forensic@company.com; adkim=r; aspf=r; fo=1
  • p: Policy for organizational domain
  • sp: Policy for subdomains
  • pct: Percentage of messages subject to policy (for gradual rollout)
  • rua: Aggregate report destination (daily XML reports)
  • ruf: Forensic report destination (per-failure reports)
  • adkim: DKIM alignment mode (r=relaxed, s=strict)
  • aspf: SPF alignment mode (r=relaxed, s=strict)
  • fo: Failure reporting options (0=both fail, 1=either fails)

SPF and DKIM Alignment

  • SPF Alignment: The domain in the Return-Path (envelope sender) must match the From header domain
  • DKIM Alignment: The d= domain in the DKIM signature must match the From header domain
  • Relaxed: Organizational domain match (sub.example.com matches example.com)
  • Strict: Exact domain match required

Workflow

Step 1: Inventory All Sending Sources (Week 1-2)

  • Audit all systems sending email as your domain (marketing, CRM, ticketing, transactional)
  • Document third-party services: Salesforce, Mailchimp, SendGrid, Zendesk, etc.
  • Identify internal mail servers, applications, and relay hosts
  • Check for shadow IT email sending (departments using unauthorized services)

Step 2: Configure SPF and DKIM (Week 2-4)

  • Consolidate SPF record with all legitimate sending IPs and includes
  • Ensure SPF record stays under 10 DNS lookup limit
  • Generate and publish DKIM keys for each sending source
  • Verify DKIM signing works for all outbound mail paths
  • Test with MX Toolbox or dmarcian SPF/DKIM validators

Step 3: Deploy DMARC in Monitoring Mode (Week 4-6)

  • Publish initial DMARC record: v=DMARC1; p=none; rua=mailto:dmarc@company.com; fo=1
  • Wait 1-2 weeks to collect representative aggregate reports
  • Analyze reports to identify unauthorized senders and alignment failures
  • Fix SPF/DKIM for all legitimate sources showing failures
  • Iterate until all legitimate mail passes DMARC

Step 4: Move to Quarantine with pct Tag (Week 6-12)

  • Update to quarantine at 10%: v=DMARC1; p=quarantine; pct=10; rua=...
  • Monitor for false positives (legitimate mail being quarantined)
  • Increase pct gradually: 10% -> 25% -> 50% -> 75% -> 100%
  • Each increase: wait 1-2 weeks and review reports before advancing
  • Fix any remaining alignment issues discovered at each stage

Step 5: Advance to Reject Policy (Week 12-20)

  • After stable quarantine at 100%, move to reject at 10%: v=DMARC1; p=reject; pct=10; rua=...
  • Gradually increase pct: 10% -> 25% -> 50% -> 100%
  • Monitor closely for legitimate mail being rejected
  • Establish emergency rollback procedure (revert to quarantine)
  • Apply subdomain policy: sp=reject for subdomains

Step 6: Ongoing Monitoring and Maintenance

  • Continuously monitor DMARC aggregate reports
  • Add new sending sources before they start sending
  • Review forensic reports for spoofing attempts
  • Maintain SPF record as sending infrastructure changes
  • Rotate DKIM keys annually

Tools & Resources

  • EasyDMARC: DMARC monitoring dashboard with aggregate/forensic report analysis
  • DMARCLY: SPF, DKIM, DMARC monitoring with auto-DNS updates
  • dmarcian: DMARC deployment and management platform
  • Valimail: Automated DMARC enforcement with hosted authentication
  • MX Toolbox: DNS record lookup and DMARC validator
  • Google Admin Toolbox: DMARC check and diagnostic tools

Validation

  • DMARC record published and resolving correctly at _dmarc.domain.com
  • All legitimate sending sources pass SPF and/or DKIM alignment
  • Aggregate reports show >99% legitimate mail passing DMARC
  • Spoofed messages from unauthorized senders are rejected
  • No legitimate mail blocked after full p=reject enforcement
  • Subdomain policy (sp=) also set to reject
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md0.9 KB

API Reference — Performing DMARC Policy Enforcement Rollout

Libraries Used

  • dnspython (dns.resolver): DNS TXT record queries for DMARC, SPF, DKIM

CLI Interface

python agent.py check --domain example.com
python agent.py audit --domains example.com example.org [--selectors default google k1]

Core Functions

check_dmarc(domain) — Query _dmarc.<domain> TXT

check_spf(domain) — Query domain TXT for v=spf1

check_dkim(domain, selector) — Query <selector>._domainkey.<domain>

audit_domains(domains, selectors) — Full DMARC/SPF/DKIM audit with scoring

DMARC Policy Levels

Policy Enforcement Score
none No enforcement (monitoring only) 0
quarantine Suspicious mail sent to spam +20
reject Unauthorized mail rejected +40

Dependencies

pip install dnspython>=2.4
standards.md1.7 KB

Standards & References: Performing DMARC Policy Enforcement Rollout

Core Protocol Standards

  • RFC 7489: Domain-based Message Authentication, Reporting, and Conformance (DMARC)
  • RFC 7208: Sender Policy Framework (SPF) for Authorizing Use of Domains in Email
  • RFC 6376: DomainKeys Identified Mail (DKIM) Signatures
  • RFC 8616: Email Authentication for Internationalized Mail
  • RFC 8601: Message Header Field for Indicating Message Authentication Status

Regulatory Requirements (2025)

  • Google Bulk Sender Requirements: DMARC required for senders of 5,000+ messages/day to Gmail
  • Yahoo Sender Requirements: DMARC authentication required for bulk senders
  • PCI DSS 4.0 Requirement 5.3: Anti-phishing mechanisms including email authentication
  • NIST SP 800-177 Rev.1: Trustworthy Email guidance

DMARC Policy Progression

Phase Duration Record pct Monitoring
Discovery 2-4 weeks p=none N/A Daily report review
Soft enforce 4-6 weeks p=quarantine 10->100 Twice-weekly review
Hard enforce 4-6 weeks p=reject 10->100 Weekly review
Maintenance Ongoing p=reject 100 Monthly review

SPF Lookup Limit

  • Maximum 10 DNS lookups per SPF evaluation (RFC 7208 Section 4.6.4)
  • Each include:, a:, mx:, redirect=, and exists: counts as one lookup
  • Exceeding 10 lookups causes SPF permerror (treated as fail by some receivers)
  • Use SPF flattening tools to stay within limit

MITRE ATT&CK References

  • T1566.001: Phishing: Spearphishing Attachment
  • T1566.002: Phishing: Spearphishing Link
  • T1586.002: Compromise Accounts: Email Accounts
  • T1656: Impersonation
workflows.md2.7 KB

Workflows: Performing DMARC Policy Enforcement Rollout

Workflow 1: DMARC Phased Rollout

Week 1-2: Discovery
  |
  v
[Inventory all legitimate email sending sources]
  +-- Internal mail servers
  +-- Third-party SaaS (marketing, CRM, support)
  +-- Transactional email services
  +-- Application-generated email
  |
  v
Week 2-4: Foundation
  |
  v
[Configure SPF and DKIM for all sources]
  +-- Publish SPF record with all includes
  +-- Validate SPF under 10 lookup limit
  +-- Generate DKIM keys per sending source
  +-- Test outbound authentication
  |
  v
Week 4-6: Monitor
  |
  v
[Publish p=none DMARC record]
  +-- Collect aggregate reports for 2 weeks
  +-- Analyze: who is sending as your domain?
  +-- Fix alignment failures for legitimate sources
  +-- Identify unauthorized/spoofing sources
  |
  v
Week 6-12: Quarantine
  |
  v
[Move to p=quarantine with gradual pct increase]
  +-- pct=10 (2 weeks) -> check false positives
  +-- pct=25 (2 weeks) -> verify clean
  +-- pct=50 (1 week) -> validate stability
  +-- pct=100 (2 weeks) -> confirm all legitimate passes
  |
  v
Week 12-20: Reject
  |
  v
[Move to p=reject with gradual pct increase]
  +-- pct=10 (2 weeks) -> monitor rejections
  +-- pct=25 (2 weeks) -> verify no legitimate blocked
  +-- pct=50 (1 week) -> near full enforcement
  +-- pct=100 -> FULL ENFORCEMENT ACHIEVED
  |
  v
Ongoing: Maintenance
  +-- Monitor aggregate reports monthly
  +-- Update SPF/DKIM for new sending sources
  +-- Rotate DKIM keys annually

Workflow 2: DMARC Report Analysis

Aggregate report received (daily XML)
  |
  v
[Parse report in DMARC analyzer]
  |
  v
[Categorize sending sources]
  +-- PASS: Legitimate, properly authenticated
  +-- FAIL (known): Legitimate source with auth issue -> FIX
  +-- FAIL (unknown): Unauthorized sender -> INVESTIGATE
  |
  v
[For each FAIL (known)]
  +-- Identify missing SPF include or DKIM config
  +-- Update DNS records
  +-- Wait for next report to confirm fix
  |
  v
[For each FAIL (unknown)]
  +-- Is it spoofing? -> Document for enforcement case
  +-- Is it shadow IT? -> Onboard or decommission
  +-- Is it forwarding? -> ARC chain may be needed

Workflow 3: Emergency Rollback

Legitimate email being rejected (false positive detected)
  |
  v
[Immediate: Roll back pct or policy]
  +-- Reduce pct to previous stable level
  +-- OR roll back from reject to quarantine
  +-- OR roll back from quarantine to none
  |
  v
[Investigate root cause]
  +-- Check aggregate reports for failing source
  +-- Verify SPF/DKIM configuration for source
  +-- Check for forwarding or mailing list issues
  |
  v
[Fix and re-advance]
  +-- Correct authentication issue
  +-- Verify fix in next report cycle
  +-- Resume gradual pct advancement

Scripts 2

agent.py5.4 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for performing DMARC policy enforcement rollout with DNS record analysis."""

import json
import argparse
from datetime import datetime

try:
    import dns.resolver
except ImportError:
    dns = None


def check_dmarc(domain):
    """Query and parse DMARC record for a domain."""
    try:
        answers = dns.resolver.resolve(f"_dmarc.{domain}", "TXT")
        for rdata in answers:
            txt = rdata.to_text().strip('"')
            if txt.startswith("v=DMARC1"):
                return parse_dmarc_tags(txt, domain)
        return {"domain": domain, "dmarc_found": False}
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, Exception) as e:
        return {"domain": domain, "dmarc_found": False, "error": str(e)}


def parse_dmarc_tags(record, domain):
    tags = {}
    for part in record.split(";"):
        part = part.strip()
        if "=" in part:
            k, _, v = part.partition("=")
            tags[k.strip()] = v.strip()
    policy = tags.get("p", "none")
    findings = []
    if policy == "none":
        findings.append({"severity": "HIGH", "finding": "DMARC policy is 'none' — no enforcement"})
    elif policy == "quarantine":
        findings.append({"severity": "MEDIUM", "finding": "DMARC policy is 'quarantine' — partial enforcement"})
    if "rua" not in tags:
        findings.append({"severity": "MEDIUM", "finding": "No aggregate report URI (rua) configured"})
    pct = int(tags.get("pct", "100"))
    if pct < 100:
        findings.append({"severity": "INFO", "finding": f"Policy applied to {pct}% of messages"})
    sp = tags.get("sp", policy)
    if sp == "none" and policy != "none":
        findings.append({"severity": "MEDIUM", "finding": "Subdomain policy (sp) is 'none'"})
    return {
        "domain": domain, "dmarc_found": True, "record": record,
        "policy": policy, "subdomain_policy": sp, "percentage": pct,
        "rua": tags.get("rua"), "ruf": tags.get("ruf"),
        "adkim": tags.get("adkim", "r"), "aspf": tags.get("aspf", "r"),
        "findings": findings,
        "enforcement_level": "full" if policy == "reject" and pct == 100 else "partial" if policy != "none" else "none",
    }


def check_spf(domain):
    """Query and analyze SPF record."""
    try:
        answers = dns.resolver.resolve(domain, "TXT")
        for rdata in answers:
            txt = rdata.to_text().strip('"')
            if txt.startswith("v=spf1"):
                mechanisms = txt.split()
                qualifier = mechanisms[-1] if mechanisms else "~all"
                return {"domain": domain, "spf_found": True, "record": txt,
                        "mechanisms": mechanisms, "qualifier": qualifier,
                        "strict": qualifier == "-all"}
        return {"domain": domain, "spf_found": False}
    except Exception as e:
        return {"domain": domain, "spf_found": False, "error": str(e)}


def check_dkim(domain, selector="default"):
    """Query DKIM public key record."""
    try:
        fqdn = f"{selector}._domainkey.{domain}"
        answers = dns.resolver.resolve(fqdn, "TXT")
        for rdata in answers:
            txt = rdata.to_text().strip('"')
            if "p=" in txt:
                return {"domain": domain, "selector": selector, "dkim_found": True, "record": txt[:200]}
        return {"domain": domain, "selector": selector, "dkim_found": False}
    except Exception as e:
        return {"domain": domain, "selector": selector, "dkim_found": False, "error": str(e)}


def audit_domains(domains, selectors=None):
    """Audit multiple domains for DMARC/SPF/DKIM readiness."""
    selectors = selectors or ["default", "google", "selector1", "selector2", "k1"]
    results = []
    for domain in domains:
        domain = domain.strip()
        if not domain:
            continue
        dmarc = check_dmarc(domain)
        spf = check_spf(domain)
        dkim_results = []
        for sel in selectors:
            dkim = check_dkim(domain, sel)
            if dkim.get("dkim_found"):
                dkim_results.append(dkim)
        score = 0
        if dmarc.get("policy") == "reject":
            score += 40
        elif dmarc.get("policy") == "quarantine":
            score += 20
        if spf.get("strict"):
            score += 30
        elif spf.get("spf_found"):
            score += 15
        if dkim_results:
            score += 30
        results.append({
            "domain": domain, "dmarc": dmarc, "spf": spf,
            "dkim": dkim_results, "security_score": score,
        })
    return {"timestamp": datetime.utcnow().isoformat(), "domains": results}


def main():
    if not dns:
        print(json.dumps({"error": "dnspython not installed"}))
        return
    parser = argparse.ArgumentParser(description="DMARC Policy Enforcement Rollout Agent")
    sub = parser.add_subparsers(dest="command")
    d = sub.add_parser("check", help="Check single domain")
    d.add_argument("--domain", required=True)
    a = sub.add_parser("audit", help="Audit multiple domains")
    a.add_argument("--domains", nargs="+", required=True)
    a.add_argument("--selectors", nargs="*", default=None)
    args = parser.parse_args()
    if args.command == "check":
        result = {"dmarc": check_dmarc(args.domain), "spf": check_spf(args.domain)}
    elif args.command == "audit":
        result = audit_domains(args.domains, args.selectors)
    else:
        parser.print_help()
        return
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
process.py11.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
DMARC Policy Enforcement Rollout Analyzer

Checks current DMARC, SPF, and DKIM status for a domain and provides
rollout recommendations. Parses DMARC aggregate reports to identify
sending sources and authentication failures.

Usage:
    python process.py check --domain example.com
    python process.py parse-report --report-file aggregate.xml
    python process.py spf-audit --domain example.com
"""

import argparse
import json
import re
import sys
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field, asdict
from collections import defaultdict

try:
    import dns.resolver
    HAS_DNS = True
except ImportError:
    HAS_DNS = False


@dataclass
class DMARCStatus:
    """Current DMARC deployment status."""
    domain: str = ""
    dmarc_record: str = ""
    dmarc_policy: str = ""
    dmarc_pct: int = 100
    subdomain_policy: str = ""
    rua_configured: bool = False
    ruf_configured: bool = False
    spf_record: str = ""
    spf_valid: bool = False
    spf_lookup_count: int = 0
    dkim_found: bool = False
    current_phase: str = ""
    next_action: str = ""
    issues: list = field(default_factory=list)


@dataclass
class ReportSummary:
    """DMARC aggregate report summary."""
    report_org: str = ""
    report_domain: str = ""
    date_range: str = ""
    total_messages: int = 0
    pass_count: int = 0
    fail_count: int = 0
    pass_rate: float = 0.0
    sources: list = field(default_factory=list)
    failing_sources: list = field(default_factory=list)


def count_spf_lookups(spf_record: str) -> int:
    """Count DNS lookups in SPF record (max 10 allowed)."""
    lookup_count = 0
    lookup_mechanisms = ['include:', 'a:', 'mx:', 'redirect=', 'exists:']
    for mechanism in lookup_mechanisms:
        lookup_count += spf_record.lower().count(mechanism)
    # bare 'a' and 'mx' without ':' also count
    parts = spf_record.split()
    for part in parts:
        p = part.lstrip('+').lstrip('-').lstrip('~').lstrip('?')
        if p in ('a', 'mx'):
            lookup_count += 1
    return lookup_count


def check_dmarc_status(domain: str) -> DMARCStatus:
    """Check DMARC, SPF, and DKIM status for a domain."""
    status = DMARCStatus(domain=domain)

    if not HAS_DNS:
        status.issues.append("dnspython not installed. Install with: pip install dnspython")
        return status

    # Check DMARC
    try:
        answers = dns.resolver.resolve(f"_dmarc.{domain}", 'TXT')
        for rdata in answers:
            txt = str(rdata).strip('"')
            if txt.startswith('v=DMARC1'):
                status.dmarc_record = txt
                policy = re.search(r'p=(\w+)', txt)
                if policy:
                    status.dmarc_policy = policy.group(1)
                pct = re.search(r'pct=(\d+)', txt)
                if pct:
                    status.dmarc_pct = int(pct.group(1))
                sp = re.search(r'sp=(\w+)', txt)
                if sp:
                    status.subdomain_policy = sp.group(1)
                status.rua_configured = 'rua=' in txt
                status.ruf_configured = 'ruf=' in txt
                break
    except Exception:
        status.issues.append("No DMARC record found - start with p=none")

    # Check SPF
    try:
        answers = dns.resolver.resolve(domain, 'TXT')
        for rdata in answers:
            txt = str(rdata).strip('"')
            if txt.startswith('v=spf1'):
                status.spf_record = txt
                status.spf_lookup_count = count_spf_lookups(txt)
                status.spf_valid = status.spf_lookup_count <= 10
                if not status.spf_valid:
                    status.issues.append(
                        f"SPF record exceeds 10 lookup limit "
                        f"({status.spf_lookup_count} lookups). Use SPF flattening."
                    )
                break
    except Exception:
        status.issues.append("No SPF record found")

    # Check common DKIM selectors
    selectors = ['default', 'google', 'selector1', 'selector2', 'proofpoint',
                 'mimecast', 's1', 's2', 'k1']
    for selector in selectors:
        try:
            dns.resolver.resolve(f"{selector}._domainkey.{domain}", 'TXT')
            status.dkim_found = True
            break
        except Exception:
            continue

    if not status.dkim_found:
        status.issues.append("No DKIM record found for common selectors")

    # Determine current phase and next action
    if not status.dmarc_record:
        status.current_phase = "Not started"
        status.next_action = "Publish DMARC record with p=none and rua= for monitoring"
    elif status.dmarc_policy == "none":
        status.current_phase = "Monitoring (p=none)"
        status.next_action = "Analyze reports, fix auth issues, then move to p=quarantine pct=10"
    elif status.dmarc_policy == "quarantine":
        if status.dmarc_pct < 100:
            status.current_phase = f"Quarantine at {status.dmarc_pct}%"
            next_pct = min(status.dmarc_pct * 2, 100)
            status.next_action = f"Increase pct to {next_pct} after reviewing reports"
        else:
            status.current_phase = "Quarantine at 100%"
            status.next_action = "Move to p=reject pct=10 after stable period"
    elif status.dmarc_policy == "reject":
        if status.dmarc_pct < 100:
            status.current_phase = f"Reject at {status.dmarc_pct}%"
            next_pct = min(status.dmarc_pct * 2, 100)
            status.next_action = f"Increase pct to {next_pct} after reviewing reports"
        else:
            status.current_phase = "FULL ENFORCEMENT (p=reject 100%)"
            status.next_action = "Maintain: monitor reports, update auth for new sources"

    if not status.rua_configured and status.dmarc_record:
        status.issues.append("No rua= tag - add aggregate report destination")

    if status.dmarc_policy == "reject" and not status.subdomain_policy:
        status.issues.append("No subdomain policy (sp=) - subdomains can still be spoofed")

    return status


def parse_dmarc_report(xml_file: str) -> ReportSummary:
    """Parse DMARC aggregate report XML."""
    summary = ReportSummary()

    tree = ET.parse(xml_file)
    root = tree.getroot()

    # Report metadata
    metadata = root.find('.//report_metadata')
    if metadata is not None:
        org = metadata.find('org_name')
        if org is not None:
            summary.report_org = org.text or ""

    policy = root.find('.//policy_published')
    if policy is not None:
        domain = policy.find('domain')
        if domain is not None:
            summary.report_domain = domain.text or ""

    date_range = root.find('.//date_range')
    if date_range is not None:
        begin = date_range.find('begin')
        end = date_range.find('end')
        if begin is not None and end is not None:
            summary.date_range = f"{begin.text} - {end.text}"

    # Process records
    for record in root.findall('.//record'):
        row = record.find('row')
        if row is None:
            continue

        source_ip = ""
        count = 0
        spf_result = ""
        dkim_result = ""
        disposition = ""

        ip_elem = row.find('source_ip')
        if ip_elem is not None:
            source_ip = ip_elem.text or ""

        count_elem = row.find('count')
        if count_elem is not None:
            count = int(count_elem.text or 0)

        policy_eval = row.find('policy_evaluated')
        if policy_eval is not None:
            dkim_elem = policy_eval.find('dkim')
            spf_elem = policy_eval.find('spf')
            disp_elem = policy_eval.find('disposition')
            if dkim_elem is not None:
                dkim_result = dkim_elem.text or ""
            if spf_elem is not None:
                spf_result = spf_elem.text or ""
            if disp_elem is not None:
                disposition = disp_elem.text or ""

        summary.total_messages += count
        passed = (spf_result == "pass" or dkim_result == "pass")

        source_info = {
            "source_ip": source_ip,
            "count": count,
            "spf": spf_result,
            "dkim": dkim_result,
            "disposition": disposition,
            "passed": passed
        }
        summary.sources.append(source_info)

        if passed:
            summary.pass_count += count
        else:
            summary.fail_count += count
            summary.failing_sources.append(source_info)

    if summary.total_messages > 0:
        summary.pass_rate = (summary.pass_count / summary.total_messages) * 100

    return summary


def format_status_report(status: DMARCStatus) -> str:
    """Format DMARC status as readable report."""
    lines = []
    lines.append("=" * 60)
    lines.append("  DMARC ENFORCEMENT ROLLOUT STATUS")
    lines.append("=" * 60)
    lines.append(f"  Domain: {status.domain}")
    lines.append(f"  Current Phase: {status.current_phase}")
    lines.append(f"  Next Action: {status.next_action}")
    lines.append("")
    lines.append(f"  DMARC Record: {status.dmarc_record or 'NOT FOUND'}")
    lines.append(f"  DMARC Policy: {status.dmarc_policy or 'N/A'}")
    lines.append(f"  DMARC pct: {status.dmarc_pct}%")
    lines.append(f"  Subdomain Policy: {status.subdomain_policy or 'Not set'}")
    lines.append(f"  RUA Reports: {'Yes' if status.rua_configured else 'No'}")
    lines.append(f"  RUF Reports: {'Yes' if status.ruf_configured else 'No'}")
    lines.append("")
    lines.append(f"  SPF Record: {status.spf_record or 'NOT FOUND'}")
    lines.append(f"  SPF Lookups: {status.spf_lookup_count}/10")
    lines.append(f"  SPF Valid: {'Yes' if status.spf_valid else 'No'}")
    lines.append(f"  DKIM Found: {'Yes' if status.dkim_found else 'No'}")

    if status.issues:
        lines.append(f"\n  [ISSUES] ({len(status.issues)})")
        for i, issue in enumerate(status.issues, 1):
            lines.append(f"    {i}. {issue}")

    lines.append("=" * 60)
    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(description="DMARC Policy Enforcement Rollout Analyzer")
    subparsers = parser.add_subparsers(dest="command")

    check_parser = subparsers.add_parser("check", help="Check DMARC deployment status")
    check_parser.add_argument("--domain", required=True)

    report_parser = subparsers.add_parser("parse-report", help="Parse DMARC aggregate report")
    report_parser.add_argument("--report-file", required=True)

    spf_parser = subparsers.add_parser("spf-audit", help="Audit SPF record")
    spf_parser.add_argument("--domain", required=True)

    parser.add_argument("--json", action="store_true")

    args = parser.parse_args()

    if args.command == "check":
        result = check_dmarc_status(args.domain)
        if args.json:
            print(json.dumps(asdict(result), indent=2))
        else:
            print(format_status_report(result))

    elif args.command == "parse-report":
        result = parse_dmarc_report(args.report_file)
        if args.json:
            print(json.dumps(asdict(result), indent=2))
        else:
            print(f"Report from: {result.report_org}")
            print(f"Domain: {result.report_domain}")
            print(f"Total messages: {result.total_messages}")
            print(f"Pass rate: {result.pass_rate:.1f}%")
            if result.failing_sources:
                print(f"\nFailing sources ({len(result.failing_sources)}):")
                for src in result.failing_sources:
                    print(f"  IP: {src['source_ip']} | Count: {src['count']} | "
                          f"SPF: {src['spf']} | DKIM: {src['dkim']}")

    elif args.command == "spf-audit":
        status = check_dmarc_status(args.domain)
        print(f"SPF Record: {status.spf_record}")
        print(f"DNS Lookups: {status.spf_lookup_count}/10")
        print(f"Valid: {'Yes' if status.spf_valid else 'OVER LIMIT'}")

    else:
        parser.print_help()


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 1.5 KB
Keep exploring