From 66536e34a7c30c96f8edd53e3f50c9d3b0c89d5f Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 8 Jun 2024 15:53:12 -0500 Subject: [PATCH 1/3] fix: update domain pw policy logic --- source/Private/Invoke-TestFunction.ps1 | 2 +- source/Public/Invoke-M365SecurityAudit.ps1 | 10 +-- .../tests/Test-PasswordNeverExpirePolicy.ps1 | 64 +++++++++++++------ 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/source/Private/Invoke-TestFunction.ps1 b/source/Private/Invoke-TestFunction.ps1 index d9a5997..5f6b944 100644 --- a/source/Private/Invoke-TestFunction.ps1 +++ b/source/Private/Invoke-TestFunction.ps1 @@ -3,7 +3,7 @@ function Invoke-TestFunction { [Parameter(Mandatory = $true)] [PSObject]$FunctionFile, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] [string]$DomainName ) diff --git a/source/Public/Invoke-M365SecurityAudit.ps1 b/source/Public/Invoke-M365SecurityAudit.ps1 index aa22711..87509c6 100644 --- a/source/Public/Invoke-M365SecurityAudit.ps1 +++ b/source/Public/Invoke-M365SecurityAudit.ps1 @@ -5,8 +5,8 @@ The Invoke-M365SecurityAudit cmdlet performs a comprehensive security audit based on the specified parameters. It allows auditing of various configurations and settings within a Microsoft 365 environment, such as compliance with CIS benchmarks. .PARAMETER TenantAdminUrl The URL of the tenant admin. This parameter is mandatory. - .PARAMETER DomainName - The domain name of the Microsoft 365 environment. This parameter is mandatory. + .PARAMETER M365DomainForPWPolicyTest + The domain name of the Microsoft 365 environment to test. This parameter is not mandatory and by default it will pass/fail all found domains as a group if a specific domain is not specified. .PARAMETER ELevel Specifies the E-Level (E3 or E5) for the audit. This parameter is optional and can be combined with the ProfileLevel parameter. .PARAMETER ProfileLevel @@ -67,9 +67,9 @@ function Invoke-M365SecurityAudit { [ValidatePattern('^https://[a-zA-Z0-9-]+-admin\.sharepoint\.com$')] [string]$TenantAdminUrl, - [Parameter(Mandatory = $true, HelpMessage = "The domain name of your organization, e.g., 'example.com'.")] + [Parameter(Mandatory = $false, HelpMessage = "Specify this to test only the default domain for password expiration policy when '1.3.1' is included in the tests to be run. The domain name of your organization, e.g., 'example.com'.")] [ValidatePattern('^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$')] - [string]$DomainName, + [string]$M365DomainForPWPolicyTest, # E-Level with optional ProfileLevel selection [Parameter(Mandatory = $true, ParameterSetName = 'ELevelFilter')] @@ -194,7 +194,7 @@ function Invoke-M365SecurityAudit { Write-Progress -Activity "Executing Tests" -Status "Executing $($currentTestIndex) of $($totalTests): $($testFunction.Name)" -PercentComplete (($currentTestIndex / $totalTests) * 100) $functionName = $testFunction.BaseName if ($PSCmdlet.ShouldProcess($functionName, "Execute test")) { - $auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $DomainName + $auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $M365DomainForPWPolicyTest # Add the result to the collection [void]$allAuditResults.Add($auditResult) } diff --git a/source/tests/Test-PasswordNeverExpirePolicy.ps1 b/source/tests/Test-PasswordNeverExpirePolicy.ps1 index a4f9a07..7cf400a 100644 --- a/source/tests/Test-PasswordNeverExpirePolicy.ps1 +++ b/source/tests/Test-PasswordNeverExpirePolicy.ps1 @@ -2,9 +2,8 @@ function Test-PasswordNeverExpirePolicy { [CmdletBinding()] [OutputType([CISAuditResult])] param ( - # Aligned - [Parameter(Mandatory)] - [string]$DomainName # DomainName parameter is now mandatory + [Parameter(Mandatory = $false)] + [string]$DomainName ) begin { @@ -12,33 +11,58 @@ function Test-PasswordNeverExpirePolicy { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed $recnum = "1.3.1" + $overallResult = $true + $detailsList = @() + $failureReasonsList = @() + + # Add headers for the details + $detailsList += "Domain|Validity Period|IsDefault" } process { try { - # 1.3.1 (L1) Ensure the 'Password expiration policy' is set to 'Set passwords to never expire' - # Pass if PasswordValidityPeriodInDays is 0. Fail otherwise. - - # Retrieve password expiration policy - $passwordPolicy = Get-MgDomain -DomainId $DomainName | Select-Object -ExpandProperty PasswordValidityPeriodInDays - - # Prepare failure reasons and details based on compliance - $failureReasons = if ($passwordPolicy -ne 0) { - "Password expiration is not set to never expire" - } - else { - "N/A" + # Retrieve all domains or a specific domain + $domains = if ($DomainName) { + Get-MgDomain -DomainId $DomainName + } else { + Get-MgDomain } - $details = "Validity Period: $passwordPolicy days" + foreach ($domain in $domains) { + $domainName = $domain.Id + $isDefault = $domain.IsDefault + # Retrieve password expiration policy + $passwordPolicy = $domain.PasswordValidityPeriodInDays + + # Determine if the policy is compliant + $isCompliant = $passwordPolicy -eq 0 + $overallResult = $overallResult -and $isCompliant + + # Prepare failure reasons and details based on compliance + $failureReasons = if ($isCompliant) { + "N/A" + } else { + "Password expiration is not set to never expire for domain $domainName. Run the following command to remediate: `nUpdate-MgDomain -DomainId $domainName -PasswordValidityPeriodInDays 2147483647 -PasswordNotificationWindowInDays 30" + } + + $details = "$domainName|$passwordPolicy days|$isDefault" + + # Add details and failure reasons to the lists + $detailsList += $details + $failureReasonsList += $failureReasons + } + + # Prepare the final failure reason and details + $finalFailureReason = $failureReasonsList -join "`n" + $finalDetails = $detailsList -join "`n" # Create and populate the CISAuditResult object $params = @{ Rec = $recnum - Result = $passwordPolicy -eq 0 - Status = if ($passwordPolicy -eq 0) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Result = $overallResult + Status = if ($overallResult) { "Pass" } else { "Fail" } + Details = $finalDetails + FailureReason = $finalFailureReason } $auditResult = Initialize-CISAuditResult @params } From 4167a3712132052d2962df93be41f01e48b6ad37 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 8 Jun 2024 15:54:48 -0500 Subject: [PATCH 2/3] docs: Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c65fa..18e8a5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,9 @@ The format is based on and uses the types of changes according to [Keep a Change - Improved `Test-RestrictCustomScripts` to handle long URL lengths better by extracting and replacing common hostnames, and provided detailed output. - Added sorting to output. - Created new functions for improved modularity. -- Parameter validation for excel and csv path in sync function +- Parameter validation for Excel and CSV path in sync function. - Added Output type to tests. +- Added `M365DomainForPWPolicyTest` parameter to `Invoke-M365SecurityAudit` to specify testing only the default domain for password expiration policy when '1.3.1' is included in the tests. ### Fixed @@ -34,6 +35,7 @@ The format is based on and uses the types of changes according to [Keep a Change - Fixed the issue with the output in `Test-RestrictCustomScripts` to ensure no extra spaces between table headers and data. + ## [0.1.4] - 2024-05-30 ### Added From 9d9b9e70d9d88221ccef3f5125e019c9447d10c7 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 8 Jun 2024 15:56:25 -0500 Subject: [PATCH 3/3] docs: update help --- README.md | Bin 21496 -> 23626 bytes docs/index.html | Bin 65358 -> 65852 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/README.md b/README.md index c7d73b928ce3ba243639d1d58b19a27163b580f8..c33fe48f53be4bd1151bfb8226d393281dfd2fdd 100644 GIT binary patch delta 1641 zcmbtUO=}ZT6uqMsRIH_6#FB~=s?es1wZvK|C{0mOP!SYFsBUgdy`L_w(F)&wamNdmrC>TbIsu%Rw2z z-aq1_^SKM)-m}#FtBQS@H~V$g@#go`KIUsqOm1)sX!VSj~1i_IPPGA8691clBs}|{L@9GcXmv|?gR!# ztIL2!NmVx!6HU&9Gjn|)u!5}M7eG^#lcwvg32E5vnpZ25YeO0(RN!AWZ_a_}iGVF3 zPU3ANUy@uC0w!C7L0}5B0oEYP8s`iw*zW)xC(N>W+YV9QCL-I@CK!oEtpiaoGg>xN z43INt%8pu|ItDPMtr(-%cZsQJ1$-K1&|c#pwb4keq{;+a7F|{^&}*-S9mOsq^6rGshx$DANA`!a zdmkK{ng1*i9BBTYxZC3EqS+Idi>4kB_5Es580;(EsaiXnMbCX5xO+$O8z z%~c20cu$eej(WT}?DfGdT`{ZAdyLWZ`>53N$l%a!7kBqtn3J(VojWlaR)(KW{{`_Q BLWckV delta 179 zcmX@LgYn05#t9yqm#~E~PSz1Lnry%kwfT_(ALC>`Z;r_sf^w6mX-jRs!)e4bxy4dq z@-`=-$qnKxn-wIK7@-0po6m`tz&Vqb$Ovs-B&mfepd*bC*t|r>1}?i= owwX)e6w_oQYmv!^IHe{lC?`ywpsX=D$4hAPG$p&uw^Rzu0Tr`8ga7~l diff --git a/docs/index.html b/docs/index.html index 0d68372bc5bc61dcca6098caccfafc4f8d73df99..81728b49e8dc5d21d9d241ed8190634662fd65d2 100644 GIT binary patch delta 797 zcmX^2k9kiM%Y=KprVLgLra)-LV8~#+@%3eo$rrp?41F1l8O(s9E)4k$xeSR6nGAUh zZa{VsLjXfK5a%=G0L79SDj7l;QW=UFN+t_>%TE>x5!!su+klaLBN$^hr+TlX%m7E< zNQw-Y?C2}L`H7zs<%SdlP;7|s<{yD|6uD&c%HW$E`Xol^WXH<8lM7-Kcoi5*fRPO% zfzdqqY^-*D9*|!G6agiRJcbk?2~tr6ObQB65e0@MFfRqDCk-f83QQ~@9hqRV97yK? zp#nnzP_7sV^}(t@q979>a%n&_OTp$V0PR2|!eT$LqGB+qz>p5K7^JWO$j<~C12RAX tXk-D<>SVA8D3zib0a6Ll5BA7p#VXOspQ17*KhWDUS)r(G^Q`zS^8rJYv~d6c delta 163 zcmdnf#B%N*^Mrf6CJa^#rVN%q(tyEaFlT&6$O!itOH_4x6vxJWoBb2YOd7bwX zIA`*!M>%m)DDFF`~A