Workloads/Get-DNSData.ps1
|
# Get-DNSData.ps1 # Checks SPF, DKIM, DMARC, and MX records for all tenant domains. # Logs findings for missing or weak records. # Part of the M365-QuickAssess module -- not exported. function Get-DNSData { param ( $Assessment ) Write-Log "Collecting DNS data" # ------------------------------------------------------------------- # Helper: Resolve-DNSRecord # Wraps Resolve-DnsName with error suppression # ------------------------------------------------------------------- function Resolve-DNSRecord { param ( [string]$Name, [string]$Type ) try { Resolve-DnsName -Name $Name -Type $Type -ErrorAction Stop } catch { $null } } # ------------------------------------------------------------------- # Get all tenant domains excluding mail.onmicrosoft.com routing domains # ------------------------------------------------------------------- $domains = @() try { $domains = Get-MgDomain -All -ErrorAction Stop | Where-Object { $_.Id -notlike "*.mail.onmicrosoft.com" } | Select-Object -ExpandProperty Id Write-Log "DNS check domains: $( $domains -join ', ' )" } catch { Write-Log "Failed to retrieve tenant domains: $( $_.Exception.Message )" "ERROR" return } if ( $domains.Count -eq 0 ) { Write-Log "No domains found to check" "WARN" return } # ------------------------------------------------------------------- # Per-domain DNS checks # ------------------------------------------------------------------- foreach ( $domain in $domains ) { Write-Log "Checking DNS records for $domain" # ------------------------------------------------------------------- # MX Record # ------------------------------------------------------------------- try { $mx = Resolve-DNSRecord -Name $domain -Type MX if ( $mx ) { $mxHosts = ( $mx | Where-Object { $_.Type -eq "MX" } | Sort-Object Preference | Select-Object -ExpandProperty NameExchange ) -join ", " $isM365MX = $mxHosts -match "mail\.protection\.outlook\.com" Write-Log "MX for $domain $mxHosts M365=$isM365MX" if ( -not $isM365MX ) { $Assessment.Findings += New-Finding ` -Type "ThirdPartyMX" ` -Summary "Domain $domain MX does not point to Exchange Online" ` -Category "DNS" ` -Severity "Medium" ` -Details @( "MX: $mxHosts" ) ` -Impact "Mail may be routed through a third-party filtering or relay service. This must be accounted for during migration." ` -Recommendation "Confirm mail routing strategy and update MX records as part of cutover planning." } } else { Write-Log "No MX record found for $domain" "WARN" $Assessment.Findings += New-Finding ` -Type "NoMXRecord" ` -Summary "No MX record found for domain $domain" ` -Category "DNS" ` -Severity "Medium" ` -Impact "Domain may not be configured to receive mail." ` -Recommendation "Verify MX record configuration for this domain." } } catch { Write-Log "MX check failed for $domain $( $_.Exception.Message )" "WARN" } # ------------------------------------------------------------------- # SPF Record # ------------------------------------------------------------------- try { $spfRaw = Resolve-DNSRecord -Name $domain -Type TXT | Where-Object { $_.Strings -match "v=spf1" } | Select-Object -ExpandProperty Strings -ErrorAction SilentlyContinue if ( -not $spfRaw ) { Write-Log "No SPF record found for $domain" "WARN" $Assessment.Findings += New-Finding ` -Type "NoSPFRecord" ` -Summary "No SPF record found for domain $domain" ` -Category "DNS" ` -Severity "High" ` -Impact "Without an SPF record this domain is vulnerable to email spoofing." ` -Recommendation "Add an SPF record for this domain before migration." } else { Write-Log "SPF for $domain $spfRaw" if ( $spfRaw -match "~all" -or $spfRaw -match "\?all" -or $spfRaw -match "\+all" ) { $Assessment.Findings += New-Finding ` -Type "WeakSPFRecord" ` -Summary "SPF record for domain $domain is not sufficiently strict" ` -Category "DNS" ` -Severity "Medium" ` -Details @( "SPF: $spfRaw" ) ` -Impact "A permissive SPF policy does not fully prevent email spoofing." ` -Recommendation "Update SPF record to use -all for strict enforcement." } } } catch { Write-Log "SPF check failed for $domain $( $_.Exception.Message )" "WARN" } # ------------------------------------------------------------------- # DKIM Record # Checks selector1 and selector2 (Microsoft default selectors) # ------------------------------------------------------------------- try { $dkimFound = $false foreach ( $selector in @( "selector1", "selector2" ) ) { $dkimCname = Resolve-DNSRecord -Name "$selector._domainkey.$domain" -Type CNAME if ( $dkimCname ) { $dkimTarget = $dkimCname | Where-Object { $_.Type -eq "CNAME" } | Select-Object -ExpandProperty NameHost -ErrorAction SilentlyContinue if ( $dkimTarget ) { $dkimTxt = Resolve-DNSRecord -Name $dkimTarget -Type TXT if ( $dkimTxt ) { $dkimRecord = $dkimTxt | Select-Object -ExpandProperty Strings -ErrorAction SilentlyContinue if ( $dkimRecord -match "v=DKIM1" -or $dkimRecord -match "k=" ) { Write-Log "DKIM found for $domain selector $selector" $dkimFound = $true break } } } } } if ( -not $dkimFound ) { Write-Log "No DKIM record found for $domain" "WARN" $Assessment.Findings += New-Finding ` -Type "NoDKIMRecord" ` -Summary "No DKIM record found for domain $domain" ` -Category "DNS" ` -Severity "High" ` -Impact "Without DKIM this domain is vulnerable to email spoofing and messages may be flagged as spam." ` -Recommendation "Enable DKIM signing in Exchange Online for this domain before migration." } } catch { Write-Log "DKIM check failed for $domain $( $_.Exception.Message )" "WARN" } # ------------------------------------------------------------------- # DMARC Record # ------------------------------------------------------------------- try { $dmarcRaw = Resolve-DNSRecord -Name "_dmarc.$domain" -Type TXT | Select-Object -ExpandProperty Strings -ErrorAction SilentlyContinue if ( -not $dmarcRaw ) { Write-Log "No DMARC record found for $domain" "WARN" $Assessment.Findings += New-Finding ` -Type "NoDMARCRecord" ` -Summary "No DMARC record found for domain $domain" ` -Category "DNS" ` -Severity "High" ` -Impact "Without DMARC this domain is at risk of being abused by phishers and spammers." ` -Recommendation "Add a DMARC record for this domain. Start with p=none and progress to p=reject." } else { Write-Log "DMARC for $domain $dmarcRaw" if ( $dmarcRaw -match "p=none" ) { $Assessment.Findings += New-Finding ` -Type "DMARCPolicyNone" ` -Summary "DMARC policy for domain $domain is set to p=none" ` -Category "DNS" ` -Severity "Medium" ` -Details @( "DMARC: $dmarcRaw" ) ` -Impact "A DMARC policy of p=none does not prevent abuse of this domain." ` -Recommendation "Progress DMARC policy to p=quarantine then p=reject after validating SPF and DKIM alignment." } elseif ( $dmarcRaw -match "p=quarantine" ) { $Assessment.Findings += New-Finding ` -Type "DMARCPolicyQuarantine" ` -Summary "DMARC policy for domain $domain is set to p=quarantine" ` -Category "DNS" ` -Severity "Low" ` -Details @( "DMARC: $dmarcRaw" ) ` -Impact "DMARC is partially enforced. Unauthenticated messages are quarantined but not rejected." ` -Recommendation "Progress DMARC policy to p=reject for full enforcement after validating SPF and DKIM alignment." } else { Write-Log "DMARC policy for $domain is p=reject -- fully enforced" } } } catch { Write-Log "DMARC check failed for $domain $( $_.Exception.Message )" "WARN" } } Write-Log "DNS data collection complete" } |