From 9a7140347febd16b96d243151c868ee792557685 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Mon, 11 Sep 2023 12:20:41 -0400 Subject: [PATCH 1/5] Initial commit --- .../MSFT_M365DSCRuleEvaluation.psm1 | 273 ++++++++++++++++++ .../MSFT_M365DSCRuleEvaluation.schema.mof | 13 + .../MSFT_M365DSCRuleEvaluation/readme.md | 5 + .../MSFT_M365DSCRuleEvaluation/settings.json | 44 +++ .../Modules/M365DSCLogEngine.psm1 | 10 +- 5 files changed, 340 insertions(+), 5 deletions(-) create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/MSFT_M365DSCRuleEvaluation.psm1 create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/MSFT_M365DSCRuleEvaluation.schema.mof create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/readme.md create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/settings.json diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/MSFT_M365DSCRuleEvaluation.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/MSFT_M365DSCRuleEvaluation.psm1 new file mode 100644 index 0000000000..9e2c9c1dde --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/MSFT_M365DSCRuleEvaluation.psm1 @@ -0,0 +1,273 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ResourceName, + + [Parameter(Mandatory = $true)] + [System.String] + $RuleDefinition, + + [Parameter()] + [System.String] + $AfterRuleCountQuery, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity + ) + return $null +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ResourceName, + + [Parameter(Mandatory = $true)] + [System.String] + $RuleDefinition, + + [Parameter()] + [System.String] + $AfterRuleCountQuery, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity + ) + # Not Implemented +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ResourceName, + + [Parameter(Mandatory = $true)] + [System.String] + $RuleDefinition, + + [Parameter()] + [System.String] + $AfterRuleCountQuery, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity + ) + #region Telemetry + $CurrentResourceName = $MyInvocation.MyCommand.ModuleName -replace 'MSFT_', '' + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $CurrentResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + Write-Verbose -Message 'Testing configuration of AzureAD Tenant Details' + + $Global:PartialExportFileName = "$((New-Guid).ToString()).partial" + $module = Get-DSCResource -Module 'Microsoft365DSC' -Name $ResourceName + if ($null -ne $module) + { + $params = @{ + Credential = $PSBoundParameters.Credential + ApplicationId = $PSBoundParameters.ApplicationId + TenantId = $PSBoundParameters.TenantId + CertificateThumbprint = $PSBoundParameters.CertificateThumbprint + ManagedIdentity = $PSBoundParameters.ManagedIdentity + } + + if ($null -ne $PSBoundParameters.ApplicationSecret) + { + $params.Add("ApplicationSecret", $PSBoundParameters.ApplicationSecret) + } + + Write-Verbose -Message "Importing module from Path {$($module.Path)}" + Import-Module $module.Path -Force -Function 'Export-TargetResource' | Out-Null + $cmdName = "MSFT_$ResourceName\Export-TargetResource" + + Write-Verbose -Message "Retrieving Instances" + $instances = &$cmdName @params + Write-Verbose -Message "Retrieved {$($instances.Length)} Instances" + + $DSCStringContent = @" + # Generated with Microsoft365DSC version 1.23.906.1 + # For additional information on how to use Microsoft365DSC, please visit https://aka.ms/M365DSC + param ( + ) + + Configuration M365TenantConfig + { + param ( + ) + + $OrganizationName = $ConfigurationData.NonNodeData.OrganizationName + + Import-DscResource -ModuleName 'Microsoft365DSC' -ModuleVersion '1.23.906.1' + + Node localhost + { + $instances + } + } + + M365TenantConfig -ConfigurationData .\ConfigurationData.psd1 +"@ + Write-Verbose -Message "Converting the retrieved instances into DSC Objects" + $DSCConvertedInstances = ConvertTo-DSCObject -Content $DSCStringContent + Write-Verbose -Message "Successfully converted {$($DSCConvertedInstances.Length)} DSC Objects." + + Write-Verbose -Message "Querying DSC Objects for invalid instances based on the specified Rule Definition." + $queryBlock = [Scriptblock]::Create($RuleDefinition) + [Array]$invalidInstances = $DSCConvertedInstances | Where-Object -FilterScript $queryBlock + Write-Verbose -Message "Identified {$($invalidInstances.Length)} invalid instances." + + $result = $InvalidInstances.Length -eq 0 + + if (-not [System.String]::IsNullOrEmpty($AfterRuleCountQuery)) + { + Write-Verbose -Message "Checking the After Rule Count" + $afterRuleCountQueryString = "`$invalidInstances.Length $AfterRuleCountQuery" + $afterRuleCountQueryBlock = [Scriptblock]::Create($afterRuleCountQueryString) + $result = [Boolean](Invoke-Command -ScriptBlock $afterRuleCountQueryBlock) + Write-Verbose -Message "Output of rule count: $($result | Out-String)" + } + + if (-not $result) + { + # Log drifts for each invalid instances found. + $invalidInstancesLogNames = '' + foreach ($invalidInstance in $invalidInstances) + { + $invalidInstancesLogNames += "[$ResourceName]$($invalidInstance.ResourceInstanceName)`r`n" + } + + if (-not $result) + { + $message = [System.Text.StringBuilder]::New() + [void]$message.AppendLine("The following resource instance(s) failed a rule validation:`r`n$invalidInstancesLogNames") + [void]$message.AppendLine("`r`nRuleDefinition:`r`n$RuleDefinition") + if (-not [System.String]::IsNullOrEmpty($AfterRuleCountQuery)) + { + [void]$message.AppendLine("`r`AfterRuleCountQuery:`r`n$AfterRuleCountQuery") + } + Add-M365DSCEvent -Message $message.ToString() ` + -EventType 'RuleEvaluation' ` + -EntryType 'Warning' ` + -EventID 1 -Source $CurrentResourceName + } + } + return $result + } +} + +function Export-TargetResource +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity + ) + return $null +} + + +Export-ModuleMember -Function *-TargetResource diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/MSFT_M365DSCRuleEvaluation.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/MSFT_M365DSCRuleEvaluation.schema.mof new file mode 100644 index 0000000000..34bf2bb21e --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/MSFT_M365DSCRuleEvaluation.schema.mof @@ -0,0 +1,13 @@ +[ClassVersion("1.0.0.0"), FriendlyName("M365DSCRuleEvaluation")] +class MSFT_M365DSCRuleEvaluation : OMI_BaseResource +{ + [Key, Description("")] String ResourceName; + [Required, Description("")] String RuleDefinition; + [Write, Description("")] String AfterRuleCountQuery; + [Write, Description("Credentials of the Azure Active Directory Admin"), EmbeddedInstance("MSFT_Credential")] string Credential; + [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; + [Write, Description("Id of the Azure Active Directory tenant used for authentication.")] String TenantId; + [Write, Description("Secret of the Azure Active Directory application to authenticate with."), EmbeddedInstance("MSFT_Credential")] String ApplicationSecret; + [Write, Description("Thumbprint of the Azure Active Directory application's authentication certificate to use for authentication.")] String CertificateThumbprint; + [Write, Description("Managed ID being used for authentication.")] Boolean ManagedIdentity; +}; diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/readme.md new file mode 100644 index 0000000000..e09b1d18bb --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/readme.md @@ -0,0 +1,5 @@ +# AAD Tenant Details + +## Description + +This resource configures the Azure AD Tenant Details diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/settings.json new file mode 100644 index 0000000000..f62031d967 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_M365DSCRuleEvaluation/settings.json @@ -0,0 +1,44 @@ +{ + "resourceName": "AADTenantDetails", + "description": "This resource configures the Azure AD Tenant Details.", + "roles": { + "read": [], + "update": [ + "Billing Administrator" + ] + }, + "permissions": { + "graph": { + "delegated": { + "read": [ + { + "name": "Organization.Read.All" + } + ], + "update": [ + { + "name": "Organization.Read.All" + }, + { + "name": "Organization.ReadWrite.All" + } + ] + }, + "application": { + "read": [ + { + "name": "Organization.Read.All" + } + ], + "update": [ + { + "name": "Organization.Read.All" + }, + { + "name": "Organization.ReadWrite.All" + } + ] + } + } + } +} diff --git a/Modules/Microsoft365DSC/Modules/M365DSCLogEngine.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCLogEngine.psm1 index 71d5847d70..de0b2dc117 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCLogEngine.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCLogEngine.psm1 @@ -180,7 +180,7 @@ function Add-M365DSCEvent [Parameter()] [System.String] - [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift')] + [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')] $EventType, [Parameter()] @@ -448,7 +448,7 @@ function New-M365DSCNotificationEndPointRegistration [Parameter(Mandatory = $true)] [System.String] - [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift')] + [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')] $EventType ) @@ -498,7 +498,7 @@ function Remove-M365DSCNotificationEndPointRegistration [Parameter(Mandatory = $true)] [System.String] - [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift')] + [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')] $EventType ) @@ -550,7 +550,7 @@ function Get-M365DSCNotificationEndPointRegistration [Parameter()] [System.String] - [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift')] + [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')] $EventType ) @@ -606,7 +606,7 @@ function Send-M365DSCNotificationEndPointMessage [Parameter()] [System.String] - [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift')] + [ValidateSet('Drift', 'Error', 'Warning', 'NonDrift', 'RuleEvaluation')] $EventType ) From 5a37032f0ea6a82a2fbc0ba02bc1ece065af814d Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 12 Sep 2023 09:44:11 -0400 Subject: [PATCH 2/5] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05509b1cd1..95ff99750d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ # UNRELEASED +* M365DSCRuleEvaluation + * Initial Release. * DEPENDENCIES * Updated ExchangeOnlineManagement to version 3.3.0. * Updated Microsoft.Graph modules to version 2.5.0. From 74da21450521400052051f6003e1ced756948b1f Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 12 Sep 2023 12:50:53 -0400 Subject: [PATCH 3/5] Create 1-Evaluate a rule.ps1 --- .../1-Evaluate a rule.ps1 | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Modules/Microsoft365DSC/Examples/Resources/M365DSCRuleEvaluation/1-Evaluate a rule.ps1 diff --git a/Modules/Microsoft365DSC/Examples/Resources/M365DSCRuleEvaluation/1-Evaluate a rule.ps1 b/Modules/Microsoft365DSC/Examples/Resources/M365DSCRuleEvaluation/1-Evaluate a rule.ps1 new file mode 100644 index 0000000000..0450b5aa3a --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/M365DSCRuleEvaluation/1-Evaluate a rule.ps1 @@ -0,0 +1,24 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $CredsCredential + ) + Import-DscResource -ModuleName Microsoft365DSC + + node localhost + { + M365DSCRuleEvaluation 'AllowAnonymousUsersToJoinMeetingAllPolicies' + { + ResourceName = 'TeamsMeetingPolicy' + RuleDefinition = "`$_.AllowAnonymousUsersToJoinMeeting -eq `$true" + Credential = $CredsCredential + } + } +} From 1ec521149b2e798685827b912e389853dd53f8a3 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 12 Sep 2023 14:05:05 -0400 Subject: [PATCH 4/5] Create Microsoft365DSC.M365DSCRuleEvaluation.Tests.ps1 --- ...soft365DSC.M365DSCRuleEvaluation.Tests.ps1 | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 Tests/Unit/Microsoft365DSC/Microsoft365DSC.M365DSCRuleEvaluation.Tests.ps1 diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.M365DSCRuleEvaluation.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.M365DSCRuleEvaluation.Tests.ps1 new file mode 100644 index 0000000000..21446da322 --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.M365DSCRuleEvaluation.Tests.ps1 @@ -0,0 +1,75 @@ +[CmdletBinding()] +param( +) +$M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` + -ChildPath '..\..\Unit' ` + -Resolve +$CmdletModule = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\Stubs\Microsoft365.psm1' ` + -Resolve) +$GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\Stubs\Generic.psm1' ` + -Resolve) +Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\UnitTestHelper.psm1' ` + -Resolve) + +$Global:DscHelper = New-M365DscUnitTestHelper -StubModule $CmdletModule ` + -DscResource 'M365DScRuleEvaluation' -GenericStubModule $GenericStubPath +Describe -Name $Global:DscHelper.DescribeHeader -Fixture { + InModuleScope -ModuleName $Global:DscHelper.ModuleName -ScriptBlock { + Invoke-Command -ScriptBlock $Global:DscHelper.InitializeScript -NoNewScope + BeforeAll { + + $secpasswd = ConvertTo-SecureString 'test@password1' -AsPlainText -Force + $Credential = New-Object System.Management.Automation.PSCredential ('tenantadmin@mydomain.com', $secpasswd) + + Mock -CommandName Confirm-M365DSCDependencies -MockWith { + } + + # Mock Write-Host to hide output during the tests + Mock -CommandName Write-Host -MockWith { + } + + Mock -CommandName MSFT_AADConditionalAccessPolicy\Export-TargetResource -MockWith { + return "AADConditionalAccessPolicy 'FakeItem1'{`r`n Enabled = `$true`r`n}`r`nAADConditionalAccessPolicy 'FakeItem2'{`r`n Enabled = `$false`r`n}" + } + + Mock -CommandName New-M365DSCConnection -MockWith { + return 'Credentials' + } + } + + # Test contexts + Context -Name 'The Rules are successfully evaluated.' -Fixture { + BeforeAll { + $testParams = @{ + ResourceName = 'AADConditionalAccessPolicy' + RuleDefinition = "`$_.Enabled -eq `$true" + AfterRuleCountQuery = '-eq 1' + Credential = $Credential + } + } + + It 'Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name 'The Rules are NOT successfully evaluated.' -Fixture { + BeforeAll { + $testParams = @{ + ResourceName = 'AADConditionalAccessPolicy' + RuleDefinition = "`$_.Enabled -eq `$true" + Credential = $Credential + } + } + + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + } + } +} + +Invoke-Command -ScriptBlock $Global:DscHelper.CleanupScript -NoNewScope From d34842faa09f4d225ed6b611757bbec525961d7b Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 12 Sep 2023 14:14:38 -0400 Subject: [PATCH 5/5] Update Unit Tests.yml --- .github/workflows/Unit Tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Unit Tests.yml b/.github/workflows/Unit Tests.yml index 5276499448..103e67b01c 100644 --- a/.github/workflows/Unit Tests.yml +++ b/.github/workflows/Unit Tests.yml @@ -20,6 +20,7 @@ jobs: shell: pwsh run: | Install-Module ReverseDSC -Force -Scope AllUsers + Install-Module DSCParser -Force -Scope AllUsers Install-Module PSDesiredStateConfiguration -Force -Scope AllUsers Install-Module Pester -Force -SkipPublisherCheck -Scope AllUsers [System.Environment]::SetEnvironmentVariable('M365DSCTelemetryEnabled', $false, [System.EnvironmentVariableTarget]::Machine);