mirror of
https://github.com/domainaware/parsedmarc.git
synced 2026-03-05 06:16:26 +00:00
Compare commits
3 Commits
master
...
copilot/dr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4219306365 | ||
|
|
a6e009c149 | ||
|
|
33384bd612 |
64
AGENTS.md
64
AGENTS.md
@@ -1,64 +0,0 @@
|
|||||||
# AGENTS.md
|
|
||||||
|
|
||||||
This file provides guidance to AI agents when working with code in this repository.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
parsedmarc is a Python module and CLI utility for parsing DMARC aggregate (RUA), forensic (RUF), and SMTP TLS reports. It reads reports from IMAP, Microsoft Graph, Gmail API, Maildir, mbox files, or direct file paths, and outputs to JSON/CSV, Elasticsearch, OpenSearch, Splunk, Kafka, S3, Azure Log Analytics, syslog, or webhooks.
|
|
||||||
|
|
||||||
## Common Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install with dev/build dependencies
|
|
||||||
pip install .[build]
|
|
||||||
|
|
||||||
# Run all tests with coverage
|
|
||||||
pytest --cov --cov-report=xml tests.py
|
|
||||||
|
|
||||||
# Run a single test
|
|
||||||
pytest tests.py::Test::testAggregateSamples
|
|
||||||
|
|
||||||
# Lint and format
|
|
||||||
ruff check .
|
|
||||||
ruff format .
|
|
||||||
|
|
||||||
# Test CLI with sample reports
|
|
||||||
parsedmarc --debug -c ci.ini samples/aggregate/*
|
|
||||||
parsedmarc --debug -c ci.ini samples/forensic/*
|
|
||||||
|
|
||||||
# Build docs
|
|
||||||
cd docs && make html
|
|
||||||
|
|
||||||
# Build distribution
|
|
||||||
hatch build
|
|
||||||
```
|
|
||||||
|
|
||||||
To skip DNS lookups during testing, set `GITHUB_ACTIONS=true`.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
**Data flow:** Input sources → CLI (`cli.py:_main`) → Parse (`__init__.py`) → Enrich (DNS/GeoIP via `utils.py`) → Output integrations
|
|
||||||
|
|
||||||
### Key modules
|
|
||||||
|
|
||||||
- `parsedmarc/__init__.py` — Core parsing logic. Main functions: `parse_report_file()`, `parse_report_email()`, `parse_aggregate_report_xml()`, `parse_forensic_report()`, `parse_smtp_tls_report_json()`, `get_dmarc_reports_from_mailbox()`, `watch_inbox()`
|
|
||||||
- `parsedmarc/cli.py` — CLI entry point (`_main`), config file parsing, output orchestration
|
|
||||||
- `parsedmarc/types.py` — TypedDict definitions for all report types (`AggregateReport`, `ForensicReport`, `SMTPTLSReport`, `ParsingResults`)
|
|
||||||
- `parsedmarc/utils.py` — IP/DNS/GeoIP enrichment, base64 decoding, compression handling
|
|
||||||
- `parsedmarc/mail/` — Polymorphic mail connections: `IMAPConnection`, `GmailConnection`, `MSGraphConnection`, `MaildirConnection`
|
|
||||||
- `parsedmarc/{elastic,opensearch,splunk,kafkaclient,loganalytics,syslog,s3,webhook,gelf}.py` — Output integrations
|
|
||||||
|
|
||||||
### Report type system
|
|
||||||
|
|
||||||
`ReportType = Literal["aggregate", "forensic", "smtp_tls"]`. Exception hierarchy: `ParserError` → `InvalidDMARCReport` → `InvalidAggregateReport`/`InvalidForensicReport`, and `InvalidSMTPTLSReport`.
|
|
||||||
|
|
||||||
### Caching
|
|
||||||
|
|
||||||
IP address info cached for 4 hours, seen aggregate report IDs cached for 1 hour (via `ExpiringDict`).
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
- Ruff for formatting and linting (configured in `.vscode/settings.json`)
|
|
||||||
- TypedDict for structured data, type hints throughout
|
|
||||||
- Python ≥3.10 required
|
|
||||||
- Tests are in a single `tests.py` file using unittest; sample reports live in `samples/`
|
|
||||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,15 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 9.1.1
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
- Fix the use of Elasticsearch and OpenSearch API keys (PR #660 fixes issue #653)
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
- Drop support for Python 3.9 (PR #661)
|
|
||||||
|
|
||||||
## 9.1.0
|
## 9.1.0
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|||||||
@@ -61,4 +61,4 @@ for RHEL or Debian.
|
|||||||
| 3.11 | ✅ | Actively maintained; supported until June 2028 (Debian 12) |
|
| 3.11 | ✅ | Actively maintained; supported until June 2028 (Debian 12) |
|
||||||
| 3.12 | ✅ | Actively maintained; supported until May 2035 (RHEL 10) |
|
| 3.12 | ✅ | Actively maintained; supported until May 2035 (RHEL 10) |
|
||||||
| 3.13 | ✅ | Actively maintained; supported until June 2030 (Debian 13) |
|
| 3.13 | ✅ | Actively maintained; supported until June 2030 (Debian 13) |
|
||||||
| 3.14 | ✅ | Supported (requires `imapclient>=3.1.0`) |
|
| 3.14 | ✅ | Actively maintained |
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ for RHEL or Debian.
|
|||||||
| 3.11 | ✅ | Actively maintained; supported until June 2028 (Debian 12) |
|
| 3.11 | ✅ | Actively maintained; supported until June 2028 (Debian 12) |
|
||||||
| 3.12 | ✅ | Actively maintained; supported until May 2035 (RHEL 10) |
|
| 3.12 | ✅ | Actively maintained; supported until May 2035 (RHEL 10) |
|
||||||
| 3.13 | ✅ | Actively maintained; supported until June 2030 (Debian 13) |
|
| 3.13 | ✅ | Actively maintained; supported until June 2030 (Debian 13) |
|
||||||
| 3.14 | ✅ | Supported (requires `imapclient>=3.1.0`) |
|
| 3.14 | ✅ | Actively maintained |
|
||||||
|
|
||||||
```{toctree}
|
```{toctree}
|
||||||
:caption: 'Contents'
|
:caption: 'Contents'
|
||||||
|
|||||||
@@ -1058,10 +1058,10 @@ def _main():
|
|||||||
opts.elasticsearch_password = elasticsearch_config["password"]
|
opts.elasticsearch_password = elasticsearch_config["password"]
|
||||||
# Until 8.20
|
# Until 8.20
|
||||||
if "apiKey" in elasticsearch_config:
|
if "apiKey" in elasticsearch_config:
|
||||||
opts.elasticsearch_api_key = elasticsearch_config["apiKey"]
|
opts.elasticsearch_apiKey = elasticsearch_config["apiKey"]
|
||||||
# Since 8.20
|
# Since 8.20
|
||||||
if "api_key" in elasticsearch_config:
|
if "api_key" in elasticsearch_config:
|
||||||
opts.elasticsearch_api_key = elasticsearch_config["api_key"]
|
opts.elasticsearch_apiKey = elasticsearch_config["api_key"]
|
||||||
|
|
||||||
if "opensearch" in config:
|
if "opensearch" in config:
|
||||||
opensearch_config = config["opensearch"]
|
opensearch_config = config["opensearch"]
|
||||||
@@ -1098,10 +1098,10 @@ def _main():
|
|||||||
opts.opensearch_password = opensearch_config["password"]
|
opts.opensearch_password = opensearch_config["password"]
|
||||||
# Until 8.20
|
# Until 8.20
|
||||||
if "apiKey" in opensearch_config:
|
if "apiKey" in opensearch_config:
|
||||||
opts.opensearch_api_key = opensearch_config["apiKey"]
|
opts.opensearch_apiKey = opensearch_config["apiKey"]
|
||||||
# Since 8.20
|
# Since 8.20
|
||||||
if "api_key" in opensearch_config:
|
if "api_key" in opensearch_config:
|
||||||
opts.opensearch_api_key = opensearch_config["api_key"]
|
opts.opensearch_apiKey = opensearch_config["api_key"]
|
||||||
|
|
||||||
if "splunk_hec" in config.sections():
|
if "splunk_hec" in config.sections():
|
||||||
hec_config = config["splunk_hec"]
|
hec_config = config["splunk_hec"]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
__version__ = "9.1.1"
|
__version__ = "9.1.0"
|
||||||
|
|
||||||
USER_AGENT = f"parsedmarc/{__version__}"
|
USER_AGENT = f"parsedmarc/{__version__}"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
requires = [
|
requires = [
|
||||||
"hatchling>=1.27.0",
|
"hatchling>=1.27.0",
|
||||||
]
|
]
|
||||||
requires_python = ">=3.10,<3.15"
|
requires_python = ">=3.10,<3.14"
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
@@ -45,7 +45,7 @@ dependencies = [
|
|||||||
"google-auth-httplib2>=0.1.0",
|
"google-auth-httplib2>=0.1.0",
|
||||||
"google-auth-oauthlib>=0.4.6",
|
"google-auth-oauthlib>=0.4.6",
|
||||||
"google-auth>=2.3.3",
|
"google-auth>=2.3.3",
|
||||||
"imapclient>=3.1.0",
|
"imapclient>=2.1.0",
|
||||||
"kafka-python-ng>=2.2.2",
|
"kafka-python-ng>=2.2.2",
|
||||||
"lxml>=4.4.0",
|
"lxml>=4.4.0",
|
||||||
"mailsuite>=1.11.2",
|
"mailsuite>=1.11.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user