6 Commits

Author SHA1 Message Date
Doug Rios
c1171ddca5 Merge pull request #137 from CriticalSolutionsNetwork/132-formatting-microsoft-graph
fix: MgGraph commands formatting
2024-06-30 15:37:02 -05:00
Douglas Rios
fc7c8ec88f docs: Update CHANGELOG 2024-06-30 15:34:58 -05:00
Douglas Rios
2fc814205d fix: MgGraph commands formatting 2024-06-30 15:32:05 -05:00
Doug Rios
7309925e89 Merge pull request #136 from CriticalSolutionsNetwork/135-add-parameter-validation-to-new-parameters
135 add parameter validation to new parameters
2024-06-30 12:55:10 -05:00
DrIOS
5637855c8b docs: Update CHANGELOG 2024-06-30 12:53:21 -05:00
DrIOS
ac98307ed1 fix: parameter validation for new parameters in Invoke-M365SecurityAudit function 2024-06-30 12:53:07 -05:00
8 changed files with 27 additions and 86 deletions

View File

@@ -4,6 +4,18 @@ The format is based on and uses the types of changes according to [Keep a Change
## [Unreleased]
### Fixed
- Formatting for MgGraph tests.
## [0.1.20] - 2024-06-30
### Fixed
- Fixed parameter validation for new parameters in `Invoke-M365SecurityAudit` function
## [0.1.19] - 2024-06-30
### Added
- Added `ApprovedCloudStorageProviders` parameter to `Invoke-M365SecurityAudit` to allow for testing of approved cloud storage providers for 8.1.1.

View File

@@ -42,10 +42,12 @@ function Get-CISMgOutput {
switch ($rec) {
'1.1.1' {
# 1.1.1
# Test-AdministrativeAccountCompliance
$AdminRoleAssignmentsAndUsers = Get-AdminRoleUserAndAssignment
return $AdminRoleAssignmentsAndUsers
}
'1.1.3' {
# Test-GlobalAdminsCount
# Step: Retrieve global admin role
$globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'"
# Step: Retrieve global admin members
@@ -53,6 +55,7 @@ function Get-CISMgOutput {
return $globalAdmins
}
'1.2.1' {
# Test-ManagedApprovedPublicGroups
$allGroups = Get-MgGroup -All | Where-Object { $_.Visibility -eq "Public" } | Select-Object DisplayName, Visibility
return $allGroups
}
@@ -67,16 +70,19 @@ function Get-CISMgOutput {
return $domains
}
'5.1.2.3' {
# Test-RestrictTenantCreation
# Retrieve the tenant creation policy
$tenantCreationPolicy = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object AllowedToCreateTenants
return $tenantCreationPolicy
}
'5.1.8.1' {
# Test-PasswordHashSync
# Retrieve password hash sync status (Condition A and C)
$passwordHashSync = Get-MgOrganization | Select-Object -ExpandProperty OnPremisesSyncEnabled
return $passwordHashSync
}
'6.1.2' {
# Test-MailboxAuditingE3
$tenantSkus = Get-MgSubscribedSku -All
$e3SkuPartNumber = "SPE_E3"
$founde3Sku = $tenantSkus | Where-Object { $_.SkuPartNumber -eq $e3SkuPartNumber }
@@ -89,6 +95,7 @@ function Get-CISMgOutput {
}
}
'6.1.3' {
# Test-MailboxAuditingE5
$tenantSkus = Get-MgSubscribedSku -All
$e5SkuPartNumber = "SPE_E5"
$founde5Sku = $tenantSkus | Where-Object { $_.SkuPartNumber -eq $e5SkuPartNumber }

View File

@@ -132,30 +132,23 @@ function Invoke-M365SecurityAudit {
[Parameter(Mandatory = $false, HelpMessage = "The SharePoint tenant admin URL, which should end with '-admin.sharepoint.com'. If not specified none of the Sharepoint Online tests will run.")]
[ValidatePattern('^https://[a-zA-Z0-9-]+-admin\.sharepoint\.com$')]
[string]$TenantAdminUrl,
[Parameter(Mandatory = $false, HelpMessage = "Specify this to test only the default domain for password expiration and DKIM Config for tests '1.3.1' and 2.1.9. The domain name of your organization, e.g., 'example.com'.")]
[ValidatePattern('^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$')]
[string]$DomainName,
# E-Level with optional ProfileLevel selection
[Parameter(Mandatory = $true, ParameterSetName = 'ELevelFilter', HelpMessage = "Specifies the E-Level (E3 or E5) for the audit.")]
[ValidateSet('E3', 'E5')]
[string]$ELevel,
[Parameter(Mandatory = $true, ParameterSetName = 'ELevelFilter', HelpMessage = "Specifies the profile level (L1 or L2) for the audit.")]
[ValidateSet('L1', 'L2')]
[string]$ProfileLevel,
# IG Filters, one at a time
[Parameter(Mandatory = $true, ParameterSetName = 'IG1Filter', HelpMessage = "Includes tests where IG1 is true.")]
[switch]$IncludeIG1,
[Parameter(Mandatory = $true, ParameterSetName = 'IG2Filter', HelpMessage = "Includes tests where IG2 is true.")]
[switch]$IncludeIG2,
[Parameter(Mandatory = $true, ParameterSetName = 'IG3Filter', HelpMessage = "Includes tests where IG3 is true.")]
[switch]$IncludeIG3,
# Inclusion of specific recommendation numbers
[Parameter(Mandatory = $true, ParameterSetName = 'RecFilter', HelpMessage = "Specifies specific recommendations to include in the audit. Accepts an array of recommendation numbers.")]
[ValidateSet(
@@ -168,7 +161,6 @@ function Invoke-M365SecurityAudit {
'8.5.7', '8.6.1'
)]
[string[]]$IncludeRecommendation,
# Exclusion of specific recommendation numbers
[Parameter(Mandatory = $true, ParameterSetName = 'SkipRecFilter', HelpMessage = "Specifies specific recommendations to exclude from the audit. Accepts an array of recommendation numbers.")]
[ValidateSet(
@@ -181,26 +173,21 @@ function Invoke-M365SecurityAudit {
'8.5.7', '8.6.1'
)]
[string[]]$SkipRecommendation,
# Common parameters for all parameter sets
[Parameter(Mandatory = $false, HelpMessage = "Specifies the approved cloud storage providers for the audit. Accepts an array of cloud storage provider names.")]
[ValidateSet(
'GoogleDrive', 'ShareFile', 'Box', 'DropBox', 'Egnyte'
)]
[string[]]$ApprovedCloudStorageProviders = @(),
[Parameter(Mandatory = $false, HelpMessage = "Specifies the approved federated domains for the audit test 8.2.1. Accepts an array of allowed domain names.")]
[ValidatePattern('^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$')]
[string[]]$ApprovedFederatedDomains,
[Parameter(Mandatory = $false, HelpMessage = "Specifies that the cmdlet will not establish a connection to Microsoft 365 services.")]
[switch]$DoNotConnect,
[Parameter(Mandatory = $false, HelpMessage = "Specifies that the cmdlet will not disconnect from Microsoft 365 services after execution.")]
[switch]$DoNotDisconnect,
[Parameter(Mandatory = $false, HelpMessage = "Specifies that the cmdlet will not check for the presence of required modules.")]
[switch]$NoModuleCheck,
[Parameter(Mandatory = $false, HelpMessage = "Specifies that the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them.")]
[switch]$DoNotConfirmConnections
)
@@ -210,10 +197,8 @@ function Invoke-M365SecurityAudit {
}
# Ensure required modules are installed
$requiredModules = Get-RequiredModule -AuditFunction
# Format the required modules list
$requiredModulesFormatted = Format-RequiredModuleList -RequiredModules $requiredModules
# Check and install required modules if necessary
if (!($NoModuleCheck) -and $PSCmdlet.ShouldProcess("Check for required modules: $requiredModulesFormatted", "Check")) {
Write-Host "Checking for and installing required modules..." -ForegroundColor DarkMagenta
@@ -221,13 +206,11 @@ function Invoke-M365SecurityAudit {
Assert-ModuleAvailability -ModuleName $module.ModuleName -RequiredVersion $module.RequiredVersion -SubModules $module.SubModules
}
}
# Load test definitions from CSV
$testDefinitionsPath = Join-Path -Path $PSScriptRoot -ChildPath "helper\TestDefinitions.csv"
$testDefinitions = Import-Csv -Path $testDefinitionsPath
# Load the Test Definitions into the script scope for use in other functions
$script:TestDefinitionsObject = $testDefinitions
# Apply filters based on parameter sets
$params = @{
TestDefinitions = $testDefinitions
@@ -256,17 +239,14 @@ function Invoke-M365SecurityAudit {
# Initialize a collection to hold failed test details
$script:FailedTests = [System.Collections.ArrayList]::new()
} # End Begin
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"
$testFiles = Get-ChildItem -Path $testsFolderPath -Filter "Test-*.ps1" |
Where-Object { $testsToLoad -contains $_.BaseName }
$totalTests = $testFiles.Count
$currentTestIndex = 0
# Establishing connections if required
try {
$actualUniqueConnections = Get-UniqueConnection -Connections $requiredConnections
@@ -279,8 +259,6 @@ function Invoke-M365SecurityAudit {
Write-Host "Connection execution aborted: $_" -ForegroundColor Red
break
}
try {
Write-Host "A total of $($totalTests) tests were selected to run..." -ForegroundColor DarkMagenta
# Import the test functions
@@ -297,7 +275,6 @@ function Invoke-M365SecurityAudit {
$script:FailedTests.Add([PSCustomObject]@{ Test = $_.Name; Error = $_ })
}
}
$currentTestIndex = 0
# Execute each test function from the prepared list
foreach ($testFunction in $testFiles) {
@@ -330,7 +307,6 @@ function Invoke-M365SecurityAudit {
# Return all collected audit results
# Define the test numbers to check
$TestNumbersToCheck = "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4"
# Check for large details in the audit results
$exceedingTests = Get-ExceededLengthResultDetail -AuditResults $allAuditResults -TestNumbersToCheck $TestNumbersToCheck -ReturnExceedingTestsOnly -DetailsLengthLimit 30000
if ($exceedingTests.Count -gt 0) {
@@ -341,7 +317,4 @@ function Invoke-M365SecurityAudit {
return $allAuditResults.ToArray() | Sort-Object -Property Rec
}
}
}
}

View File

@@ -1,7 +1,6 @@
function Test-AdministrativeAccountCompliance {
[CmdletBinding()]
param ()
begin {
# The following conditions are checked:
# Condition A: The administrative account is cloud-only (not synced).
@@ -11,16 +10,12 @@ function Test-AdministrativeAccountCompliance {
$recnum = "1.1.1"
Write-Verbose "Starting Test-AdministrativeAccountCompliance with Rec: $recnum"
}
process {
try {
# Retrieve admin roles, assignments, and user details including licenses
Write-Verbose "Retrieving admin roles, assignments, and user details including licenses"
$adminRoleAssignments = Get-CISMgOutput -Rec $recnum
$adminRoleUsers = @()
foreach ($roleName in $adminRoleAssignments.Keys) {
$assignments = $adminRoleAssignments[$roleName]
foreach ($assignment in $assignments) {
@@ -29,21 +24,16 @@ function Test-AdministrativeAccountCompliance {
$userPrincipalName = $userDetails.UserPrincipalName
$licenses = $assignment.Licenses
$licenseString = if ($licenses) { ($licenses.SkuPartNumber -join '|') } else { "No Licenses Found" }
# Condition A: Check if the account is cloud-only
$cloudOnlyStatus = if ($userDetails.OnPremisesSyncEnabled) { "Fail" } else { "Pass" }
# Condition B: Check if the account has valid licenses
$hasValidLicense = $licenses.SkuPartNumber | ForEach-Object { $validLicenses -contains $_ }
$validLicensesStatus = if ($hasValidLicense) { "Pass" } else { "Fail" }
# Condition C: Check if the account has no other licenses
$hasInvalidLicense = $licenses.SkuPartNumber | ForEach-Object { $validLicenses -notcontains $_ }
$invalidLicenses = $licenses.SkuPartNumber | Where-Object { $validLicenses -notcontains $_ }
$applicationAssignmentStatus = if ($hasInvalidLicense) { "Fail" } else { "Pass" }
Write-Verbose "User: $userPrincipalName, Cloud-Only: $cloudOnlyStatus, Valid Licenses: $validLicensesStatus, Invalid Licenses: $($invalidLicenses -join ', ')"
# Collect user information
$adminRoleUsers += [PSCustomObject]@{
UserName = $userPrincipalName
@@ -57,17 +47,14 @@ function Test-AdministrativeAccountCompliance {
}
}
}
# Group admin role users by UserName and collect unique roles and licenses
Write-Verbose "Grouping admin role users by UserName"
$uniqueAdminRoleUsers = $adminRoleUsers | Group-Object -Property UserName | ForEach-Object {
$first = $_.Group | Select-Object -First 1
$roles = ($_.Group.RoleName -join ', ')
$licenses = (($_.Group | Select-Object -ExpandProperty Licenses) -join ',').Split(',') | Select-Object -Unique
$first | Select-Object UserName, UserId, HybridUser, @{Name = 'Roles'; Expression = { $roles } }, @{Name = 'Licenses'; Expression = { $licenses -join '|' } }, CloudOnlyStatus, ValidLicensesStatus, ApplicationAssignmentStatus
}
# Identify non-compliant users based on conditions A, B, and C
Write-Verbose "Identifying non-compliant users based on conditions"
$nonCompliantUsers = $uniqueAdminRoleUsers | Where-Object {
@@ -75,7 +62,6 @@ function Test-AdministrativeAccountCompliance {
$_.ValidLicensesStatus -eq "Fail" -or # Fails Condition B
$_.ApplicationAssignmentStatus -eq "Fail" # Fails Condition C
}
# Generate failure reasons
Write-Verbose "Generating failure reasons for non-compliant users"
$failureReasons = $nonCompliantUsers | ForEach-Object {
@@ -88,13 +74,10 @@ function Test-AdministrativeAccountCompliance {
else {
"Compliant Accounts: $($uniqueAdminRoleUsers.Count)"
}
$result = $nonCompliantUsers.Count -eq 0
$status = if ($result) { 'Pass' } else { 'Fail' }
$details = if ($nonCompliantUsers) { "Username | Roles | Cloud-Only Status | EntraID P1/P2 License Status | Other Applications Assigned Status`n$failureReasons" } else { "N/A" }
Write-Verbose "Assessment completed. Result: $status"
# Create the parameter splat
$params = @{
Rec = $recnum
@@ -103,7 +86,6 @@ function Test-AdministrativeAccountCompliance {
Details = $details
FailureReason = $failureReason
}
$auditResult = Initialize-CISAuditResult @params
}
catch {
@@ -111,7 +93,6 @@ function Test-AdministrativeAccountCompliance {
$auditResult = Get-TestError -LastError $LastError -recnum $recnum
}
}
end {
# Output the result
return $auditResult

View File

@@ -4,7 +4,6 @@ function Test-GlobalAdminsCount {
param (
# Define your parameters here if needed
)
begin {
# Dot source the class script if necessary
#. .\source\Classes\CISAuditResult.ps1
@@ -23,23 +22,19 @@ function Test-GlobalAdminsCount {
# - Condition A: The number of global admins is less than 2.
# - Condition B: The number of global admins is more than 4.
# - Condition C: Any discrepancies or errors in retrieving the list of global admin usernames.
# Initialization code, if needed
$recnum = "1.1.3"
Write-Verbose "Starting Test-GlobalAdminsCount with Rec: $recnum"
}
process {
try {
$globalAdmins = Get-CISMgOutput -Rec $recnum
# Step: Count the number of global admins
$globalAdminCount = $globalAdmins.Count
# Step: Retrieve and format the usernames of global admins
$globalAdminUsernames = ($globalAdmins | ForEach-Object {
"$($_.AdditionalProperties["displayName"]) ($($_.AdditionalProperties["userPrincipalName"]))"
}) -join ', '
# Step: Determine failure reasons based on global admin count
$failureReasons = if ($globalAdminCount -lt 2) {
"Less than 2 global admins: $globalAdminUsernames"
@@ -50,10 +45,8 @@ function Test-GlobalAdminsCount {
else {
"N/A"
}
# Step: Prepare details for the audit result
$details = "Count: $globalAdminCount; Users: $globalAdminUsernames"
# Step: Create and populate the CISAuditResult object
$params = @{
Rec = $recnum
@@ -69,7 +62,6 @@ function Test-GlobalAdminsCount {
$auditResult = Get-TestError -LastError $LastError -recnum $recnum
}
}
end {
# Return the audit result
return $auditResult

View File

@@ -4,14 +4,12 @@ function Test-ManagedApprovedPublicGroups {
param (
# Parameters can be added if needed
)
begin {
# Dot source the class script if necessary
#. .\source\Classes\CISAuditResult.ps1
# Initialization code, if needed
$recnum = "1.2.1"
Write-Verbose "Starting Test-ManagedApprovedPublicGroups with Rec: $recnum"
# Conditions for 1.2.1 (L2) Ensure that only organizationally managed/approved public groups exist (Automated)
#
# Validate test for a pass:
@@ -26,12 +24,10 @@ function Test-ManagedApprovedPublicGroups {
# - Condition A: One or more groups have the status 'Public' in the privacy column on the Active teams and groups page.
# - Condition B: Using Microsoft Graph PowerShell, one or more groups return a status of 'Public' when checked.
}
process {
try {
# Step: Retrieve all groups with visibility set to 'Public'
$allGroups = Get-CISMgOutput -Rec $recnum
# Step: Determine failure reasons based on the presence of public groups
$failureReasons = if ($null -ne $allGroups -and $allGroups.Count -gt 0) {
"There are public groups present that are not organizationally managed/approved."
@@ -39,7 +35,6 @@ function Test-ManagedApprovedPublicGroups {
else {
"N/A"
}
# Step: Prepare details for the audit result
$details = if ($null -eq $allGroups -or $allGroups.Count -eq 0) {
"No public groups found."
@@ -48,7 +43,6 @@ function Test-ManagedApprovedPublicGroups {
$groupDetails = $allGroups | ForEach-Object { $_.DisplayName + " (" + $_.Visibility + ")" }
"Public groups found: $($groupDetails -join ', ')"
}
# Step: Create and populate the CISAuditResult object
$params = @{
Rec = $recnum
@@ -64,7 +58,6 @@ function Test-ManagedApprovedPublicGroups {
$auditResult = Get-TestError -LastError $LastError -recnum $recnum
}
}
end {
# Return the audit result
return $auditResult

View File

@@ -5,7 +5,6 @@ function Test-PasswordHashSync {
# Aligned
# Parameters can be added if needed
)
begin {
# Conditions for 5.1.8.1 (L1) Ensure password hash sync is enabled for hybrid deployments
#
@@ -22,21 +21,18 @@ function Test-PasswordHashSync {
# - Condition A: Password hash sync is not enabled in the Azure AD Connect tool on the on-premises server.
# - Condition B: Password hash sync is not verified as enabled in the Azure AD Connect Sync section in the Microsoft Entra admin center.
# - Condition C: Using Microsoft Graph PowerShell, the verification command returns no result indicating that password sync is not enabled for the on-premises AD.
# Dot source the class script if necessary
#. .\source\Classes\CISAuditResult.ps1
# Initialization code, if needed
$recnum = "5.1.8.1"
Write-Verbose "Starting Test-PasswordHashSync with Rec: $recnum"
}
process {
try {
# 5.1.8.1 (L1) Ensure password hash sync is enabled for hybrid deployments
# Retrieve password hash sync status (Condition A and C)
$passwordHashSync = Get-CISMgOutput -Rec $recnum
$hashSyncResult = $passwordHashSync
# Prepare failure reasons and details based on compliance
$failureReasons = if (-not $hashSyncResult) {
"Password hash sync for hybrid deployments is not enabled"
@@ -44,9 +40,7 @@ function Test-PasswordHashSync {
else {
"N/A"
}
$details = "OnPremisesSyncEnabled: $($passwordHashSync)"
# Create and populate the CISAuditResult object
$params = @{
Rec = $recnum
@@ -62,9 +56,8 @@ function Test-PasswordHashSync {
$auditResult = Get-TestError -LastError $LastError -recnum $recnum
}
}
end {
# Return the audit result
return $auditResult
}
}
}

View File

@@ -5,23 +5,19 @@ function Test-RestrictTenantCreation {
# Aligned
# Parameters can be added if needed
)
begin {
# Dot source the class script if necessary
#. .\source\Classes\CISAuditResult.ps1
# Initialization code, if needed
$recnum = "5.1.2.3"
Write-Verbose "Starting Test-RestrictTenantCreation with Rec: $recnum"
<#
Conditions for 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes'
Validate test for a pass:
- Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark.
- Specific conditions to check:
- Condition A: Restrict non-admin users from creating tenants is set to 'Yes' in the Azure AD and Entra administration portal.
- Condition B: Using PowerShell, the setting for restricting non-admin users from creating tenants is set to 'Yes'.
Validate test for a fail:
- Confirm that the failure conditions in the automated test are consistent with the manual audit results.
- Specific conditions to check:
@@ -29,15 +25,12 @@ function Test-RestrictTenantCreation {
- Condition B: Using PowerShell, the setting for restricting non-admin users from creating tenants is not set to 'Yes'.
#>
}
process {
try {
# 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes'
# Retrieve the tenant creation policy
$tenantCreationPolicy = Get-CISMgOutput -Rec $recnum
$tenantCreationResult = -not $tenantCreationPolicy.AllowedToCreateTenants
# Prepare failure reasons and details based on compliance
$failureReasons = if ($tenantCreationResult) {
"N/A"
@@ -45,9 +38,7 @@ function Test-RestrictTenantCreation {
else {
"Non-admin users can create tenants"
}
$details = "AllowedToCreateTenants: $($tenantCreationPolicy.AllowedToCreateTenants)"
# Create and populate the CISAuditResult object
$params = @{
Rec = $recnum
@@ -63,9 +54,8 @@ function Test-RestrictTenantCreation {
$auditResult = Get-TestError -LastError $LastError -recnum $recnum
}
}
end {
# Return the audit result
return $auditResult
}
}
}