Appearance
DMARC Enforcement Plan — ciriusgroup.com and ciriusdde.com
Story: MAESTER-010 Owner: Rory (architect) / Kobe (DNS automation) Status: p=reject already published on both apex zones (2026-04-16 verification). Remaining work: validate aggregate reports show zero legitimate failures, document rollback, formalize the staged rollout playbook for future domain additions.
TL;DR
Maester flags domains that do not publish DMARC p=reject. Public DNS lookups on 2026-04-16 show:
| Domain | DMARC | Policy | sp | Strict alignment |
|---|---|---|---|---|
| ciriusgroup.com | published | p=reject | sp=reject | (not set — implicit relaxed) |
| ciriusdde.com | published | p=reject | (not set → inherits policy) | adkim=s; aspf=s |
| ciriussupport.com | zone record exists but not resolvable publicly — see ciriussupport-email-security.md | — | — | — |
The Maester finding for MAESTER-010 is the authoritative view at audit time; we have since confirmed that the records are in place. This document captures:
- How to verify current state (for re-audit).
- The staged rollout playbook — used retroactively to document how we got here and prescriptively for any future domain we send mail from.
- Rollback procedure if ACS, Microsoft 365, Zendesk, or a new sender starts failing.
- Where DNS lives (it is not in
azure-infraTerraform — zones are hosted atgeneric.host).
1. Current State Assessment
1.1 Authoritative nameservers
Both apex zones are delegated to generic.host:
$ dig +short NS ciriusgroup.com @8.8.8.8
ns1.vps-ciriusgroup.generic.host.
ns2.vps-ciriusgroup.generic.host.
$ dig +short NS ciriusdde.com @8.8.8.8
ns1.vps-ciriusgroup.generic.host.
ns2.vps-ciriusgroup.generic.host.Zone data is edited in the generic.host VPS control panel. There are no azurerm_dns_zone resources for these apex zones in azure-infra. The only DNS resources in Terraform are azurerm_private_dns_zone entries (privatelink zones used for private endpoints — unrelated to public email).
Implication: DMARC / SPF / DKIM changes are manual DNS edits at generic.host. They do not go through the azure-infra PR → Checkov → apply pipeline. That is a known gap; migrating these zones to Azure DNS or Cloudflare would bring email DNS under change control, but is out of scope for MAESTER-010.
1.2 Published DMARC records
$ dig +short TXT _dmarc.ciriusgroup.com @8.8.8.8
"v=DMARC1;p=reject; sp=reject; rua=mailto:dmarc@ciriusgroup.com; ruf=mailto:dmarc@ciriusgroup.com"
$ dig +short TXT _dmarc.ciriusdde.com @8.8.8.8
"v=DMARC1; p=reject; rua=mailto:dmarc-reports@ciriusdde.com; ruf=mailto:dmarc-reports@ciriusdde.com; fo=1; adkim=s; aspf=s"Both records satisfy Maester's p=reject check. Two differences worth normalizing in a follow-up pass:
ciriusgroup.comdoes not setadkim/aspf. Default is relaxed — acceptable but less strict than DDE. If we have zero alignment failures in 30 days of aggregate reports, tighten toadkim=s; aspf=sto match DDE.ciriusdde.comsetsfo=1(report any failure). PROD does not.fo=1is optional and produces moreruftraffic; keep as-is.
1.3 Published SPF records
$ dig +short TXT ciriusgroup.com @8.8.8.8 | grep spf1
"v=spf1 include:spf.protection.outlook.com include:_spf.generic.host include:mail.zendesk.com include:spf.azurecomm.net -all"
$ dig +short TXT ciriusdde.com @8.8.8.8 | grep spf1
"v=spf1 include:spf.protection.outlook.com -all"Both end with -all (hardfail) which is the correct stance alongside p=reject.
1.4 Published DKIM selectors
$ dig +short CNAME selector1._domainkey.ciriusgroup.com @8.8.8.8
selector1-ciriusgroup-com._domainkey.CiriusGroupinc.onmicrosoft.com.
$ dig +short CNAME selector2._domainkey.ciriusgroup.com @8.8.8.8
selector2-ciriusgroup-com._domainkey.CiriusGroupinc.onmicrosoft.com.
$ dig +short CNAME selector1._domainkey.ciriusdde.com @8.8.8.8
selector1-ciriusdde-com._domainkey.CIRIUSDDE.r-v1.dkim.mail.microsoft.
$ dig +short CNAME selector2._domainkey.ciriusdde.com @8.8.8.8
selector2-ciriusdde-com._domainkey.CIRIUSDDE.r-v1.dkim.mail.microsoft.
$ dig +short CNAME selector1-azurecomm-prod-net._domainkey.ciriusgroup.com @8.8.8.8
selector1-azurecomm-prod-net._domainkey.azurecomm.net.Microsoft 365 DKIM (selector1/selector2) is in place on both tenants. ACS DKIM is in place on ciriusgroup.com. Zendesk signs with its own headers — alignment works via SPF only (include:mail.zendesk.com in the SPF record).
1.5 Known mail flows
| Sender | Domain | Auth path | Notes |
|---|---|---|---|
| Microsoft 365 (user mail) | ciriusgroup.com, ciriusdde.com | SPF include:spf.protection.outlook.com + DKIM (selector1/selector2) | Primary user mail. DKIM enabled in Exchange Online Protection. |
| Azure Communication Services — services@, servicesinternal@, security@ | ciriusgroup.com | SPF include:spf.azurecomm.net + ACS DKIM | Transactional relay (see azure-infra/logging/acs_email.tf). |
| Zendesk (support tickets) | ciriusgroup.com | SPF include:mail.zendesk.com; Zendesk-signed DKIM (their domain) | Alignment via SPF. |
| generic.host webmail forwarder | ciriusgroup.com | SPF include:_spf.generic.host | Legacy fallback from the zone host. Candidate for removal once confirmed unused. |
| Twingate notifications | — | SaaS-side — from twingate.com, not our domains | Does not need our SPF/DKIM. |
| Sentinel alert webhooks | — | Posts to Teams/Telegram, not email | N/A for DMARC. |
1.6 Maester authoritative check
To reproduce Maester's DMARC evaluation locally:
az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/domains" \
--query "value[].{id:id, auth:authenticationType}"
# Or run Maester directly:
pwsh -c "Install-Module Maester -Scope CurrentUser -Force; Connect-Maester; Invoke-Maester -Include Network"The MT.1036 test is the DMARC policy check; it passes only when p=reject is published on every accepted domain.
2. Staged Rollout Playbook (Prescriptive)
For new domains added to the tenant, or if a DMARC failure forces us to back a domain down to p=none and roll it forward again, follow this five-phase sequence.
Phase 0 — Prerequisites
Before publishing any DMARC record, confirm:
- [ ] Every legitimate mail flow for the domain is identified (see the mail-flow table pattern in §1.5).
- [ ] SPF record lists every legitimate sender with
-allat the end. - [ ] DKIM is enabled and signing for every legitimate sender. For M365, enable at
security.microsoft.com → Email & collaboration → Policies → DKIM. - [ ] An address for
rua=aggregate reports exists and forwards to a mailbox that is reviewed (or to a DMARC analyzer like dmarcian, URIports, Valimail). - [ ] An MX record for the
rua=recipient's domain accepts mail from-allsenders (otherwise report forwarders drop).
Phase 1 — p=none (monitoring, 14 days minimum)
Publish:
Name: _dmarc.<domain>
Type: TXT
Value: v=DMARC1; p=none; rua=mailto:dmarc@ciriusgroup.com; ruf=mailto:dmarc@ciriusgroup.com; fo=1
TTL: 3600Watch aggregate reports daily. Any dkim=fail or spf=fail from a known-good sender = SPF/DKIM fix needed before advancing.
Advance criterion: 14 consecutive days with zero legitimate-sender failures in aggregate reports.
Phase 2 — p=quarantine; pct=25 (7 days)
Quarantine 25% of failing mail. This is the canary.
v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc@ciriusgroup.com; ruf=mailto:dmarc@ciriusgroup.com; fo=1Advance criterion: zero user-reported mail loss tickets for 7 days.
Phase 3 — p=quarantine; pct=100 (30 days)
Quarantine all failing mail. Long bake window catches monthly/quarterly senders (invoice systems, annual notices, vendor relays that fire infrequently).
v=DMARC1; p=quarantine; rua=mailto:dmarc@ciriusgroup.com; ruf=mailto:dmarc@ciriusgroup.com; fo=1Advance criterion: 30 days with zero legitimate failures in aggregate reports. Document any new senders discovered and add them to SPF/DKIM before advancing.
Phase 4 — p=reject (target state)
v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; rua=mailto:dmarc@ciriusgroup.com; ruf=mailto:dmarc@ciriusgroup.com; fo=1Rationale for each tag:
p=reject— main policy.sp=reject— subdomain policy; required because attackers frequently spoofaccounts.<domain>,billing.<domain>, etc.adkim=s— strict DKIM alignment (d= must exactly match From).aspf=s— strict SPF alignment (MAIL FROM must exactly match From).fo=1— report any failure (SPF or DKIM), not just both failing.
Phase 5 — Steady state (monitoring)
- Review aggregate reports weekly for two months after reject.
- After two clean months, reduce review cadence to monthly.
- If a new sender is added, go back to Phase 0 checklist for that sender before it sends its first email.
3. Rollback Procedure
Trigger: legitimate mail is being rejected and cannot be fixed in <2 hours.
- Edit
_dmarc.<domain>TXT atgeneric.hostcontrol panel. - Change
p=reject→p=none(monitor only — fails open, no blocked mail). Do not change top=quarantineduring an incident; it still quarantines and users must retrieve from junk. - TTL is 3600 — plan for up to 1 hour of propagation.
- File a
CMticket in SecOps (POST /api/changes) so SecurityAgent suppresses DMARC-related alerts during the rollback window. - Root-cause: pull the failing message's headers, identify the sender IP, confirm whether SPF
include:or DKIM selector is missing. - Fix SPF/DKIM. Re-run Phase 1 → Phase 4 for that sender (not the whole domain — the domain is already known good except for the missing sender).
4. Terraform / azure-infra Changes
None required for MAESTER-010. The apex DNS zones for ciriusgroup.com and ciriusdde.com are not managed in Terraform — they live at generic.host and are edited manually in the VPS control panel. A comment to this effect is added in logging/acs_email.tf (existing file already contains the ACS DNS manual-step instructions).
If/when these zones migrate into Azure DNS or Cloudflare, add a _dmarc TXT resource of the form:
hcl
# Example for future migration to Azure DNS
resource "azurerm_dns_txt_record" "dmarc_ciriusgroup" {
name = "_dmarc"
zone_name = azurerm_dns_zone.ciriusgroup.name
resource_group_name = azurerm_resource_group.logging.name
ttl = 3600
record {
value = "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; rua=mailto:dmarc@ciriusgroup.com; ruf=mailto:dmarc@ciriusgroup.com; fo=1"
}
tags = local.required_tags
}Do not add this resource now — creating azurerm_dns_zone.ciriusgroup would hijack the delegation and silently black-hole mail. Migration is a dedicated project (new story in the DMARC epic backlog).
5. Follow-ups
- [ ] Normalize
ciriusgroup.comDMARC to includeadkim=s; aspf=safter 30 days of clean aggregate reports (currently implicit relaxed). - [ ] Confirm whether
_spf.generic.hostis still sending for ciriusgroup.com; if not, remove from SPF to shrink the include chain (10-lookup limit). - [ ] Stand up a DMARC aggregate report parser (dmarcian free tier, URIports, or a self-hosted
parsedmarccontainer job) sorua@ciriusgroup.comreports are visualized rather than reviewed as raw XML. - [ ] Evaluate migrating the two apex zones to Azure DNS so DMARC/SPF/DKIM become Terraform-managed and CI-reviewed.
References
- MAESTER-010 story (SecOps) — audit origin and acceptance criteria.
- RFC 7489 — DMARC specification.
- RFC 7208 — SPF specification (10-lookup limit,
-allvs~all). - Microsoft "Use DMARC to validate email" — https://learn.microsoft.com/en-us/defender-office-365/email-authentication-dmarc-configure
azure-infra/logging/acs_email.tf— ACS Email setup with DNS verification instructions.bedrock-docs/security/ciriussupport-email-security.md— sibling story, EMAILSEC-013.