From 2a6aaffe2f79a9d5717ca45283cedb01ac4a8292 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Mon, 21 Apr 2025 11:17:24 -0500 Subject: [PATCH] Add: Get-TestDefinition function --- source/Public/Invoke-M365SecurityAudit.ps1 | 152 +++++++-------------- 1 file changed, 51 insertions(+), 101 deletions(-) diff --git a/source/Public/Invoke-M365SecurityAudit.ps1 b/source/Public/Invoke-M365SecurityAudit.ps1 index 829a368..13fa0b9 100644 --- a/source/Public/Invoke-M365SecurityAudit.ps1 +++ b/source/Public/Invoke-M365SecurityAudit.ps1 @@ -1,97 +1,63 @@ <# .SYNOPSIS - Invokes a security audit for Microsoft 365 environments. + Perform a CIS‑aligned security audit of a Microsoft 365 tenant. .DESCRIPTION - 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 in alignment with CIS benchmarks designated "Automatic". - Supports selection of CIS benchmark definitions version (default is 4.0.0). + Invoke-M365SecurityAudit runs a series of CIS benchmark tests (v3.0.0 or v4.0.0) against your + Microsoft 365 environment. You can filter by domain, license level (E3/E5), profile level (L1/L2), + IG levels, include or skip specific recommendations, and supply app‑based credentials. + Results are returned as an array of CISAuditResult objects. .PARAMETER TenantAdminUrl - The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + The SharePoint admin URL (e.g. https://contoso-admin.sharepoint.com). If omitted, SPO tests are skipped. .PARAMETER DomainName - The domain name of the Microsoft 365 environment to test. It is optional and will trigger various tests to run only for the specified domain. - Tests Affected: 2.1.9/Test-EnableDKIM, 1.3.1/Test-PasswordNeverExpirePolicy, 2.1.4/Test-SafeAttachmentsPolicy + Limit domain‐specific tests (1.3.1, 2.1.9) to this domain (e.g. “contoso.com”). .PARAMETER ELevel - Specifies the E-Level (E3 or E5) for the audit. This parameter is optional and can be combined with the ProfileLevel parameter. + License audit level (“E3” or “E5”). Requires -ProfileLevel to also be specified. .PARAMETER ProfileLevel - Specifies the profile level (L1 or L2) for the audit. This parameter is mandatory, but only when ELevel is selected. Otherwise, it is not required. + CIS profile level (“L1” or “L2”). Mandatory when -ELevel is used. .PARAMETER IncludeIG1 - If specified, includes tests where IG1 is true. + Include IG1‐only tests in the audit. .PARAMETER IncludeIG2 - If specified, includes tests where IG2 is true. + Include IG2‐only tests in the audit. .PARAMETER IncludeIG3 - If specified, includes tests where IG3 is true. + Include IG3‐only tests in the audit. .PARAMETER IncludeRecommendation - Specifies specific recommendations to include in the audit. Accepts an array of recommendation numbers. + An array of specific recommendation IDs to include (e.g. '1.1.3','2.1.1'). .PARAMETER SkipRecommendation - Specifies specific recommendations to exclude from the audit. Accepts an array of recommendation numbers. + An array of specific recommendation IDs to exclude. .PARAMETER ApprovedCloudStorageProviders - Specifies the approved cloud storage providers for the audit. Accepts an array of cloud storage provider names for test 8.1.1/Test-TeamsExternalFileSharing. - Acceptable values: 'GoogleDrive', 'ShareFile', 'Box', 'DropBox', 'Egnyte' + For test 8.1.1, list allowed storage providers (‘GoogleDrive’,’Box’,’ShareFile’,’DropBox’,’Egnyte’). .PARAMETER ApprovedFederatedDomains - Specifies the approved federated domains for the audit test 8.2.1/Test-TeamsExternalAccess. Accepts an array of allowed domain names. - Additional Tests may include this parameter in the future. + For test 8.2.1, list allowed federated domains (e.g. 'microsoft.com'). .PARAMETER DoNotConnect - If specified, the cmdlet will not establish a connection to Microsoft 365 services. + Skip connecting to Microsoft 365 services; you must have an existing session. .PARAMETER DoNotDisconnect - If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. + Skip disconnecting from Microsoft 365 services at the end. .PARAMETER NoModuleCheck - If specified, the cmdlet will not check for the presence of required modules. + Skip installing/checking required PowerShell modules. .PARAMETER DoNotConfirmConnections - If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. + When connecting, do not prompt for “Proceed?” before authenticating. .PARAMETER AuthParams - Specifies an authentication object containing parameters for application-based authentication. If provided, this will be used for connecting to services. + A CISAuthenticationParameters object for certificate‑based app authentication. .PARAMETER Version - Specifies the CIS benchmark definitions version to use. Default is 4.0.0. Valid values are "3.0.0" or "4.0.0". - .EXAMPLE - PS> Invoke-M365SecurityAudit - # Performs a security audit using default parameters. - .EXAMPLE - PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -ELevel "E5" -ProfileLevel "L1" - # Performs a security audit for the E5 level and L1 profile in the specified Microsoft 365 environment. - .EXAMPLE - PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -IncludeIG1 - # Performs a security audit while including tests where IG1 is true. - .EXAMPLE - PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -SkipRecommendation '1.1.3', '2.1.1' - # Performs an audit while excluding specific recommendations 1.1.3 and 2.1.1. - .EXAMPLE - PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -Version "3.0.0" - # Performs a security audit using the CIS benchmark definitions version 3.0.0. - .EXAMPLE - PS> $auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" - PS> Export-M365SecurityAuditTable -AuditResults $auditResults -ExportPath "C:\temp" -ExportOriginalTests -ExportAllTests - .EXAMPLE - # (PowerShell 7.x Only) Creating a new authentication object for the security audit for app-based authentication. - PS> $authParams = New-M365SecurityAuditAuthObject ` - -ClientCertThumbPrint "ABCDEF1234567890ABCDEF1234567890ABCDEF12" ` - -ClientId "12345678-1234-1234-1234-123456789012" ` - -TenantId "12345678-1234-1234-1234-123456789012" ` - -OnMicrosoftUrl "yourcompany.onmicrosoft.com" ` - -SpAdminUrl "https://yourcompany-admin.sharepoint.com" - Invoke-M365SecurityAudit -AuthParams $authParams -TenantAdminUrl "https://yourcompany-admin.sharepoint.com" - # Or: - PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation - # Captures the audit results into a variable and exports them to a CSV file (Nested tables will be truncated). - Output: - CISAuditResult[] - auditResults.csv - .EXAMPLE - PS> Invoke-M365SecurityAudit -WhatIf - Displays what would happen if the cmdlet is run without actually performing the audit. - Output: - What if: Performing the operation "Invoke-M365SecurityAudit" on target "Microsoft 365 environment". + CIS definitions version (“3.0.0” or “4.0.0”; default “4.0.0”). .INPUTS - None. You cannot pipe objects to Invoke-M365SecurityAudit. + None; this cmdlet does not accept pipeline input. .OUTPUTS - CISAuditResult[] - The cmdlet returns an array of CISAuditResult objects representing the results of the security audit. - .NOTES - - This module is based on CIS benchmarks. - - Governed by the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. - - Commercial use is not permitted. This module cannot be sold or used for commercial purposes. - - Modifications and sharing are allowed under the same license. - - For full license details, visit: https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en - - Register for CIS Benchmarks at: https://www.cisecurity.org/cis-benchmarks + CISAuditResult[] — an array of PSCustomObjects representing each test’s outcome. + .EXAMPLE + # Quick audit with defaults (v4.0.0) + Invoke-M365SecurityAudit + .EXAMPLE + # Audit E5, level L1, for a single domain: + Invoke-M365SecurityAudit -TenantAdminUrl 'https://contoso-admin.sharepoint.com' ` + -DomainName 'contoso.com' -ELevel E5 -ProfileLevel L1 + .EXAMPLE + # Only include specific recommendations: + Invoke-M365SecurityAudit -IncludeRecommendation '1.1.3','2.1.1' + .EXAMPLE + # App‑only auth + skip confirmation: + $auth = New-M365SecurityAuditAuthObject -ClientId ... -ClientCertThumbPrint ... + Invoke-M365SecurityAudit -AuthParams $auth -DoNotConfirmConnections .LINK https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Invoke-M365SecurityAudit #> @@ -185,7 +151,7 @@ function Invoke-M365SecurityAudit { [string] $Version = '4.0.0' ) - Begin { + begin { if ($script:MaximumFunctionCount -lt 8192) { Write-Verbose "Setting the `$script:MaximumFunctionCount to 8192 for the test run." $script:MaximumFunctionCount = 8192 @@ -228,28 +194,10 @@ function Invoke-M365SecurityAudit { # Ensure MgGraph assemblies are loaded prior to running PnP cmdlets Get-MgGroup -Top 1 -ErrorAction SilentlyContinue | Out-Null } - # Load test definitions from CSV - $testDefinitionsPath = Join-Path -Path $PSScriptRoot -ChildPath 'helper\TestDefinitions.csv' - $testDefinitions = Import-Csv -Path $testDefinitionsPath - # ################ Check for $Version -eq '4.0.0' ################ - if ($Version -eq '4.0.0') { - $script:Version400 = $true - $testDefinitionsV4Path = Join-Path -Path $PSScriptRoot -ChildPath 'helper\TestDefinitions-v4.0.0.csv' - $testDefinitionsV4 = Import-Csv -Path $testDefinitionsV4Path - # Merge the definitions, prioritizing version 4.0.0 - $mergedDefinitions = @{} - foreach ($test in $testDefinitions) { - $mergedDefinitions[$test.Rec] = $test - } - foreach ($testV4 in $testDefinitionsV4) { - $mergedDefinitions[$testV4.Rec] = $testV4 # Overwrite if Rec exists - } - # Convert back to an array - $testDefinitions = $mergedDefinitions.Values - Write-Verbose "Total tests after merging: $(($testDefinitions).Count)" - $overwrittenTests = $testDefinitionsV4 | Where-Object { $testDefinitions[$_.Rec] } - Write-Verbose "Overwritten tests: $($overwrittenTests.Rec -join ', ')" - } + # Define a function to load and merge test definitions + + # Call the function to load and merge test definitions + $testDefinitions = Get-TestDefinitions -Version $Version # Load the Test Definitions into the script scope for use in other functions $script:TestDefinitionsObject = $testDefinitions # Apply filters based on parameter sets @@ -280,7 +228,7 @@ function Invoke-M365SecurityAudit { # Initialize a collection to hold failed test details $script:FailedTests = [System.Collections.ArrayList]::new() } # End Begin - Process { + process { $allAuditResults = [System.Collections.ArrayList]::new() # Initialize a collection to hold all results # Dynamically dot-source the test scripts $testsFolderPath = Join-Path -Path $PSScriptRoot -ChildPath 'tests' @@ -297,10 +245,10 @@ function Invoke-M365SecurityAudit { } } catch { - Throw "Connection execution aborted: $_" + throw "Connection execution aborted: $_" } } - End { + end { try { if ($PSCmdlet.ShouldProcess("Measure and display audit results for $($totalTests) tests", 'Measure')) { Write-Information "A total of $($totalTests) tests were selected to run..." @@ -308,11 +256,11 @@ function Invoke-M365SecurityAudit { $testFiles | ForEach-Object { $currentTestIndex++ Write-Progress -Activity 'Loading Test Scripts' -Status "Loading $($currentTestIndex) of $($totalTests): $($_.Name)" -PercentComplete (($currentTestIndex / $totalTests) * 100) - Try { + try { # Dot source the test function . $_.FullName } - Catch { + catch { # Log the error and add the test to the failed tests collection Write-Verbose "Failed to load test function $($_.Name): $_" $script:FailedTests.Add([PSCustomObject]@{ Test = $_.Name; Error = $_ }) @@ -341,7 +289,9 @@ function Invoke-M365SecurityAudit { Write-Information "( Assuming the results were instantiated. Ex: `$object = invoke-M365SecurityAudit )`nUse the following command and adjust as necessary to view the full details of the test results:" Write-Information "Export-M365SecurityAuditTable -ExportAllTests -AuditResults `$object -ExportPath `"C:\temp`" -ExportOriginalTests" } - return $allAuditResults.ToArray() | Sort-Object -Property Rec + # return $allAuditResults.ToArray() | Sort-Object -Property Rec + # TODO Check if this fixes export-table. + return $allAuditResults | Sort-Object -Property Rec } } catch {