diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index 408f1648..58ffa724 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -119,6 +119,7 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Clear-MtGraphCache', 'Connect-Mae 'Test-MtCisaSpfRestriction', 'Test-MtCisaSpfDirective', 'Test-MtCisaDkim', 'Test-MtCisaDmarcRecordExist', 'Test-MtCisaDmarcRecordReject', 'Test-MtCisaDmarcAggregateCisa', 'Test-MtCisaDmarcReport', + 'Test-MtCisaDlp', 'Test-MtConditionalAccessWhatIf', 'Test-MtConnection', 'Test-MtEidscaAF01', diff --git a/powershell/internal/Get-MtSkippedReason.ps1 b/powershell/internal/Get-MtSkippedReason.ps1 index e278259b..3572a0eb 100644 --- a/powershell/internal/Get-MtSkippedReason.ps1 +++ b/powershell/internal/Get-MtSkippedReason.ps1 @@ -11,12 +11,14 @@ function Get-MtSkippedReason { switch($SkippedBecause){ "NotConnectedAzure" { "Not connected to Azure. See [Connecting to Azure](https://maester.dev/docs/installation#optional-modules-and-permissions)" ; break} "NotConnectedExchange" { "Not connected to Exchange Online. See [Connecting to Exchange Online](https://maester.dev/docs/installation#optional-modules-and-permissions)"; break} + "NotConnectedSecurityCompliance" { "Not connected to Security & Compliance. See [Connecting to Security & Compliance](https://maester.dev/docs/installation#optional-modules-and-permissions)"; break} "NotDotGovDomain" { "This test is only for federal, executive branch, departments and agencies. To override use [Test-MtCisaDmarcAggregateCisa -Force](https://maester.dev/docs/commands/Test-MtCisaDmarcAggregateCisa)"; break} "NotLicensedEntraIDP1" { "This test is for tenants that are licensed for Entra ID P1. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break} "NotLicensedEntraIDP2" { "This test is for tenants that are licensed for Entra ID P2. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break} "NotLicensedEntraIDGovernance" { "This test is for tenants that are licensed for Entra ID Governance. See [Entra ID Governance licensing](https://learn.microsoft.com/entra/fundamentals/licensing#microsoft-entra-id-governance)"; break} "NotLicensedEntraWorkloadID" { "This test is for tenants that are licensed for Entra Workload ID. See [Entra Workload ID licensing](https://learn.microsoft.com/entra/workload-id/workload-identities-faqs)"; break} "LicensedEntraIDPremium" { "This test is for tenants that are not licensed for any Entra ID Premium license. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break} + "NotSupported" { "This test relies on capabilities not currently available (e.g., cmdlets that are not available on all platforms, Resolve-DnsName)"; break} default { $SkippedBecause; break} } } diff --git a/powershell/public/Add-MtTestResultDetail.ps1 b/powershell/public/Add-MtTestResultDetail.ps1 index 1d43405b..f22125e7 100644 --- a/powershell/public/Add-MtTestResultDetail.ps1 +++ b/powershell/public/Add-MtTestResultDetail.ps1 @@ -62,8 +62,8 @@ Function Add-MtTestResultDetail { [Parameter(Mandatory = $false)] [string] $TestName = $____Pester.CurrentTest.ExpandedName, - [ValidateSet('NotConnectedAzure', 'NotConnectedExchange', 'NotDotGovDomain', 'NotLicensedEntraIDP1', - 'NotLicensedEntraIDP2', 'NotLicensedEntraIDGovernance', 'NotLicensedEntraWorkloadID', "LicensedEntraIDPremium" + [ValidateSet('NotConnectedAzure', 'NotConnectedExchange', 'NotDotGovDomain', 'NotLicensedEntraIDP1', 'NotConnectedSecurityCompliance', + 'NotLicensedEntraIDP2', 'NotLicensedEntraIDGovernance', 'NotLicensedEntraWorkloadID', "LicensedEntraIDPremium", 'NotSupported' )] [string] $SkippedBecause ) diff --git a/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordDkim.ps1 b/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordDkim.ps1 index ca754e91..fe684430 100644 --- a/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordDkim.ps1 +++ b/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordDkim.ps1 @@ -94,10 +94,10 @@ Function ConvertFrom-MailAuthenticationRecordDkim { Where-Object {$_.Type -eq "TXT"} | ` Where-Object {$_.Strings -match $matchRecord}).Strings) }catch [System.Management.Automation.CommandNotFoundException]{ - Write-Error $Error[0] + Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" }catch{ - Write-Error $Error[0] + Write-Error $_ return "Failure to obtain record" } diff --git a/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordDmarc.ps1 b/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordDmarc.ps1 index 4970ade5..4e40f1b7 100644 --- a/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordDmarc.ps1 +++ b/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordDmarc.ps1 @@ -225,10 +225,10 @@ Function ConvertFrom-MailAuthenticationRecordDmarc { Where-Object {$_.Type -eq "TXT"} | ` Where-Object {$_.Strings -match $matchRecord}).Strings) }catch [System.Management.Automation.CommandNotFoundException]{ - Write-Error $Error[0] + Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" }catch{ - Write-Error $Error[0] + Write-Error $_ return "Failure to obtain record" } diff --git a/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordMx.ps1 b/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordMx.ps1 index 022f99da..d1488860 100644 --- a/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordMx.ps1 +++ b/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordMx.ps1 @@ -40,10 +40,10 @@ Function ConvertFrom-MailAuthenticationRecordMx { try{ $mxRecords = Resolve-DnsName @mxSplat | Where-Object {$_.Type -eq "MX"} }catch [System.Management.Automation.CommandNotFoundException]{ - Write-Error $Error[0] + Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" }catch{ - Write-Error $Error[0] + Write-Error $_ return "Failure to obtain record" } diff --git a/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordSpf.ps1 b/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordSpf.ps1 index a6084a25..bff3b5f8 100644 --- a/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordSpf.ps1 +++ b/powershell/public/CISA/exchange/ConvertFrom-MailAuthenticationRecordSpf.ps1 @@ -127,10 +127,10 @@ Function ConvertFrom-MailAuthenticationRecordSpf { Where-Object {$_.Type -eq "TXT"} | ` Where-Object {$_.Strings -imatch $matchRecord}).Strings) }catch [System.Management.Automation.CommandNotFoundException]{ - Write-Error $Error[0] + Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" }catch{ - Write-Error $Error[0] + Write-Error $_ return "Failure to obtain record" } diff --git a/powershell/public/CISA/exchange/Get-MailAuthenticationRecord.ps1 b/powershell/public/CISA/exchange/Get-MailAuthenticationRecord.ps1 index a00017fb..63e18227 100644 --- a/powershell/public/CISA/exchange/Get-MailAuthenticationRecord.ps1 +++ b/powershell/public/CISA/exchange/Get-MailAuthenticationRecord.ps1 @@ -89,14 +89,15 @@ Function Get-MailAuthenticationRecord { $recordSet.spfLookups = $mtDnsCache.spfLookups }else{ $recordSet.spfRecord = ConvertFrom-MailAuthenticationRecordSpf @splat - if($recordSet.spfRecord.terms.modifier -contains "redirect"){ - Write-Verbose "SPF redirect modifier found, recursing" - $redirect = ($recordSet.spfRecord.terms|Where-Object {` - $_.modifier -eq "redirect" - }).modifierTarget - Get-MailAuthenticationRecord -DomainName $redirect - } - if($recordSet.spfRecord -ne "Failure to obtain record"){ + if($recordSet.spfRecord.GetType() -ne "SPFRecord"){ + if($recordSet.spfRecord.terms.modifier -contains "redirect"){ + Write-Verbose "SPF redirect modifier found, recursing" + $redirect = ($recordSet.spfRecord.terms|Where-Object {` + $_.modifier -eq "redirect" + }).modifierTarget + Get-MailAuthenticationRecord -DomainName $redirect + } + Write-Verbose "SPF record resolved, checking lookups" if($null -ne $mtDnsCache.spfLookups){ Write-Verbose "SPF Lookups records exist in cache - Use Clear-MtDnsCache to reset" diff --git a/powershell/public/CISA/exchange/Resolve-SPFRecord.ps1 b/powershell/public/CISA/exchange/Resolve-SPFRecord.ps1 index 0d865c97..3d71895b 100644 --- a/powershell/public/CISA/exchange/Resolve-SPFRecord.ps1 +++ b/powershell/public/CISA/exchange/Resolve-SPFRecord.ps1 @@ -69,10 +69,10 @@ function Resolve-SPFRecord { try{ $DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type TXT }catch [System.Management.Automation.CommandNotFoundException]{ - Write-Error $Error[0] + Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" }catch{ - Write-Error $Error[0] + Write-Error $_ return "Failure to obtain record" } # Check SPF record diff --git a/powershell/public/CISA/exchange/Test-MtCisaDkim.ps1 b/powershell/public/CISA/exchange/Test-MtCisaDkim.ps1 index 28d38c65..f3fdd866 100644 --- a/powershell/public/CISA/exchange/Test-MtCisaDkim.ps1 +++ b/powershell/public/CISA/exchange/Test-MtCisaDkim.ps1 @@ -53,15 +53,20 @@ Function Test-MtCisaDkim { $dkimRecord | Add-Member -MemberType NoteProperty -Name "pass" -Value "Failed" $dkimRecord | Add-Member -MemberType NoteProperty -Name "reason" -Value "" - if($dkimRecord.dkimRecord.GetType().Name -eq "DKIMRecord" -and $config.enabled){ - if(-not $dkimRecord.dkimRecord.validBase64){ - $dkimRecord.reason = "Malformed public key" + if($dkimRecord.dkimRecord.GetType().Name -eq "DKIMRecord"){ + if($config.enabled){ + if(-not $dkimRecord.dkimRecord.validBase64){ + $dkimRecord.reason = "Malformed public key" + }else{ + $dkimRecord.pass = "Passed" + } }else{ - $dkimRecord.pass = "Passed" + $dkimRecord.pass = "Skipped" + $dkimRecord.reason = "Parked domain" } - }elseif($dkimRecord.dkimRecord.GetType().Name -eq "DKIMRecord" -and -not $config.enabled){ + }elseif($dkimRecord.dkimRecord -eq "Unsupported platform, Resolve-DnsName not available"){ $dkimRecord.pass = "Skipped" - $dkimRecord.reason = "Parked domain" + $dkimRecord.reason = $dkimRecord.dkimRecord }else{ $dkimRecord.reason = $dkimRecord.dkimRecord } @@ -71,6 +76,9 @@ Function Test-MtCisaDkim { if("Failed" -in $dkimRecords.pass){ $testResult = $false + }elseif("Failed" -notin $dkimRecords.pass -and "Passed" -notin $dkimRecords.pass){ + Add-MtTestResultDetail -SkippedBecause NotSupported + return $null }else{ $testResult = $true } diff --git a/powershell/public/CISA/exchange/Test-MtCisaDlp.md b/powershell/public/CISA/exchange/Test-MtCisaDlp.md new file mode 100644 index 00000000..59b2c51a --- /dev/null +++ b/powershell/public/CISA/exchange/Test-MtCisaDlp.md @@ -0,0 +1,44 @@ +A DLP solution SHALL be used. + +Rationale: Users may inadvertently disclose sensitive information to unauthorized individuals. A DLP solution may detect the presence of sensitive information in Exchange Online and block access to unauthorized entities. + +#### Remediation action: + +1. Sign in to the **Microsoft Purview compliance portal**. +2. Under the **Solutions** section, select **Data loss prevention**. +3. Select [**Policies**](https://purview.microsoft.com/datalossprevention/policies) from the left menu. +4. Select **Create policy**. +5. From the **Categories** list, select **Custom**. +6. From the **Templates** list, select **Custom policy** and then click **Next**. +7. Edit the name and description of the policy if desired, then click **Next**. +8. Under **Choose locations to apply the policy**, set **Status** to **On** for at least the Exchange email, OneDrive accounts, SharePoint sites, Teams chat and channel messages, and Devices locations, then click **Next**. +9. Under **Define policy settings**, select **Create or customize advanced DLP rules**, and then click **Next**. +10. Click **Create rule**. Assign the rule an appropriate name and description. +11. Click **Add condition**, then **Content contains**. +12. Click **Add**, then **Sensitive info types**. +13. Add information types that protect information sensitive to the agency. + + At a minimum, the agency should protect: + - Credit card numbers + - U.S. Individual Taxpayer Identification Numbers (ITIN) + - U.S. Social Security Numbers (SSN) + - All agency-defined PII and sensitive information + +14. Click **Add**. +15. Under **Actions**, click **Add an action**. +16. Check **Restrict Access or encrypt the content in Microsoft 365 locations**. +17. Under this action, select **Block Everyone**. +18. Under **User notifications**, turn on **Use notifications to inform your users and help educate them on the proper use of sensitive info**. +19. Under **Microsoft 365 services**, a section that appears after user notifications are turned on, check the box next to **Notify users in Office 365 service with a policy tip**. +20. Click **Save**, then **Next**. +21. Select **Turn it on right away**, then click **Next**. +22. Click **Submit**. + +#### Related links + +* [Purview admin center - Data loss prevention policies](https://purview.microsoft.com/datalossprevention/policies) +* [CISA 8 Data Loss Prevention Solutions - MS.EXO.8.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo81v2) +* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/EXOConfig.rego#L439) + + +%TestResult% \ No newline at end of file diff --git a/powershell/public/CISA/exchange/Test-MtCisaDlp.ps1 b/powershell/public/CISA/exchange/Test-MtCisaDlp.ps1 new file mode 100644 index 00000000..2d6c1e5f --- /dev/null +++ b/powershell/public/CISA/exchange/Test-MtCisaDlp.ps1 @@ -0,0 +1,64 @@ +<# +.SYNOPSIS + Checks state of DLP for EXO + +.DESCRIPTION + + A DLP solution SHALL be used. + +.EXAMPLE + Test-MtCisaDlp + + Returns true if +#> + +Function Test-MtCisaDlp { + [CmdletBinding()] + [OutputType([bool])] + param() + + if(!(Test-MtConnection ExchangeOnline)){ + Add-MtTestResultDetail -SkippedBecause NotConnectedExchange + return $null + }elseif(!(Test-MtConnection SecurityCompliance)){ + Add-MtTestResultDetail -SkippedBecause NotConnectedSecurityCompliance + return $null + } + + $policies = Get-DlpCompliancePolicy + + $resultPolicies = $policies | Where-Object {` + $_.ExchangeLocation.DisplayName -contains "All" -and ` + $_.Workload -like "*Exchange*" -and ` + -not $_.IsSimulationPolicy -and ` + $_.Enabled + } + + $testResult = ($resultPolicies | Measure-Object).Count -ge 1 + + $portalLink = "https://purview.microsoft.com/datalossprevention/policies" + + if ($testResult) { + $testResultMarkdown = "Well done. Your tenant has [Purview Data Loss Prevention Policies]($portalLink) enabled.`n`n%TestResult%" + } else { + $testResultMarkdown = "Your tenant does not have [Purview Data Loss Prevention Policies]($portalLink) enabled.`n`n%TestResult%" + } + + $passResult = "✅ Pass" + $failResult = "❌ Fail" + $result = "| Name | Status | Description |`n" + $result += "| --- | --- | --- |`n" + foreach ($item in ($policies | Where-Object {$_.ExchangeLocation.DisplayName -contains "All"}) | Sort-Object -Property name) { + $itemResult = $failResult + if($item.Guid -in $resultPolicies.Guid){ + $itemResult = $passResult + } + $result += "| $($item.name) | $($itemResult) | $($item.comment) |`n" + } + + $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result + + Add-MtTestResultDetail -Result $testResultMarkdown + + return $testResult +} \ No newline at end of file diff --git a/powershell/public/CISA/exchange/Test-MtCisaDmarcAggregateCisa.ps1 b/powershell/public/CISA/exchange/Test-MtCisaDmarcAggregateCisa.ps1 index 7d365963..0dd6f9d3 100644 --- a/powershell/public/CISA/exchange/Test-MtCisaDmarcAggregateCisa.ps1 +++ b/powershell/public/CISA/exchange/Test-MtCisaDmarcAggregateCisa.ps1 @@ -69,6 +69,9 @@ Function Test-MtCisaDmarcAggregateCisa { $dmarcRecord.pass = "Passed" }elseif($checkType -and -not $checkTarget){ $dmarcRecord.reason = "Missing CISA report target" + }elseif($dmarcRecord.dmarcRecord -eq "Unsupported platform, Resolve-DnsName not available"){ + $dmarcRecord.pass = "Skipped" + $dmarcRecord.reason = $dmarcRecord.dmarcRecord }else{ $dmarcRecord.reason = $dmarcRecord.dmarcRecord } @@ -78,6 +81,9 @@ Function Test-MtCisaDmarcAggregateCisa { if("Failed" -in $dmarcRecords.pass){ $testResult = $false + }elseif("Failed" -notin $dmarcRecords.pass -and "Passed" -notin $dmarcRecords.pass){ + Add-MtTestResultDetail -SkippedBecause NotSupported + return $null }else{ $testResult = $true } diff --git a/powershell/public/CISA/exchange/Test-MtCisaDmarcRecordExist.ps1 b/powershell/public/CISA/exchange/Test-MtCisaDmarcRecordExist.ps1 index 481299ed..347d50aa 100644 --- a/powershell/public/CISA/exchange/Test-MtCisaDmarcRecordExist.ps1 +++ b/powershell/public/CISA/exchange/Test-MtCisaDmarcRecordExist.ps1 @@ -47,6 +47,9 @@ Function Test-MtCisaDmarcRecordExist { if($dmarcRecord.dmarcRecord.GetType().Name -eq "DMARCRecord"){ $dmarcRecord.pass = "Passed" + }elseif($dmarcRecord.dmarcRecord -eq "Unsupported platform, Resolve-DnsName not available"){ + $dmarcRecord.pass = "Skipped" + $dmarcRecord.reason = $dmarcRecord.dmarcRecord }else{ $dmarcRecord.reason = $dmarcRecord.dmarcRecord } @@ -56,6 +59,9 @@ Function Test-MtCisaDmarcRecordExist { if("Failed" -in $dmarcRecords.pass){ $testResult = $false + }elseif("Failed" -notin $dmarcRecords.pass -and "Passed" -notin $dmarcRecords.pass){ + Add-MtTestResultDetail -SkippedBecause NotSupported + return $null }else{ $testResult = $true } diff --git a/powershell/public/CISA/exchange/Test-MtCisaDmarcRecordReject.ps1 b/powershell/public/CISA/exchange/Test-MtCisaDmarcRecordReject.ps1 index 22411acf..88119c53 100644 --- a/powershell/public/CISA/exchange/Test-MtCisaDmarcRecordReject.ps1 +++ b/powershell/public/CISA/exchange/Test-MtCisaDmarcRecordReject.ps1 @@ -58,6 +58,9 @@ Function Test-MtCisaDmarcRecordReject { $dmarcRecord.reason = "Policy is not reject" }elseif($checkType -and $dmarcRecord.dmarcRecord.policySubdomain -in @("none","quarantine")){ $dmarcRecord.reason = "Subdomain policy is not reject" + }elseif($dmarcRecord.dmarcRecord -eq "Unsupported platform, Resolve-DnsName not available"){ + $dmarcRecord.pass = "Skipped" + $dmarcRecord.reason = $dmarcRecord.dmarcRecord }else{ $dmarcRecord.reason = $dmarcRecord.dmarcRecord } @@ -67,6 +70,9 @@ Function Test-MtCisaDmarcRecordReject { if("Failed" -in $dmarcRecords.pass){ $testResult = $false + }elseif("Failed" -notin $dmarcRecords.pass -and "Passed" -notin $dmarcRecords.pass){ + Add-MtTestResultDetail -SkippedBecause NotSupported + return $null }else{ $testResult = $true } diff --git a/powershell/public/CISA/exchange/Test-MtCisaDmarcReport.ps1 b/powershell/public/CISA/exchange/Test-MtCisaDmarcReport.ps1 index 342c1177..f13c65b6 100644 --- a/powershell/public/CISA/exchange/Test-MtCisaDmarcReport.ps1 +++ b/powershell/public/CISA/exchange/Test-MtCisaDmarcReport.ps1 @@ -58,6 +58,9 @@ Function Test-MtCisaDmarcReport { $dmarcRecord.pass = "Passed" }elseif($checkType){ $dmarcRecord.reason = "No target in domain" + }elseif($dmarcRecord.dmarcRecord -eq "Unsupported platform, Resolve-DnsName not available"){ + $dmarcRecord.pass = "Skipped" + $dmarcRecord.reason = $dmarcRecord.dmarcRecord }else{ $dmarcRecord.reason = $dmarcRecord.dmarcRecord } @@ -67,6 +70,9 @@ Function Test-MtCisaDmarcReport { if("Failed" -in $dmarcRecords.pass){ $testResult = $false + }elseif("Failed" -notin $dmarcRecords.pass -and "Passed" -notin $dmarcRecords.pass){ + Add-MtTestResultDetail -SkippedBecause NotSupported + return $null }else{ $testResult = $true } diff --git a/powershell/public/CISA/exchange/Test-MtCisaSpfDirective.ps1 b/powershell/public/CISA/exchange/Test-MtCisaSpfDirective.ps1 index 53e2c214..05010f07 100644 --- a/powershell/public/CISA/exchange/Test-MtCisaSpfDirective.ps1 +++ b/powershell/public/CISA/exchange/Test-MtCisaSpfDirective.ps1 @@ -44,9 +44,14 @@ Function Test-MtCisaSpfDirective { $spfRecord.reason = "1+ mechanism targets" }elseif(($directives|Measure-Object).Count -ge 1 -and -not $check){ $spfRecord.reason = "No EXO directive" - }elseif($spfRecord.spfRecord.terms[-1].modifier -eq "redirect"){ + }elseif($spfRecord.spfRecord -eq "Unsupported platform, Resolve-DnsName not available"){ $spfRecord.pass = "Skipped" - $spfRecord.reason = "Redirect modifier" + $spfRecord.reason = $spfRecord.spfRecord + }elseif($spfRecord.spfRecord.GetType().Name -eq "SPFRecord"){ + if($spfRecord.spfRecord.terms[-1].modifier -eq "redirect"){ + $spfRecord.pass = "Skipped" + $spfRecord.reason = "Redirect modifier" + } }else{ $spfRecord.reason = "No mechanism targets" } @@ -62,6 +67,9 @@ Function Test-MtCisaSpfDirective { if("Failed" -in $spfRecords.pass){ $testResult = $false + }elseif("Failed" -notin $spfRecords.pass -and "Passed" -notin $spfRecords.pass){ + Add-MtTestResultDetail -SkippedBecause NotSupported + return $null }else{ $testResult = $true } diff --git a/powershell/public/CISA/exchange/Test-MtCisaSpfRestriction.ps1 b/powershell/public/CISA/exchange/Test-MtCisaSpfRestriction.ps1 index 73492761..ac492130 100644 --- a/powershell/public/CISA/exchange/Test-MtCisaSpfRestriction.ps1 +++ b/powershell/public/CISA/exchange/Test-MtCisaSpfRestriction.ps1 @@ -12,7 +12,7 @@ Returns true if SPF record exists and has a fail all modifier for all exo domains #> -Function Test-MtCisaSpfRestriction2 { +Function Test-MtCisaSpfRestriction { [CmdletBinding()] [OutputType([bool])] param() @@ -35,12 +35,17 @@ Function Test-MtCisaSpfRestriction2 { $spfRecord | Add-Member -MemberType NoteProperty -Name "pass" -Value "Failed" $spfRecord | Add-Member -MemberType NoteProperty -Name "reason" -Value "" - if($spfRecord.spfRecord.terms[-1].directive -eq "-all"){ - $spfRecord.pass = "Passed" - $spfRecord.reason = "Last directive is '-all'" - }elseif($spfRecord.spfRecord.terms[-1].modifier -eq "redirect"){ + if($spfRecord.spfRecord.GetType().Name -eq "SPFRecord"){ + if($spfRecord.spfRecord.terms[-1].directive -eq "-all"){ + $spfRecord.pass = "Passed" + $spfRecord.reason = "Last directive is '-all'" + }elseif($spfRecord.spfRecord.terms[-1].modifier -eq "redirect"){ + $spfRecord.pass = "Skipped" + $spfRecord.reason = "Redirect modifier" + } + }elseif($spfRecord.spfRecord -eq "Unsupported platform, Resolve-DnsName not available"){ $spfRecord.pass = "Skipped" - $spfRecord.reason = "Redirect modifier" + $spfRecord.reason = $spfRecord.spfRecord }else{ $spfRecord.reason = "Last directive is not '-all'" } diff --git a/powershell/public/Connect-Maester.ps1 b/powershell/public/Connect-Maester.ps1 index 0788edbd..9ad6ac7f 100644 --- a/powershell/public/Connect-Maester.ps1 +++ b/powershell/public/Connect-Maester.ps1 @@ -80,7 +80,7 @@ Function Connect-Maester { [string]$ExchangeEnvironmentName = "O365Default", # The services to connect to such as Azure and EXO. Default is Graph. - [ValidateSet("All", "Azure", "ExchangeOnline", "Graph")] + [ValidateSet("All", "Azure", "ExchangeOnline", "Graph", "SecurityCompliance")] [string[]]$Service = "Graph" ) @@ -115,4 +115,44 @@ Function Connect-Maester { Write-Host "`nInstall-Module ExchangeOnlineManagement -Scope CurrentUser`n" -ForegroundColor Yellow } } + + if ($Service -contains "SecurityCompliance" -or $Service -contains "All") { + $environments = @{ + O365China = @{ + ConnectionUri = "https://ps.compliance.protection.partner.outlook.cn/powershell-liveid" + AuthZEndpointUri = "https://login.chinacloudapi.cn/common" + } + O365GermanyCloud = @{ + ConnectionUri = "https://ps.compliance.protection.outlook.com/powershell-liveid/" + AuthZEndpointUri = "https://login.microsoftonline.com/common" + } + O365Default = @{ + ConnectionUri = "https://ps.compliance.protection.outlook.com/powershell-liveid/" + AuthZEndpointUri = "https://login.microsoftonline.com/common" + } + O365USGovGCCHigh = @{ + ConnectionUri = "https://ps.compliance.protection.office365.us/powershell-liveid/" + AuthZEndpointUri = "https://login.microsoftonline.us/common" + } + O365USGovDoD = @{ + ConnectionUri = "https://l5.ps.compliance.protection.office365.us/powershell-liveid/" + AuthZEndpointUri = "https://login.microsoftonline.us/common" + } + } + Write-Verbose "Connecting to Microsoft Security & Complaince PowerShell" + if ($Service -notcontains "ExchangeOnline"){ + Write-Host "`nThe Security & Complaince module is dependent on the Exchange Online module. Please include ExchangeOnline when specifying the services.`nFor more information see https://learn.microsoft.com/en-us/powershell/exchange/connect-to-scc-powershell" -ForegroundColor Red + }else{ + if ($UseDeviceCode){ + Write-Host "`nThe Security & Compliance module does not support device code flow authentication." -ForegroundColor Red + }else{ + try { + Connect-IPPSSession -BypassMailboxAnchoring -ConnectionUri $environments[$ExchangeEnvironmentName].ConnectionUri -AzureADAuthorizationEndpointUri $environments[$ExchangeEnvironmentName].AuthZEndpointUri + } catch [Management.Automation.CommandNotFoundException] { + Write-Host "`nThe Exchange Online module is not installed. Please install the module using the following command.`nFor more information see https://learn.microsoft.com/powershell/exchange/exchange-online-powershell-v2" -ForegroundColor Red + Write-Host "`nInstall-Module ExchangeOnlineManagement -Scope CurrentUser`n" -ForegroundColor Yellow + } + } + } + } } \ No newline at end of file diff --git a/powershell/public/Disconnect-Maester.ps1 b/powershell/public/Disconnect-Maester.ps1 index a99745c8..9d76d9f3 100644 --- a/powershell/public/Disconnect-Maester.ps1 +++ b/powershell/public/Disconnect-Maester.ps1 @@ -36,7 +36,7 @@ Function Disconnect-Maester { Disconnect-AzAccount } - if($__MtSession.Connections -contains "ExchangeOnline" -or $__MtSession.Connections -contains "All"){ + if($__MtSession.Connections -contains "ExchangeOnline" -or $__MtSession.Connections -contains "SecurityCompliance" -or $__MtSession.Connections -contains "All"){ Write-Verbose -Message "Disconnecting from Microsoft Exchange Online." Disconnect-ExchangeOnline } diff --git a/powershell/public/core/Test-MtConnection.ps1 b/powershell/public/core/Test-MtConnection.ps1 index 1bf1446d..0aafa82f 100644 --- a/powershell/public/core/Test-MtConnection.ps1 +++ b/powershell/public/core/Test-MtConnection.ps1 @@ -19,7 +19,7 @@ Function Test-MtConnection { [CmdletBinding()] param( # Checks if the current session is connected to the specified service - [ValidateSet("All", "Azure", "ExchangeOnline", "Graph")] + [ValidateSet("All", "Azure", "ExchangeOnline", "Graph", "SecurityCompliance")] [Parameter(Position = 0, Mandatory = $false)] [string[]]$Service = "Graph" ) @@ -59,5 +59,16 @@ Function Test-MtConnection { if (!$isConnected) { $connectionState = $false } } + if ($Service -contains "SecurityCompliance" -or $Service -contains "All") { + $isConnected = $false + try { + $isConnected = $null -ne ((Get-ConnectionInformation | Where-Object { $_.Name -match 'ExchangeOnline' -and $_.state -eq 'Connected' -and $_.IsEopSession })) + } catch { + Write-Debug "Security & Compliance: $false" + } + Write-Verbose "Security & Compliance: $isConnected" + if (!$isConnected) { $connectionState = $false } + } + Write-Output $connectionState } \ No newline at end of file diff --git a/tests/CISA/exchange/Test-MtCisaDlp.Tests copy.ps1 b/tests/CISA/exchange/Test-MtCisaDlp.Tests copy.ps1 new file mode 100644 index 00000000..c829e143 --- /dev/null +++ b/tests/CISA/exchange/Test-MtCisaDlp.Tests copy.ps1 @@ -0,0 +1,10 @@ +Describe "CISA SCuBA" -Tag "MS.EXO", "MS.EXO.8.1", "CISA", "Security", "All" { + It "MS.EXO.8.1: A DLP solution SHALL be used." { + + $cisaDlp = Test-MtCisaDlp + + if ($null -ne $cisaDlp) { + $cisaDlp | Should -Be $true -Because "DLP is enabled." + } + } +} \ No newline at end of file diff --git a/website/docs/installation.md b/website/docs/installation.md index a9b8bd06..655338f1 100644 --- a/website/docs/installation.md +++ b/website/docs/installation.md @@ -31,11 +31,13 @@ Install-Module Az -Scope CurrentUser Install-Module ExchangeOnlineManagement -Scope CurrentUser ``` +> The Security & Compliance PowerShell module is dependent on the ExchangeOnlineManagement `Connect-IPPSSession` cmdlet. + ### Connecting to Azure, Exchange and other services -In order to run all the CISA tests, you need to connect to the Azure and Exchange Online modules. +In order to run all the CISA tests, you need to connect to the Azure, Exchange Online, and other modules. -Run the following command to interactively connect to the Azure and Exchange Online modules. A sign in window will appear for each module. +Run the following command to interactively connect to the Azure, Exchange Online, and other modules. A sign in window will appear for each module. ```powershell Connect-Maester -Service All diff --git a/website/docs/tests/cisa/exo.md b/website/docs/tests/cisa/exo.md index 2b439d6e..58b15e69 100644 --- a/website/docs/tests/cisa/exo.md +++ b/website/docs/tests/cisa/exo.md @@ -31,6 +31,7 @@ See the [Installation guide](/docs/installation#optional-modules-and-permissions | Test-MtCisaContactSharing | [MS.EXO.6.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo61v1) | | Test-MtCisaCalendarSharing | [MS.EXO.6.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo62v1) | | Test-MtCisaExternalSenderWarning | [MS.EXO.7.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo71v1) | +| Test-MtCisaDlp | [MS.EXO.8.1v2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo81v2) | | Test-MtCisaAntiSpamAllowList | [MS.EXO.12.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo121v1) | | Test-MtCisaAntiSpamSafeList | [MS.EXO.12.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo122v1) | | Test-MtCisaMailboxAuditing | [MS.EXO.13.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo131v1) | \ No newline at end of file