Skip to content

Commit

Permalink
Merge pull request #305 from Snozzberries/exoSecurity
Browse files Browse the repository at this point in the history
Exo security
  • Loading branch information
merill authored Jul 7, 2024
2 parents c19cdd1 + 314c5a6 commit ac7d470
Show file tree
Hide file tree
Showing 24 changed files with 260 additions and 39 deletions.
1 change: 1 addition & 0 deletions powershell/Maester.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions powershell/internal/Get-MtSkippedReason.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
}
4 changes: 2 additions & 2 deletions powershell/public/Add-MtTestResultDetail.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand Down
17 changes: 9 additions & 8 deletions powershell/public/CISA/exchange/Get-MailAuthenticationRecord.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions powershell/public/CISA/exchange/Resolve-SPFRecord.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 14 additions & 6 deletions powershell/public/CISA/exchange/Test-MtCisaDkim.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
44 changes: 44 additions & 0 deletions powershell/public/CISA/exchange/Test-MtCisaDlp.md
Original file line number Diff line number Diff line change
@@ -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)

<!--- Results --->
%TestResult%
64 changes: 64 additions & 0 deletions powershell/public/CISA/exchange/Test-MtCisaDlp.ps1
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions powershell/public/CISA/exchange/Test-MtCisaDmarcReport.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
12 changes: 10 additions & 2 deletions powershell/public/CISA/exchange/Test-MtCisaSpfDirective.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand All @@ -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
}
Expand Down
Loading

0 comments on commit ac7d470

Please sign in to comment.