mirror of
https://github.com/domainaware/parsedmarc.git
synced 2026-07-16 13:34:56 +00:00
Refresh Microsoft Graph docs: national clouds, examples, troubleshooting (#826)
* Send report summary via Microsoft Graph; make Graph failures observable
Two related fixes shipped together:
Send via Graph: the periodic DMARC summary email can now be sent
through the already-authenticated Microsoft Graph mailbox connection
(MSGraphConnection.send_message(), /users/{mailbox}/sendMail) instead
of only SMTP. Triggered when [msgraph] is configured and [smtp] has a
`to` value but no `host` -- SMTP is always preferred when `host` is
set, with no automatic fallback to Graph on SMTP failure. Reuses the
same connection used for reading; no new send-only config mode.
email_results()'s SMTP behavior is unchanged; a new
email_results_via_msgraph() shares its content-building logic via a
new _build_report_email_content() helper. Graph's sendMail always
sends as the authenticated mailbox, so [smtp] from is ignored on this
path -- documented, along with the required Mail.Send permissions and
a caveat that delegated auth flows (UsernamePassword/DeviceCode) don't
currently request that scope, so app-only auth is the supported path
for sending. Tracks #472.
Observable Graph failures: MSGraphConnection construction, mailbox
fetch, message send, and --watch failures now catch
ClientAuthenticationError/APIError/httpx.HTTPError specifically and
log one clear ERROR line naming the mailbox, tenant, auth method, and
the Graph request-id/client-request-id when available, instead of a
bare "MS Graph Error"/"Mailbox Error" with no context. Full traceback
still preserved at --debug. --watch previously had no Graph-specific
error handling at all -- a Graph error there crashed with a raw
uncaught traceback; it now exits the same way as the other three
sites. No new config options.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Refresh Microsoft Graph docs: national clouds, examples, troubleshooting
The [msgraph] docs were accurate but missed guidance the community has
been asking for:
- graph_url now lists the actual national/sovereign-cloud endpoint
values (GCC High, DoD, China/21Vianet), with an explicit warning
that setting it alone is not sufficient -- the Entra ID auth
endpoint isn't independently configurable in parsedmarc or
mailsuite, so it always hits the global login.microsoftonline.com.
- A minimal working [msgraph] example for every auth method
(UsernamePassword, DeviceCode, ClientSecret, Certificate,
ClientAssertion) -- previously only Certificate had one, entangled
with the SMTP-sending example.
- A reading-permission matrix alongside the existing sending one, so
every auth method x own/shared-mailbox combination is explicit in
one place for both directions.
- An accurate note on the parsedmarc-named token cache: it's a
deliberate backward-compatibility choice from the 9.11.0 mailsuite
extraction (mailsuite's own default cache name differs), not a
migration users need to act on.
- A troubleshooting table for four error scenarios, verified against
source rather than assumed: admin consent and folder-resolution
failures are still live and documented with real fixes; the
event-loop and ISO-timestamp errors are historical, already fixed
below this project's dependency/version floor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+167
-4
@@ -249,12 +249,44 @@ The full set of configuration options are:
|
||||
could be a shared mailbox if the user has access to the mailbox
|
||||
- `graph_url` - str: Microsoft Graph URL. Allows for use of National Clouds (ex Azure Gov)
|
||||
(Default: https://graph.microsoft.com)
|
||||
|
||||
:::{warning}
|
||||
Setting `graph_url` alone is **not** sufficient for a national/sovereign
|
||||
cloud tenant. It only changes the Microsoft Graph API root; the
|
||||
Microsoft Entra ID (Azure AD) token endpoint used for authentication is
|
||||
not currently configurable in parsedmarc or `mailsuite`, and always
|
||||
defaults to the global `https://login.microsoftonline.com`. A true
|
||||
national-cloud deployment also needs its own Entra ID endpoint, so
|
||||
`graph_url` by itself only helps if your tenant is registered in the
|
||||
global cloud but you specifically need to reach one of these Graph API
|
||||
roots (per [Microsoft's national cloud deployment docs][ms-graph-clouds]):
|
||||
|
||||
| National cloud | Microsoft Graph URL | Entra ID endpoint (not configurable here) |
|
||||
|---|---|---|
|
||||
| Global (default) | `https://graph.microsoft.com` | `https://login.microsoftonline.com` |
|
||||
| US Government L4 (GCC High) | `https://graph.microsoft.us` | `https://login.microsoftonline.us` |
|
||||
| US Government L5 (DoD) | `https://dod-graph.microsoft.us` | `https://login.microsoftonline.us` |
|
||||
| China, operated by 21Vianet | `https://microsoftgraph.chinacloudapi.cn` | `https://login.chinacloudapi.cn` |
|
||||
|
||||
[ms-graph-clouds]: https://learn.microsoft.com/en-us/graph/deployments
|
||||
:::
|
||||
- `token_file` - str: Path to save the token file
|
||||
(Default: `.token`)
|
||||
- `allow_unencrypted_storage` - bool: Allows the Azure Identity
|
||||
module to fall back to unencrypted token cache (Default: `False`).
|
||||
Even if enabled, the cache will always try encrypted storage first.
|
||||
|
||||
:::{note}
|
||||
`token_file` stores the serialized authentication record; the
|
||||
underlying MSAL persistent token cache is separately named
|
||||
`parsedmarc`, not `mailsuite`'s own default cache name. This is
|
||||
deliberate: the Graph mailbox backend used to live directly in
|
||||
parsedmarc, and moved into the `mailsuite` dependency in parsedmarc
|
||||
9.11.0. Explicitly keeping the `parsedmarc` cache name means tokens
|
||||
cached before that move keep working after upgrading — there is
|
||||
nothing to migrate and no action needed on your part.
|
||||
:::
|
||||
|
||||
:::{note}
|
||||
You must create an app registration in Azure AD and have an
|
||||
admin grant the Microsoft Graph `Mail.ReadWrite`
|
||||
@@ -263,6 +295,11 @@ The full set of configuration options are:
|
||||
username, you must grant the app `Mail.ReadWrite.Shared`.
|
||||
:::
|
||||
|
||||
| Auth method | Reading (own mailbox) | Reading (shared mailbox) |
|
||||
|---|---|---|
|
||||
| `UsernamePassword` / `DeviceCode` (delegated) | `Mail.ReadWrite` | `Mail.ReadWrite.Shared` |
|
||||
| `ClientSecret` / `Certificate` / `ClientAssertion` (application) | `Mail.ReadWrite` (application), scoped via `New-ApplicationAccessPolicy` | same as own mailbox — app-only access is scoped by policy, not by permission name |
|
||||
|
||||
:::{tip}
|
||||
**Troubleshooting connections.** Run with `--verbose` to log a
|
||||
redacted connection summary (auth method, tenant, client ID,
|
||||
@@ -273,6 +310,11 @@ The full set of configuration options are:
|
||||
an Exchange Online-side one), Microsoft Graph SDK requests, and
|
||||
`httpx` HTTP request lines. Secret values (passwords, client
|
||||
secrets, certificate passwords) are never written to logs.
|
||||
Connection, mailbox fetch, message send, and `--watch` failures
|
||||
each log a single ERROR line naming the mailbox, tenant, auth
|
||||
method, and the Graph `request-id`/`client-request-id` when
|
||||
available — worth quoting verbatim when contacting Microsoft
|
||||
support.
|
||||
:::
|
||||
|
||||
:::{warning}
|
||||
@@ -295,6 +337,120 @@ The full set of configuration options are:
|
||||
applies to the `Certificate` and `ClientAssertion` auth methods.
|
||||
|
||||
:::
|
||||
|
||||
**Sending the summary email via Microsoft Graph.**
|
||||
When `[msgraph]` is configured and `[smtp]` has a `to` value but no
|
||||
`host`, the periodic summary email is sent through the same
|
||||
already-authenticated Graph mailbox connection used for reading
|
||||
(`/users/{mailbox}/sendMail`), and a copy is saved to Sent Items.
|
||||
When `[smtp] host` is set, SMTP is used regardless of whether
|
||||
`[msgraph]` is also configured — SMTP is always preferred, with no
|
||||
automatic fallback to Graph on SMTP failure.
|
||||
|
||||
Example config combining `[msgraph]` with a `[smtp]` section that
|
||||
only sets `to`/`subject` (no `host`):
|
||||
|
||||
```ini
|
||||
[msgraph]
|
||||
auth_method = Certificate
|
||||
client_id = ...
|
||||
tenant_id = ...
|
||||
mailbox = dmarc-reports@example.com
|
||||
certificate_path = /path/to/cert.pem
|
||||
|
||||
[smtp]
|
||||
to = admin@example.com
|
||||
subject = DMARC Summary
|
||||
```
|
||||
|
||||
Required Microsoft Graph permissions, in addition to the
|
||||
reading-related permissions documented above:
|
||||
|
||||
| Auth method | Reading | Sending (own mailbox) | Sending (shared mailbox) |
|
||||
|---|---|---|---|
|
||||
| `UsernamePassword` / `DeviceCode` (delegated) | `Mail.ReadWrite` (+`.Shared` for shared) | `Mail.Send` | `Mail.Send.Shared` |
|
||||
| `ClientSecret` / `Certificate` / `ClientAssertion` (application) | `Mail.ReadWrite` (application) | `Mail.Send` (application) | `Mail.Send` (application), scoped via `New-ApplicationAccessPolicy` |
|
||||
|
||||
:::{warning}
|
||||
Graph-based sending is only confirmed to work with the app-only
|
||||
auth methods (`ClientSecret`, `Certificate`, `ClientAssertion`).
|
||||
The delegated auth methods (`UsernamePassword`, `DeviceCode`)
|
||||
currently request only the `Mail.ReadWrite`(`.Shared`) scope when
|
||||
authenticating, not `Mail.Send` — so a delegated connection's
|
||||
access token will not carry `Mail.Send` even if an administrator
|
||||
has granted it, and `/sendMail` calls are expected to fail with an
|
||||
access-denied error regardless of what's granted in Azure AD. Use
|
||||
an app-only auth method if you need Graph-based sending.
|
||||
:::
|
||||
|
||||
**Minimal example configs.** Each auth method needs a different
|
||||
minimum set of keys. These read-only examples omit `[smtp]`; see
|
||||
above for adding Graph-based sending on top of any of them.
|
||||
|
||||
`UsernamePassword` (delegated, own mailbox):
|
||||
```ini
|
||||
[msgraph]
|
||||
auth_method = UsernamePassword
|
||||
client_id = ...
|
||||
client_secret = ...
|
||||
user = dmarc-reports@example.com
|
||||
password = ...
|
||||
```
|
||||
|
||||
`DeviceCode` (delegated, interactive sign-in on first run — `user`
|
||||
is the account that signs in; `mailbox` is the shared mailbox it
|
||||
reads, and only needs to differ from `user` to request
|
||||
`Mail.ReadWrite.Shared` instead of plain `Mail.ReadWrite`):
|
||||
```ini
|
||||
[msgraph]
|
||||
auth_method = DeviceCode
|
||||
client_id = ...
|
||||
tenant_id = ...
|
||||
user = signing-in-user@example.com
|
||||
mailbox = dmarc-reports@example.com
|
||||
```
|
||||
|
||||
`ClientSecret` (app-only):
|
||||
```ini
|
||||
[msgraph]
|
||||
auth_method = ClientSecret
|
||||
client_id = ...
|
||||
tenant_id = ...
|
||||
client_secret = ...
|
||||
mailbox = dmarc-reports@example.com
|
||||
```
|
||||
|
||||
`Certificate` (app-only):
|
||||
```ini
|
||||
[msgraph]
|
||||
auth_method = Certificate
|
||||
client_id = ...
|
||||
tenant_id = ...
|
||||
certificate_path = /path/to/cert.pem
|
||||
mailbox = dmarc-reports@example.com
|
||||
```
|
||||
|
||||
`ClientAssertion` (app-only, short-lived JWT — see the note above
|
||||
about its unsuitability for `watch` mode):
|
||||
```ini
|
||||
[msgraph]
|
||||
auth_method = ClientAssertion
|
||||
client_id = ...
|
||||
tenant_id = ...
|
||||
client_assertion = ...
|
||||
mailbox = dmarc-reports@example.com
|
||||
```
|
||||
|
||||
:::{tip}
|
||||
**Troubleshooting.**
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|---|---|---|
|
||||
| *"...needs permission to access resources in your organization that only an admin can grant"* / "Admin consent required" | A delegated auth method (`UsernamePassword`, `DeviceCode`) is authenticating with a scope (`Mail.ReadWrite` or `Mail.ReadWrite.Shared`) the tenant admin hasn't consented to yet. | Have an Entra ID admin grant consent: Azure Portal → **Enterprise Applications** → *your app* → **Permissions** → **Grant admin consent**, or `az ad app permission admin-consent --id <CLIENT_ID>`. This is separate from the `New-ApplicationAccessPolicy` step above, which only applies to app-only auth. |
|
||||
| `ErrorItemNotFound: ... Default folder Root not found` | `mailsuite` can resolve the well-known folders (`Inbox`, `Archive`, `Drafts`, `Sent Items`, `Deleted Items`, `Junk Email`) even when a mailbox's folder hierarchy hasn't fully provisioned, but a **custom, non-well-known** `reports_folder` name still fails to resolve on such a mailbox. | Point `reports_folder` at (or under) one of the six well-known folder names above, or sign into the (shared) mailbox once via Outlook/OWA to force Exchange to provision it, then retry. |
|
||||
| `RuntimeError: Event loop is closed` | Historical bug, fixed in `mailsuite` 2.0.2. Not reachable with the `mailsuite>=2.2.2` this project requires. | Confirm your installed `mailsuite` version is current (`pip show mailsuite`); upgrade if it's somehow pinned below 2.0.2. |
|
||||
| Invalid/rejected timestamp in the `since`/`receivedDateTime` filter | Historical bug (parsedmarc [#706](https://github.com/domainaware/parsedmarc/pull/706)/[#708](https://github.com/domainaware/parsedmarc/pull/708)): older versions appended a spurious `Z` to an already-UTC-offset ISO timestamp. Fixed since parsedmarc 9.5.1/9.5.5. | Upgrade parsedmarc if you're on a version older than 9.5.5. |
|
||||
:::
|
||||
- `elasticsearch`
|
||||
- `hosts` - str: A comma separated list of hostnames and ports
|
||||
or URLs (e.g. `127.0.0.1:9200` or
|
||||
@@ -371,14 +527,21 @@ The full set of configuration options are:
|
||||
- `aggregate_topic` - str: The Kafka topic for aggregate reports
|
||||
- `failure_topic` - str: The Kafka topic for failure reports
|
||||
- `smtp`
|
||||
- `host` - str: The SMTP hostname
|
||||
- `host` - str: The SMTP hostname. Required unless `[msgraph]` is
|
||||
configured, in which case omitting it sends the summary via
|
||||
Microsoft Graph instead — see "Sending the summary email via
|
||||
Microsoft Graph" above.
|
||||
- `port` - int: The SMTP port (Default: `25`)
|
||||
- `ssl` - bool: Require SSL/TLS instead of using STARTTLS
|
||||
- `skip_certificate_verification` - bool: Skip certificate
|
||||
verification (not recommended)
|
||||
- `user` - str: the SMTP username
|
||||
- `password` - str: the SMTP password
|
||||
- `from` - str: The From header to use in the email
|
||||
- `user` - str: the SMTP username. SMTP-only; not used when sending
|
||||
via Microsoft Graph.
|
||||
- `password` - str: the SMTP password. SMTP-only; not used when
|
||||
sending via Microsoft Graph.
|
||||
- `from` - str: The From header to use in the email. SMTP-only.
|
||||
When sent via Microsoft Graph, the message's `From` is always the
|
||||
`[msgraph]` mailbox — `[smtp] from` has no effect.
|
||||
- `to` - list: A list of email addresses to send to
|
||||
- `subject` - str: The Subject header to use in the email
|
||||
(Default: `parsedmarc report`)
|
||||
|
||||
Reference in New Issue
Block a user