From 6ec78135727959588c77d3d90ab36ec14fe427cf Mon Sep 17 00:00:00 2001 From: mcollera <30903564+mcollera@users.noreply.github.com> Date: Fri, 9 Feb 2018 14:19:09 -0500 Subject: [PATCH] Merge Dev into Master Build 1.1.0.0 (#33) * Active directory access entry (#28) * initial ActiveDirectoryAccessEntry resource * updates to ActiveDirectoryAccessEntry resource * ActiveDirectoryAccessEntry unit test; resource fixes * updated readme; added example; mof fixes * version rev * AuditRule fixes * Updated issue with ACLRules not always being an array when trying to add additional objects. Updated issue where Expected.Rules might only be a single object while trying to call a Where extension method. (#31) * Rights guid (#32) * Updated ActiveDirectoryAccessEntry example with a valid ADRights value Refactored Get-SchemaGuidId helper function to Get-DelegationRightsGuid so it returns schemaGuids and rightsGuids * typo corrections * Update Get-SchemaObjectName to resolve SchemaGuids and RightsGuids * Added $guidmap to Get-SchemaObjectName * Added $rootDse to Get-SchemaObjectName --- AccessControlDsc.psd1 | 2 +- .../AccessControlResourceHelper.psm1 | 76 +++ .../ActiveDirectoryAccessEntry.psm1 | 462 ++++++++++++++++++ .../ActiveDirectoryAccessEntry.schema.mof | 25 + .../ActiveDirectoryAuditRuleEntry.psm1 | 60 +-- .../ActiveDirectoryAuditRuleEntry.schema.mof | 2 +- .../NTFSAccessEntry/NTFSAccessEntry.psm1 | 6 +- .../RegistryAccessEntry.psm1 | 6 +- .../ActiveDirectoryAccessEntry_example.ps1 | 63 +++ .../ActiveDirectoryAuditRuleEntry_example.ps1 | 2 +- README.md | 52 +- Tests/TestHelper.psm1 | 98 ++++ .../Unit/ActiveDirectoryAccessEntry.Tests.ps1 | 351 +++++++++++++ .../ActiveDirectoryAuditRuleEntry.Tests.ps1 | 4 +- 14 files changed, 1138 insertions(+), 71 deletions(-) create mode 100644 DscResources/ActiveDirectoryAccessEntry/ActiveDirectoryAccessEntry.psm1 create mode 100644 DscResources/ActiveDirectoryAccessEntry/ActiveDirectoryAccessEntry.schema.mof create mode 100644 Examples/ActiveDirectoryAccessEntry_example.ps1 create mode 100644 Tests/Unit/ActiveDirectoryAccessEntry.Tests.ps1 diff --git a/AccessControlDsc.psd1 b/AccessControlDsc.psd1 index 539ed8c..c8f4c1e 100644 --- a/AccessControlDsc.psd1 +++ b/AccessControlDsc.psd1 @@ -8,7 +8,7 @@ @{ # Version number of this module. - ModuleVersion = '1.0.0.0' + ModuleVersion = '1.1.0.0' # ID used to uniquely identify this module GUID = 'a544c26f-3f96-4c1e-8351-1604867aafc5' diff --git a/DscResources/AccessControlResourceHelper/AccessControlResourceHelper.psm1 b/DscResources/AccessControlResourceHelper/AccessControlResourceHelper.psm1 index ada0edf..a5d7349 100644 --- a/DscResources/AccessControlResourceHelper/AccessControlResourceHelper.psm1 +++ b/DscResources/AccessControlResourceHelper/AccessControlResourceHelper.psm1 @@ -118,3 +118,79 @@ function ConvertTo-SID } } + +function Assert-Module +{ + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName + ) + + if (-not (Get-Module -Name $ModuleName -ListAvailable)) + { + $errorId = '{0}_ModuleNotFound' -f $ModuleName; + $errorMessage = $localizedString.RoleNotFoundError -f $ModuleName; + ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage; + } +} + +function Get-DelegationRightsGuid +{ + Param + ( + [Parameter()] + [string] + $ObjectName + ) + + if($ObjectName) + { + # Create a hashtable to store the GUID value of each schemaGuids and rightsGuids + $guidmap = @{} + $rootdse = Get-ADRootDSE + Get-ADObject -SearchBase ($rootdse.SchemaNamingContext) -LDAPFilter "(schemaidguid=*)" -Properties Name,schemaIDGUID | + Foreach-Object -Process { $guidmap[$_.Name] = [System.GUID]$_.schemaIDGUID } + + Get-ADObject -SearchBase ($rootdse.ConfigurationNamingContext) -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties Name,rightsGuid | + Foreach-Object -Process { $guidmap[$_.Name] = [System.GUID]$_.rightsGuid } + + return [system.guid]$guidmap[$ObjectName] + } + else + { + return [system.guid]"00000000-0000-0000-0000-000000000000" + } +} + +function Get-SchemaObjectName +{ + Param + ( + [Parameter()] + [guid] + $SchemaIdGuid + ) + + if($SchemaIdGuid) + { + $guidmap = @{} + $rootdse = Get-ADRootDSE + Get-ADObject -SearchBase ($rootdse.SchemaNamingContext) -LDAPFilter "(schemaidguid=*)" -Properties Name,schemaIDGUID | + Foreach-Object -Process { $guidmap[$_.Name] = [System.GUID]$_.schemaIDGUID } + + Get-ADObject -SearchBase ($rootdse.ConfigurationNamingContext) -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties Name,rightsGuid | + Foreach-Object -Process { $guidmap[$_.Name] = [System.GUID]$_.rightsGuid } + + # This is to address the edge case where one guid resolves to multiple names ex. f3a64788-5306-11d1-a9c5-0000f80367c1 resolves to Service-Principal-Name,Validated-SPN + $names = ( $guidmap.GetEnumerator() | Where-Object -FilterScript { $_.Value -eq $SchemaIdGuid } ).Name + return $names -join ',' + } + else + { + return "none" + } +} diff --git a/DscResources/ActiveDirectoryAccessEntry/ActiveDirectoryAccessEntry.psm1 b/DscResources/ActiveDirectoryAccessEntry/ActiveDirectoryAccessEntry.psm1 new file mode 100644 index 0000000..fd2dfba --- /dev/null +++ b/DscResources/ActiveDirectoryAccessEntry/ActiveDirectoryAccessEntry.psm1 @@ -0,0 +1,462 @@ +Import-Module -Name (Join-Path -Path ( Split-Path $PSScriptRoot -Parent ) ` + -ChildPath 'AccessControlResourceHelper\AccessControlResourceHelper.psm1') ` + -Force + +# Localized messages +data LocalizedData +{ + # culture="en-US" + ConvertFrom-StringData -StringData @' + ErrorPathNotFound = The requested path "{0}" cannot be found. + AclNotFound = Error obtaining "{0}" ACL + AclFound = Obtained "{0}" ACL + RemoveAccessError = "Unable to remove Access for "{0}" +'@ +} + +Function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([Hashtable])] + param + ( + [Parameter(Mandatory=$true)] + [System.String] + $DistinguishedName, + + [Parameter(Mandatory=$true)] + [Microsoft.Management.Infrastructure.CimInstance[]] + $AccessControlList + ) + + Assert-Module -ModuleName 'ActiveDirectory' + Import-Module -Name 'ActiveDirectory' -Verbose:$false -force + + $namespace = "root/Microsoft/Windows/DesiredStateConfiguration" + $cimAccessControlList = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]' + + $path = Join-Path -Path "ad:\" -ChildPath $DistinguishedName + + if(Test-Path -Path $path) + { + $currentAcl = Get-Acl -Path $path + + if($null -ne $currentAcl) + { + $message = $LocalizedData.AclFound -f $path + Write-Verbose -Message $message + + foreach($principal in $AccessControlList) + { + $cimAccessControlEntry = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]' + + $principalName = $principal.Principal + $forcePrincipal = $principal.ForcePrincipal + + $identity = Resolve-Identity -Identity $principalName + $currentPrincipalAccess = $currentAcl.Access.Where({$_.IdentityReference -eq $identity.Name}) + + foreach($access in $currentPrincipalAccess) + { + $accessControlType = $access.AccessControlType.ToString() + $activeDirectoryRights = $access.ActiveDirectoryRights.ToString().Split(',').Trim() + $inheritanceType = $access.InheritanceType.ToString() + $inheritedObjectType = $access.InheritedObjectType.ToString() + $objectType = $access.ObjectType.ToString() + + $cimAccessControlEntry += New-CimInstance -ClientOnly -Namespace $namespace -ClassName ActiveDirectoryAccessRule -Property @{ + AccessControlType = $accessControlType + ActiveDirectoryRights = @($activeDirectoryRights) + InheritanceType = $inheritanceType + InheritedObjectType = $inheritedObjectType + ObjectType = $objectType + Ensure = "" + } + } + + $CimAccessControlList += New-CimInstance -ClientOnly -Namespace $namespace -ClassName ActiveDirectoryAccessControlList -Property @{ + Principal = $principalName + ForcePrincipal = $forcePrincipal + AccessControlEntry = [Microsoft.Management.Infrastructure.CimInstance[]]@($cimAccessControlEntry) + } + } + } + else + { + $message = $LocalizedData.AclNotFound -f $path + Write-Verbose -Message $message + } + } + else + { + $Message = $LocalizedData.ErrorPathNotFound -f $path + Write-Verbose -Message $Message + } + + $ReturnValue = @{ + DistinguishedName = $DistinguishedName + AccessControlList = $CimAccessControlList + } + + return $ReturnValue +} + +Function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory=$true)] + [System.String] + $DistinguishedName, + + [Parameter(Mandatory=$true)] + [Microsoft.Management.Infrastructure.CimInstance[]] + $AccessControlList + ) + + Assert-Module -ModuleName 'ActiveDirectory' + Import-Module -Name 'ActiveDirectory' -Verbose:$false -force + + $path = Join-Path -Path "ad:\" -ChildPath $DistinguishedName + + if(Test-Path -Path $path) + { + $currentAcl = Get-Acl -Path $path + if($null -ne $currentAcl) + { + foreach($accessControlItem in $AccessControlList) + { + $principal = $accessControlItem.Principal + $identity = Resolve-Identity -Identity $principal + $identityRef = New-Object System.Security.Principal.NTAccount($identity.Name) + + $actualAce = $currentAcl.Access.Where({$_.IdentityReference -eq $identity.Name}) + + $aclRules = ConvertTo-ActiveDirectoryAccessRule -AccessControlList $accessControlItem -IdentityRef $identityRef + $results = Compare-ActiveDirectoryAccessRule -Expected $aclRules -Actual $actualAce + + $expected += $results.Rules + $absentToBeRemoved += $results.Absent + + if($accessControlItem.ForcePrinciPal) + { + $toBeRemoved += $results.ToBeRemoved + } + } + + $isInherited = 0 + $isInherited += $absentToBeRemoved.Rule.Where({$_.IsInherited -eq $true}).Count + $isInherited += $toBeRemoved.Rule.Where({$_.IsInherited -eq $true}).Count + + if($isInherited -gt 0) + { + $currentAcl.SetAccessRuleProtection($true,$true) + Set-Acl -Path $path -AclObject $currentAcl + } + + foreach($rule in $expected) + { + if($rule.Match -eq $false) + { + $nonMatch = $rule.Rule + ("Adding Access rule:"), + ("> Path : '{0}'" -f $path), + ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference), + ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights), + ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType), + ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType), + ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)), + ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) | + Write-Verbose + + $currentAcl.AddAccessRule($rule.Rule) + } + } + + foreach($rule in $absentToBeRemoved) + { + $nonMatch = $rule.Rule + ("Removing Access rule:"), + ("> Path : '{0}'" -f $path), + ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference), + ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights), + ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType), + ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType), + ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)), + ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) | + Write-Verbose + + $currentAcl.RemoveAccessRule($rule.Rule) + } + + foreach($rule in $toBeRemoved) + { + $nonMatch = $rule.Rule + ("Removing Access rule:"), + ("> Path : '{0}'" -f $path), + ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference), + ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights), + ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType), + ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType), + ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)), + ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) | + Write-Verbose + $currentAcl.RemoveAccessRule($rule.Rule) + } + + Set-Acl -Path $path -AclObject $currentAcl + } + else + { + $message = $LocalizedData.AclNotFound -f $path + Write-Verbose -Message $message + } + } + else + { + $Message = $LocalizedData.ErrorPathNotFound -f $path + Write-Verbose -Message $Message + } +} + +Function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory=$true)] + [System.String] + $DistinguishedName, + + [Parameter(Mandatory=$true)] + [Microsoft.Management.Infrastructure.CimInstance[]] + $AccessControlList + ) + + Assert-Module -ModuleName 'ActiveDirectory' + Import-Module -Name 'ActiveDirectory' -Verbose:$false -force + + $inDesiredState = $True + $path = Join-Path -Path "ad:\" -ChildPath $DistinguishedName + + if(Test-Path -Path $path) + { + $currentAcl = Get-Acl -Path $path + + if($null -ne $currentAcl) + { + foreach($accessControlItem in $AccessControlList) + { + $principal = $accessControlItem.Principal + $identity = Resolve-Identity -Identity $principal + $identityRef = New-Object System.Security.Principal.NTAccount($identity.Name) + + $aclRules = ConvertTo-ActiveDirectoryAccessRule -AccessControlList $accessControlItem -IdentityRef $identityRef + + $actualAce = $currentAcl.Access.Where({$_.IdentityReference -eq $identity.Name}) + + $results = Compare-ActiveDirectoryAccessRule -Expected $aclRules -Actual $actualAce + + $expected += $results.Rules + $absentToBeRemoved += $results.Absent + + if($accessControlItem.ForcePrincipal) + { + $toBeRemoved += $results.ToBeRemoved + } + + } + + foreach($rule in $expected) + { + if($rule.Match -eq $false) + { + $nonMatch = $rule.Rule + ("Found missing [present] Access rule:"), + ("> Principal : '{0}'" -f $principal), + ("> Path : '{0}'" -f $path), + ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference), + ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights), + ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType), + ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType), + ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)), + ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) | + Write-Verbose + + $inDesiredState = $False + } + } + + if($absentToBeRemoved.Count -gt 0) + { + foreach($rule in $absentToBeRemoved) + { + $nonMatch = $rule.Rule + ("Found [absent] Access rule:"), + ("> Principal : '{0}'" -f $principal), + ("> Path : '{0}'" -f $path), + ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference), + ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights), + ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType), + ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType), + ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)), + ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) | + Write-Verbose + $inDesiredState = $False + } + } + + if($toBeRemoved.Count -gt 0) + { + foreach($rule in $toBeRemoved) + { + $nonMatch = $rule.Rule + ("Non-matching Access rule found:"), + ("> Principal : '{0}'" -f $principal), + ("> Path : '{0}'" -f $path), + ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference), + ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights), + ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType), + ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType), + ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)), + ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) | + Write-Verbose + $inDesiredState = $False + } + } + } + else + { + $message = $LocalizedData.AclNotFound -f $path + Write-Verbose -Message $message + $inDesiredState = $False + } + } + else + { + $message = $LocalizedData.ErrorPathNotFound -f $path + Write-Verbose -Message $Message + $inDesiredState = $False + } + + return $inDesiredState +} + +Function ConvertTo-ActiveDirectoryAccessRule +{ + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.Management.Infrastructure.CimInstance] + $AccessControlList, + + [Parameter(Mandatory = $true)] + [System.Security.Principal.NTAccount] + $IdentityRef + ) + + $referenceObject = @() + + foreach($ace in $AccessControlList.AccessControlEntry) + { + $inheritedObjectType = Get-DelegationRightsGuid -ObjectName $ace.InheritedObjectType + $objectType = Get-DelegationRightsGuid -ObjectName $ace.ObjectType + $rule = [PSCustomObject]@{ + Rules = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($IdentityRef, $ace.ActiveDirectoryRights, $ace.AccessControlType, $objectType, $ace.InheritanceType, $inheritedObjectType) + Ensure = $ace.Ensure + } + $referenceObject += $rule + } + + return $referenceObject +} + +Function Compare-ActiveDirectoryAccessRule +{ + param + ( + [Parameter(Mandatory = $true)] + [PSCustomObject[]] + $Expected, + + [Parameter()] + [System.DirectoryServices.ActiveDirectoryAccessRule[]] + $Actual + ) + + $results = @() + $toBeRemoved = @() + $absentToBeRemoved = @() + + $presentRules = $Expected.Where({$_.Ensure -eq 'Present'}).Rules + $absentRules = $Expected.Where({$_.Ensure -eq 'Absent'}).Rules + foreach($referenceObject in $presentRules) + { + $match = $Actual.Where({ + $_.ActiveDirectoryRights -eq $referenceObject.ActiveDirectoryRights -and + $_.AccessControlType -eq $referenceObject.AccessControlType -and + $_.InheritanceType -eq $referenceObject.InheritanceType -and + $_.InheritedObjectType -eq $referenceObject.InheritedObjectType -and + $_.ObjectType -eq $referenceObject.ObjectType -and + $_.IdentityReference -eq $referenceObject.IdentityReference + }) + if($match.Count -ge 1) + { + $results += [PSCustomObject]@{ + Rule = $referenceObject + Match = $true + } + } + else + { + $results += [PSCustomObject]@{ + Rule = $referenceObject + Match = $false + } + } + } + + foreach($referenceObject in $absentRules) + { + $match = $Actual.Where({ + $_.ActiveDirectoryRights -eq $referenceObject.ActiveDirectoryRights -and + $_.AccessControlType -eq $referenceObject.AccessControlType -and + $_.InheritanceType -eq $referenceObject.InheritanceType -and + $_.InheritedObjectType -eq $referenceObject.InheritedObjectType -and + $_.ObjectType -eq $referenceObject.ObjectType -and + $_.IdentityReference -eq $referenceObject.IdentityReference + }) + if($match.Count -gt 0) + { + $absentToBeRemoved += [PSCustomObject]@{ + Rule = $referenceObject + } + } + } + + foreach($referenceObject in $Actual) + { + $match = $Expected.Rules.Where({ + $_.ActiveDirectoryRights -eq $referenceObject.ActiveDirectoryRights -and + $_.AccessControlType -eq $referenceObject.AccessControlType -and + $_.InheritanceType -eq $referenceObject.InheritanceType -and + $_.InheritedObjectType -eq $referenceObject.InheritedObjectType -and + $_.ObjectType -eq $referenceObject.ObjectType -and + $_.IdentityReference -eq $referenceObject.IdentityReference + }) + if($match.Count -eq 0) + { + $toBeRemoved += [PSCustomObject]@{ + Rule = $referenceObject + } + } + } + + return [PSCustomObject]@{ + Rules = $results + ToBeRemoved = $toBeRemoved + Absent = $absentToBeRemoved + } +} diff --git a/DscResources/ActiveDirectoryAccessEntry/ActiveDirectoryAccessEntry.schema.mof b/DscResources/ActiveDirectoryAccessEntry/ActiveDirectoryAccessEntry.schema.mof new file mode 100644 index 0000000..1ae8404 --- /dev/null +++ b/DscResources/ActiveDirectoryAccessEntry/ActiveDirectoryAccessEntry.schema.mof @@ -0,0 +1,25 @@ +[ClassVersion("1.0.0.0")] +class ActiveDirectoryAccessRule +{ + [Required, Description("Indicates whether to allow or deny access to the target item."), ValueMap{"Allow","Deny"}, Values{"Allow","Deny"}] String AccessControlType; + [Required, Description("Specifies the access rights that are assigned to an Active Directory Domain Services object."), ValueMap{"AccessSystemSecurity","CreateChild","Delete","DeleteChild","DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren","ListObject","ReadControl","ReadProperty","Self","WriteDacl","WriteOwner","WriteProperty"}, Values{"AccessSystemSecurity","CreateChild","Delete","DeleteChild","DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren","ListObject","ReadControl","ReadProperty","Self","WriteDacl","WriteOwner","WriteProperty"}] String ActiveDirectoryRights[]; + [Write, Description("Specifies if, and how, ACE information is applied to an object and its descendents."), ValueMap{"1","4","2","0","3"}, Values{"All","Children","Descendents","None","SelfAndChildren"}] String InheritanceType; + [Write, Description("Specifies the schema GUID of the object to which the access rule applies.")] String ObjectType; + [Write, Description("Specifies the object type name that identifies the type of child object that can inherit this access rule.")] String InheritedObjectType; + [Required, ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure; +}; + +[ClassVersion("1.0.0.0")] +class ActiveDirectoryAccessControlList +{ + [Write, Description("Indicates the identity of the principal.")] String Principal; + [Write] Boolean ForcePrincipal; + [Write, Description("Indicates the access control entry in the form of an array of instances of the AccessControlList CIM class."), EmbeddedInstance("ActiveDirectoryAccessRule")] String AccessControlEntry[]; +}; + +[ClassVersion("1.0.0.0"), FriendlyName("ActiveDirectoryAccessEntry")] +class ActiveDirectoryAccessEntry : OMI_BaseResource +{ + [Key, Description("Indicates the Distinguished Name value for the target Active Directory Object.")] String DistinguishedName; + [Required, Description("Indicates the access control information in the form of an array of instances of the ActiveDirectoryAccessControlList CIM class."), EmbeddedInstance("ActiveDirectoryAccessControlList")] String AccessControlList[]; +}; diff --git a/DscResources/ActiveDirectoryAuditRuleEntry/ActiveDirectoryAuditRuleEntry.psm1 b/DscResources/ActiveDirectoryAuditRuleEntry/ActiveDirectoryAuditRuleEntry.psm1 index 8af723e..93c5de3 100644 --- a/DscResources/ActiveDirectoryAuditRuleEntry/ActiveDirectoryAuditRuleEntry.psm1 +++ b/DscResources/ActiveDirectoryAuditRuleEntry/ActiveDirectoryAuditRuleEntry.psm1 @@ -203,7 +203,7 @@ Function Set-TargetResource } } - foreach($Rule in $AbsentToBeRemoved.Rule) + foreach($Rule in $AbsentToBeRemoved) { $NonMatch = $Rule.Rule ("Removing audit rule:"), @@ -215,10 +215,10 @@ Function Set-TargetResource ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $NonMatch.InheritedObjectType)) | Write-Verbose - $currentAcl.RemoveAuditRule($Rule) + $currentAcl.RemoveAuditRule($Rule.Rule) } - foreach($Rule in $ToBeRemoved.Rule) + foreach($Rule in $ToBeRemoved) { $NonMatch = $Rule.Rule ("Removing audit rule:"), @@ -229,7 +229,7 @@ Function Set-TargetResource ("> InheritanceType : '{0}'" -f $NonMatch.InheritanceType), ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $NonMatch.InheritedObjectType)) | Write-Verbose - $currentAcl.RemoveAuditRule($Rule) + $currentAcl.RemoveAuditRule($Rule.Rule) } Set-Acl -Path $Path -AclObject $currentAcl @@ -406,7 +406,7 @@ Function ConvertTo-ActiveDirectoryAuditRule foreach($ace in $AccessControlList.AccessControlEntry) { - $InheritedObjectType = Get-SchemaIdGuid -ObjectName $ace.InheritedObjectType + $InheritedObjectType = Get-DelegationRightsGuid -ObjectName $ace.InheritedObjectType $rule = [PSCustomObject]@{ Rules = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($IdentityRef, $ace.ActiveDirectoryRights, $ace.AuditFlags, $ace.InheritanceType, $InheritedObjectType) Ensure = $ace.Ensure @@ -501,53 +501,3 @@ Function Compare-ActiveDirectoryAuditRule Absent = $AbsentToBeRemoved } } - -function Assert-Module -{ - [CmdletBinding()] - param - ( - [Parameter()] [ValidateNotNullOrEmpty()] - [System.String] $ModuleName = 'ActiveDirectory' - ) - - if (-not (Get-Module -Name $ModuleName -ListAvailable)) - { - $errorId = '{0}_ModuleNotFound' -f $ModuleName; - $errorMessage = $localizedString.RoleNotFoundError -f $moduleName; - ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage; - } -} - -Function Get-SchemaIdGuid -{ - Param - ( - [Parameter()] - [string] - $ObjectName - ) - - if($ObjectName) - { - $value = Get-ADObject -filter {name -eq $ObjectName} -SearchBase (Get-ADRootDSE).schemaNamingContext -prop schemaIDGUID - return [guid]$value.schemaIDGUID - } - else - { - return [system.guid]"00000000-0000-0000-0000-000000000000" - } -} - -Function Get-SchemaObjectName -{ - Param - ( - [Parameter()] - [guid] - $SchemaIdGuid - ) - - $value = Get-ADObject -filter {schemaIDGUID -eq $SchemaIdGuid} -SearchBase (Get-ADRootDSE).schemaNamingContext -prop schemaIDGUID - return $value.name -} diff --git a/DscResources/ActiveDirectoryAuditRuleEntry/ActiveDirectoryAuditRuleEntry.schema.mof b/DscResources/ActiveDirectoryAuditRuleEntry/ActiveDirectoryAuditRuleEntry.schema.mof index 7aea321..0381205 100644 --- a/DscResources/ActiveDirectoryAuditRuleEntry/ActiveDirectoryAuditRuleEntry.schema.mof +++ b/DscResources/ActiveDirectoryAuditRuleEntry/ActiveDirectoryAuditRuleEntry.schema.mof @@ -2,7 +2,7 @@ class ActiveDirectoryAuditRule { [Required, Description("Specifies the conditions for auditing attempts to access a securable object."), ValueMap{"Success","Failure"}, Values{"Success","Failure"}] String AuditFlags; - [Required, Description("Specifies the access rights that are assigned to an Active Directory Domain Services object."), ValueMap{"AccessSystemSecurity","CreateChild","Delete","DeleteChild","DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren","ListObject","ReadControl","ReadProperty","Self","WriteDacl","WriteOwner","WriteProperty"}, Values{"AccessSystemSecurity","CreateAllChildObjects","Delete","DeleteAllChildObjects","DeleteSubtree","AllExtendedRights","FullControl","Execute","Read","Write","ListContents","ListObject","ReadPermissions","ReadAllProperties","AllValidatedWrites","ModifyPermissions","ModifyOwner","WriteAllProperties"}] String ActiveDirectoryRights[]; + [Required, Description("Specifies the access rights that are assigned to an Active Directory Domain Services object."), ValueMap{"AccessSystemSecurity","CreateChild","Delete","DeleteChild","DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren","ListObject","ReadControl","ReadProperty","Self","WriteDacl","WriteOwner","WriteProperty"}, Values{"AccessSystemSecurity","CreateChild","Delete","DeleteChild","DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren","ListObject","ReadControl","ReadProperty","Self","WriteDacl","WriteOwner","WriteProperty"}] String ActiveDirectoryRights[]; [Write, Description("Specifies if, and how, ACE information is applied to an object and its descendents."), ValueMap{"1","4","2","0","3"}, Values{"All","Children","Descendents","None","SelfAndChildren"}] String InheritanceType; [Write, Description("Specifies the object type name that identifies the type of child object that can inherit this access rule.")] String InheritedObjectType; [Required, ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure; diff --git a/DscResources/NTFSAccessEntry/NTFSAccessEntry.psm1 b/DscResources/NTFSAccessEntry/NTFSAccessEntry.psm1 index 4252051..5e48123 100644 --- a/DscResources/NTFSAccessEntry/NTFSAccessEntry.psm1 +++ b/DscResources/NTFSAccessEntry/NTFSAccessEntry.psm1 @@ -117,6 +117,8 @@ Function Set-TargetResource $Force = $false ) + $ACLRules = @() + $inputPath = Get-InputPath($Path) if(Test-Path -Path $inputPath) @@ -274,6 +276,8 @@ Function Test-TargetResource $Force = $false ) + $ACLRules = @() + $InDesiredState = $True $inputPath = Get-InputPath($Path) @@ -598,7 +602,7 @@ Function Compare-NtfsRule foreach($refrenceObject in $Actual) { - $match = $Expected.Rules.Where({ + $match = @($Expected.Rules).Where({ (((($_.FileSystemRights.value__ -band $refrenceObject.FileSystemRights.value__) -match "$($_.FileSystemRights.value__)|$($refrenceObject.FileSystemRights.value__)") -and !$Force) -or ($_.FileSystemRights -eq $refrenceObject.FileSystemRights -and $Force)) -and $_.InheritanceFlags -eq $refrenceObject.InheritanceFlags -and $_.PropagationFlags -eq $refrenceObject.PropagationFlags -and diff --git a/DscResources/RegistryAccessEntry/RegistryAccessEntry.psm1 b/DscResources/RegistryAccessEntry/RegistryAccessEntry.psm1 index 3fb6578..b9e5200 100644 --- a/DscResources/RegistryAccessEntry/RegistryAccessEntry.psm1 +++ b/DscResources/RegistryAccessEntry/RegistryAccessEntry.psm1 @@ -113,6 +113,8 @@ Function Set-TargetResource $Force = $false ) + $ACLRules = @() + if(-not (Test-Path -Path $Path)) { $errorMessage = $LocalizedData.ErrorPathNotFound -f $Path @@ -236,6 +238,8 @@ Function Test-TargetResource $Force = $false ) + $ACLRules = @() + if(-not (Test-Path -Path $Path)) { $LocalizedData.ErrorPathNotFound -f $Path | Write-Verbose @@ -384,7 +388,7 @@ Function Compare-RegistryRule foreach($refrenceObject in $Actual) { - $match = $Expected.Rules.Where({ + $match = @($Expected.Rules).Where({ $_.RegistryRights -eq $refrenceObject.RegistryRights -and $_.InheritanceFlags -eq $refrenceObject.InheritanceFlags -and $_.PropagationFlags -eq $refrenceObject.PropagationFlags -and diff --git a/Examples/ActiveDirectoryAccessEntry_example.ps1 b/Examples/ActiveDirectoryAccessEntry_example.ps1 new file mode 100644 index 0000000..f1d172a --- /dev/null +++ b/Examples/ActiveDirectoryAccessEntry_example.ps1 @@ -0,0 +1,63 @@ +configuration Sample_ADAccessControl +{ + Import-DscResource -ModuleName AccessControlDsc + node localhost + { + ActiveDirectoryAccessEntry EastOU + { + DistinguishedName = "OU=east,DC=contoso,DC=com" + AccessControlList = @( + ActiveDirectoryAccessControlList + { + Principal = "contoso\Tier3" + ForcePrincipal = $false + AccessControlEntry = @( + ActiveDirectoryAccessRule + { + AccessControlType = 'Allow' + ActiveDirectoryRights = 'GenericAll' + InheritanceType = 'Descendents' + Ensure = 'Present' + } + ) + } + ) + } + ActiveDirectoryAccessEntry HelpdeskOU + { + DistinguishedName = "OU=dsc,DC=contoso,DC=com" + AccessControlList = @( + ActiveDirectoryAccessControlList + { + Principal = "contoso\helpdesk" + ForcePrincipal = $true + AccessControlEntry = @( + ActiveDirectoryAccessRule + { + AccessControlType = 'Allow' + ActiveDirectoryRights = 'Delete' + InheritanceType = 'Descendents' + InheritedObjectType = 'organizational-unit' + Ensure = 'Present' + } + ) + } + ActiveDirectoryAccessControlList + { + Principal = "contoso\testgroup" + ForcePrincipal = $true + AccessControlEntry = @( + ActiveDirectoryAccessRule + { + AccessControlType = 'Allow' + ActiveDirectoryRights = 'CreateChild', 'DeleteChild' + InheritanceType = 'all' + ObjectType = 'computer' + Ensure = 'Present' + } + ) + } + ) + } + } +} diff --git a/Examples/ActiveDirectoryAuditRuleEntry_example.ps1 b/Examples/ActiveDirectoryAuditRuleEntry_example.ps1 index f678780..eeeb323 100644 --- a/Examples/ActiveDirectoryAuditRuleEntry_example.ps1 +++ b/Examples/ActiveDirectoryAuditRuleEntry_example.ps1 @@ -15,7 +15,7 @@ configuration Sample_ADAccessControl ActiveDirectoryAuditRule { AuditFlags = 'Success' - ActiveDirectoryRights = 'FullControl' + ActiveDirectoryRights = 'GenericAll' InheritanceType = 'Descendents' Ensure = 'Present' } diff --git a/README.md b/README.md index 58b7617..dc2e172 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct]( https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ]( https://opensource.microsoft.com/codeofconduct/faq/) -or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions +or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## Contributing @@ -21,11 +21,42 @@ Please check out common DSC Resources [contributing guidelines]( ## Resources -* **[ActiveDirectoryAuditRule](#ActiveDirectoryAuditRule):** Provides the ability to manage audit access for Active Directory object SACL. +* [**ActiveDirectoryAccessEntry**](#activedirectoryaccessentry): Provides the ability to manage access entries for Active Directory objects. -* **[NtfsAccessEntry](#NtfsAccessEntry):** Provides the ability to manage access entries for NTFS files and directories. +* [**ActiveDirectoryAuditRule**](#activedirectoryauditrule): Provides the ability to manage audit access for Active Directory object SACL. -* **[RegistryAccessEntry](#RegistryAccessEntry):** Provides the ability to manage access entries for Registry objects. +* [**NtfsAccessEntry**](#ntfsaccessentry): Provides the ability to manage access entries for NTFS files and directories. + +* [**RegistryAccessEntry**](#registryaccessentry): Provides the ability to manage access entries for Registry objects. + +### **ActiveDirectoryAccessEntryRule** + +* **[String] DistinguishedName** _(Key)_: Indicates the Distinguished Name value for the target Active Directory Object. + +* **[String] AccessControlList**: Indicates the access control information in the form of an array of instances of the ActiveDirectoryAccessControlList CIM class. Includes the following properties: + + * **[String] Principal:** Indicates the identity of the principal. + + * **[String] AccessControlEntry:** Indicates the access control entry in the form of an array of instances of the AccessControlList CIM class. Includes the following properties: + + * **[String] AccessControlType:** Specifies whether an AccessRule object is used to allow or deny access. _{ Allow | Deny }_ + + * **[String] ActiveDirectoryRights:** Specifies the access rights that are assigned to an Active Directory Domain Services object. _{ AccessSystemSecurity | CreateChild | Delete | DeleteChild | DeleteTree | ExtendedRight | GenericAll | GenericExecute | GenericRead | GenericWrite | ListChildren | ListObject | ReadControl | ReadProperty | Self | WriteDacl | WriteOwner | WriteProperty }_ + + * **[String] Ensure:** Whether the rights should be present or absent. _{ Ensure | Present }_ + + * **[String] InheritanceType:** Specifies if, and how, ACE information is applied to an object and its descendents. _{ All | Children | Descendents | None | SelfAndChildren }_ + + * **[String] InheritedObjectType:** Specifies the object type name that identifies the type of child object that can inherit this access rule. + + * **[String] ObjectType:** Specifies the object type name that identifies the type of child object that can inherit this access rule. + + * [String] ForcePrincipal: Indicates whether the rights for this principal should be forced. Will remove any rights not explicitly defined in the configuration for the principal. + +#### ActiveDirectoryAccessRule Examples + +* [Set Active Directory OU access rules]( + https://github.com/mcollera/AccessControlDsc/blob/master/Examples/ActiveDirectoryAccessEntry_example.ps1) ### **ActiveDirectoryAuditRule** @@ -39,7 +70,7 @@ Please check out common DSC Resources [contributing guidelines]( * **[String] AuditFlags:** Specifies the conditions for auditing attempts to access a securable object. _{ Success | Failure }_ - * **[String] ActiveDirectoryRights:** Specifies the access rights that are assigned to an Active Directory Domain Services object. _{ AccessSystemSecurity | CreateAllChildObjects | Delete | DeleteAllChildObjects | DeleteSubtree | AllExtendedRights | FullControl | Execute | Read | Write | ListContents | ListObject | ReadPermissions | ReadAllProperties | AllValidatedWrites | ModifyPermissions | ModifyOwner | WriteAllProperties }_ + * **[String] ActiveDirectoryRights:** Specifies the access rights that are assigned to an Active Directory Domain Services object. _{ AccessSystemSecurity | CreateChild | Delete | DeleteChild | DeleteTree | ExtendedRight | GenericAll | GenericExecute | GenericRead | GenericWrite | ListChildren | ListObject | ReadControl | ReadProperty | Self | WriteDacl | WriteOwner | WriteProperty }_ * **[String] Ensure:** Whether the rights should be present or absent. _{ Ensure | Present }_ @@ -54,7 +85,7 @@ Please check out common DSC Resources [contributing guidelines]( #### ActiveDirectoryAuditRule Examples * [Set Active Directory OU audit access rules]( - https://github.com/PowerShell/AccessControlDsc/blob/master/Examples/ActiveDirectoryAuditRuleEntry_example.ps1) + https://github.com/mcollera/AccessControlDsc/blob/master/Examples/ActiveDirectoryAuditRuleEntry_example.ps1) ### **NtfsAccessEntry** @@ -81,7 +112,7 @@ Please check out common DSC Resources [contributing guidelines]( #### NtfsAccessEntry Examples * [Set access entries for NTFS folders]( - https://github.com/PowerShell/AccessControlDsc/blob/master/Examples/NtfsAccessEntry_example.ps1) + https://github.com/mcollera/AccessControlDsc/blob/master/Examples/NtfsAccessEntry_example.ps1) ### **RegistryAccessEntry** @@ -108,11 +139,14 @@ Please check out common DSC Resources [contributing guidelines]( #### RegistryAccessEntry Examples * [Configure access entries for registry key]( - https://github.com/PowerShell/AccessControlDsc/blob/master/Examples/RegistryAccessEntry_example.ps1) + https://github.com/mcollera/AccessControlDsc/blob/master/Examples/RegistryAccessEntry_example.ps1) ## Versions -### Unreleased +### 1.1.0.0 + +* Added ActiveDirectoryAccessEntry resource +* **Breaking Change:** Modified ActiveDirectoryAuditRuleEntry *ActiveDirectoryRights* parameter values to match [System.DirectoryServices.ActiveDirectoryRights](https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectoryrights(v=vs.110).aspx) members ### 1.0.0.0 diff --git a/Tests/TestHelper.psm1 b/Tests/TestHelper.psm1 index b6c50ed..93244e0 100644 --- a/Tests/TestHelper.psm1 +++ b/Tests/TestHelper.psm1 @@ -432,3 +432,101 @@ function New-AuditAccessControlList } Return $CimAccessControlList } + +<# + .SYNOPSIS + Creates an Access Control List Ciminstance + + .PARAMETER Principal + Name of the principal which access rights are being managed + + .PARAMETER ForcePrincipal + Used to force the desired access rule + + .PARAMETER AccessControlType + States if the principal should be will be allowed or denied access + + .PARAMETER ActiveDirectoryRights + What rights the principal is being given over an object + + .PARAMETER Inheritance + The inheritance properties of the object being managed + + .PARAMETER InheritedObjectType + The object type the inheritance property applies to + + .PARAMETER ObjectType + The object type the ActiveDirectoryRights applies to + + .PARAMETER Ensure + Either Present or Absent +#> + +function New-ActiveDirectoryAccessControlList +{ + param + ( + [Parameter(Mandatory = $true)] + $Principal, + + [Parameter(Mandatory = $true)] + [boolean] + $ForcePrincipal, + + [Parameter()] + [ValidateSet("Allow","Deny")] + [string] + $AccessControlType, + + [Parameter()] + [ValidateSet("AccessSystemSecurity","CreateChild","Delete","DeleteChild","DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren","ListObject","ReadControl","ReadProperty","Self","WriteDacl","WriteOwner","WriteProperty")] + $ActiveDirectoryRights, + + [Parameter()] + [ValidateSet("All","Children","Descendents","None","SelfAndChildren")] + [String] + $InheritanceType, + + [Parameter()] + [string] + $InheritedObjectType, + + [Parameter()] + [string] + $ObjectType, + + [Parameter(Mandatory = $true)] + [ValidateSet("Absent", "Present")] + $Ensure + ) + + $NameSpace = "root/Microsoft/Windows/DesiredStateConfiguration" + $CimAccessControlList = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]' + $CimAccessControlEntry = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]' + + if ($null -eq $ActiveDirectoryRights) + { + $CimAccessControlList += New-CimInstance -ClientOnly -Namespace $NameSpace -ClassName ActiveDirectoryAccessControlList -Property @{ + Principal = $Principal + ForcePrincipal = $ForcePrincipal + } + } + else + { + $CimAccessControlEntry += New-CimInstance -ClientOnly -Namespace $NameSpace -ClassName ActiveDirectoryAccessRule -Property @{ + AccessControlType = $AccessControlType + ActiveDirectoryRights = @($ActiveDirectoryRights) + InheritanceType = $InheritanceType + InheritedObjectType = $InheritedObjectType + ObjectType = $ObjectType + Ensure = $Ensure + } + + $CimAccessControlList += New-CimInstance -ClientOnly -Namespace $NameSpace -ClassName ActiveDirectoryAccessControlList -Property @{ + Principal = $Principal + ForcePrincipal = $ForcePrincipal + AccessControlEntry = [Microsoft.Management.Infrastructure.CimInstance[]]@($CimAccessControlEntry) + } + } + Return $CimAccessControlList +} diff --git a/Tests/Unit/ActiveDirectoryAccessEntry.Tests.ps1 b/Tests/Unit/ActiveDirectoryAccessEntry.Tests.ps1 new file mode 100644 index 0000000..e1c0e7b --- /dev/null +++ b/Tests/Unit/ActiveDirectoryAccessEntry.Tests.ps1 @@ -0,0 +1,351 @@ +#requires -Version 4.0 -Modules Pester + +#region Setup for tests + +$DSCResourceName = 'ActiveDirectoryAccessEntry' + +Import-Module "$($PSScriptRoot)\..\..\DSCResources\$($DSCResourceName)\$($DSCResourceName).psm1" -Force +Import-Module "$($PSScriptRoot)\..\..\DscResources\AccessControlResourceHelper\AccessControlResourceHelper.psm1" -Force +Import-Module "$($PSScriptRoot)\..\TestHelper.psm1" -Force + +#endregion + +InModuleScope ActiveDirectoryAccessEntry { + $DSCResourceName = 'ActiveDirectoryAccessEntry' + Describe "$DSCResourceName\Get-TargetResource" { + + Mock -CommandName Join-Path -MockWith { return "AD:\DC=PowerStig,DC=Local" } -ModuleName $DSCResourceName + Mock -CommandName Test-Path -MockWith { return $true } -ModuleName $DSCResourceName + Mock -CommandName Assert-Module -MockWith {} -ModuleName $DSCResourceName + Mock -CommandName Import-Module -MockWith {} -ParameterFilter {$name -eq 'ActiveDirectory'} -ModuleName $DSCResourceName + + Context "Should return current Access Rules" { + Mock -CommandName Get-Acl -MockWith { + $collection = [System.Security.AccessControl.AuthorizationRuleCollection]::new() + $identity = Resolve-Identity -Identity "Everyone" + $identityRef = [System.Security.Principal.NTAccount]::new($identity.Name) + $activeDirectoryRights = @("Delete","ReadControl") + + foreach($right in $activeDirectoryRights) + { + $accessRule = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($identityRef, [System.DirectoryServices.ActiveDirectoryRights]::$right , [System.Security.AccessControl.AccessControlType]::Allow, ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit,[System.Security.AccessControl.InheritanceFlags]::ObjectInherit) , [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a") + $collection.AddRule($accessRule) + } + + $acl = @{Access = $collection} + return $acl + } -ModuleName $DSCResourceName + + $tempAcl = New-ActiveDirectoryAccessControlList -Principal "Everyone" -ForcePrincipal $false -AccessControlType Allow -ActiveDirectoryRights GenericAll -InheritanceType All -InheritedObjectType "52ea1a9a-be7e-4213-9e69-5f28cb89b56a" -ObjectType "00000000-0000-0000-0000-000000000000" -Ensure Present + + $contextParams = @{ + DistinguishedName = "DC=PowerStig,DC=Local" + AccessControlList = $tempAcl + } + + $getResult = & "$($DSCResourceName)\Get-TargetResource" @contextParams + + It 'Should return Ensure set as empty' { + [string]::IsNullOrWhiteSpace($getResult.AccessControlList.AccessControlEntry.Ensure) | Should Be $true + } + + It 'Should return DistinguishedName' { + $getResult.DistinguishedName | Should Be "DC=PowerStig,DC=Local" + } + + It 'Should return Principal' { + $getResult.AccessControlList.Principal | Should Be "Everyone" + } + + It 'Should return AccessControlEntries' { + $getResult.AccessControlList.AccessControlEntry.Count | Should Be 2 + } + + It 'Should return InheritanceType' { + $getResult.AccessControlList.AccessControlEntry[0].InheritanceType | Should Be "SelfAndChildren" + } + + It 'Should return AccessControlType' { + $getResult.AccessControlList.AccessControlEntry[0].AccessControlType | Should Be "Allow" + } + } + + Context 'No permissions exist' { + + Mock -CommandName Get-Acl -MockWith { + $collection = [System.Security.AccessControl.AuthorizationRuleCollection]::new() + $acl = @{Access = $collection} + return $acl + } -ModuleName $DSCResourceName + + $tempAcl = New-ActiveDirectoryAccessControlList -Principal "Everyone" -ForcePrincipal $false -AccessControlType Allow -ActiveDirectoryRights GenericAll -InheritanceType All -InheritedObjectType "52ea1a9a-be7e-4213-9e69-5f28cb89b56a" -ObjectType "00000000-0000-0000-0000-000000000000" -Ensure Present + + $contextParams = @{ + DistinguishedName = "DC=PowerStig,DC=Local" + AccessControlList = $tempAcl + } + + $getResult = Get-TargetResource @contextParams + + It 'Should return Ensure set as empty' { + [string]::IsNullOrEmpty($getResult.AccessControl.AccessControlEntry.Ensure) | Should Be $true + } + + It 'Should return DistinguishedName' { + $getResult.DistinguishedName | Should Be $contextParams.DistinguishedName + } + + It 'Should return Principal' { + $getResult.AccessControlList.Principal | Should Be "Everyone" + } + + It 'Should return empty AccessControlEntry' { + $getResult.AccessControlList.AccessControlEntry.Count | Should Be 0 + } + } + } + + Describe "$DSCResourceName\Test-TargetResource" { + + Mock -CommandName Join-Path -MockWith { return "AD:\DC=PowerStig,DC=Local" } -ModuleName $DSCResourceName + Mock -CommandName Test-Path -MockWith { return $true } -ModuleName $DSCResourceName + Mock -CommandName Assert-Module -MockWith {} -ModuleName $DSCResourceName + Mock -CommandName Import-Module -MockWith {} -ParameterFilter {$name -eq 'ActiveDirectory'}-ModuleName $DSCResourceName + Mock -CommandName Get-DelegationRightsGuid -MockWith { return [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a" } -ModuleName $DSCResourceName + Mock -CommandName Get-SchemaObjectName -MockWith { return "Pwd-Last-Set" } -ModuleName $DSCResourceName + + Mock -CommandName Get-Acl -MockWith { + $collection = [System.Security.AccessControl.AuthorizationRuleCollection]::new() + $identity = Resolve-Identity -Identity "Everyone" + $identityRef = [System.Security.Principal.NTAccount]::new($identity.Name) + $activeDirectoryRights = @("Delete","ReadControl") + + foreach($right in $activeDirectoryRights) + { + $accessRule = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($identityRef, [System.DirectoryServices.ActiveDirectoryRights]::$right , [System.Security.AccessControl.AccessControlType]::Allow, ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit,[System.Security.AccessControl.InheritanceFlags]::ObjectInherit) , [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a") + $collection.AddRule($accessRule) + } + + $acl = @{Access = $collection} + return $acl + } -ModuleName $DSCResourceName + + Context "Permissions already exist with ForcePrincipal False" { + + $tempAcl = New-ActiveDirectoryAccessControlList -Principal "Everyone" -ForcePrincipal $false -AccessControlType Allow -ActiveDirectoryRights Delete -InheritanceType SelfAndChildren -InheritedObjectType "52ea1a9a-be7e-4213-9e69-5f28cb89b56a" -Ensure Present + + $contextParams = @{ + DistinguishedName = "DC=PowerStig,DC=Local" + AccessControlList = $tempAcl + } + + $testResult = & "$($DSCResourceName)\Test-TargetResource" @contextParams + + It 'Should return true' { + $testResult | Should Be $true + } + } + + Context "Permissions dont exist with ForcePrincipal False" { + + $tempAcl = New-ActiveDirectoryAccessControlList -Principal "Everyone" -ForcePrincipal $false -AccessControlType Allow -ActiveDirectoryRights CreateChild -InheritanceType SelfAndChildren -InheritedObjectType "52ea1a9a-be7e-4213-9e69-5f28cb89b56a" -Ensure Present + + $contextParams = @{ + DistinguishedName = "DC=PowerStig,DC=Local" + AccessControlList = $tempAcl + } + + $testResult = & "$($DSCResourceName)\Test-TargetResource" @contextParams + + It 'Should return false' { + $testResult | Should Be $false + } + } + + Context "Permissions dont exist with ForcePrincipal true" { + + $tempAcl = New-ActiveDirectoryAccessControlList -Principal "Everyone" -ForcePrincipal $true -AccessControlType Allow -ActiveDirectoryRights CreateChild -InheritanceType SelfAndChildren -InheritedObjectType "52ea1a9a-be7e-4213-9e69-5f28cb89b56a" -Ensure Present + + $contextParams = @{ + DistinguishedName = "DC=PowerStig,DC=Local" + AccessControlList = $tempAcl + } + + $testResult = & "$($DSCResourceName)\Test-TargetResource" @contextParams + + It 'Should return false' { + $testResult | Should Be $false + } + } + + Context "Permissions dont exist with ForcePrincipal false" { + + $tempAcl = New-ActiveDirectoryAccessControlList -Principal "Everyone" -ForcePrincipal $false -AccessControlType Allow -ActiveDirectoryRights CreateChild -InheritanceType SelfAndChildren -InheritedObjectType "52ea1a9a-be7e-4213-9e69-5f28cb89b56a" -Ensure Present + + $contextParams = @{ + DistinguishedName = "DC=PowerStig,DC=Local" + AccessControlList = $tempAcl + } + + $testResult = & "$($DSCResourceName)\Test-TargetResource" @contextParams + + It 'Should return false' { + $testResult | Should Be $false + } + + } + + Context "Multiple permissions already exist with ForcePrincipal true and only one principal required" { + + $tempAcl = New-ActiveDirectoryAccessControlList -Principal "Everyone" -ForcePrincipal $true -AccessControlType Allow -ActiveDirectoryRights Delete -InheritanceType SelfAndChildren -InheritedObjectType "52ea1a9a-be7e-4213-9e69-5f28cb89b56a" -Ensure Present + + $contextParams = @{ + DistinguishedName = "DC=PowerStig,DC=Local" + AccessControlList = $tempAcl + } + + $testResult = & "$($DSCResourceName)\Test-TargetResource" @contextParams + + It 'Should return false' { + $testResult | Should Be $false + } + } + } + + Describe "Helper Functions" { + + Mock -CommandName Join-Path -MockWith { return "AD:\DC=PowerStig,DC=Local" } -ModuleName $DSCResourceName + Mock -CommandName Test-Path -MockWith { return $true } -ModuleName $DSCResourceName + Mock -CommandName Assert-Module -MockWith {} -ModuleName $DSCResourceName + Mock -CommandName Import-Module -MockWith {} -ParameterFilter {$name -eq 'ActiveDirectory'} -ModuleName $DSCResourceName + Mock -CommandName Get-DelegationRightsGuid -MockWith { return [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a" } -ModuleName $DSCResourceName + Mock -CommandName Get-SchemaObjectName -MockWith { return "Pwd-Last-Set" } -ModuleName $DSCResourceName + + $identity = Resolve-Identity -Identity "Everyone" + $identityRef = [System.Security.Principal.NTAccount]::new($identity.Name) + $accessRule = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($identityRef, [System.DirectoryServices.ActiveDirectoryRights]::Delete , [System.Security.AccessControl.AccessControlType]::Allow, ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit,[System.Security.AccessControl.InheritanceFlags]::ObjectInherit) , [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a") + $tempAcl = New-ActiveDirectoryAccessControlList -Principal "Everyone" -ForcePrincipal $false -AccessControlType Allow -ActiveDirectoryRights Delete -InheritanceType SelfAndChildren -InheritedObjectType "52ea1a9a-be7e-4213-9e69-5f28cb89b56a" -ObjectType "00000000-0000-0000-0000-000000000000" -Ensure Present + + Context "ConvertTo-ActiveDirectoryAccessRule" { + + $convertedAccessRule = ConvertTo-ActiveDirectoryAccessRule -AccessControlList $tempAcl -IdentityRef $identityRef + + It 'Should return 1 rule' { + $convertedAccessRule.Rules.Count | Should Be 1 + } + + It 'Should return a pscustomobject' { + $convertedAccessRule.GetType().Name | Should Be "PSCustomObject" + } + + foreach($property in ($accessRule | Get-Member -MemberType Properties)) + { + It "$($property.Name) should match" { + $accessRule.($property.Name) | Should Be $convertedAccessRule.Rules[0].($property.Name) + } + } + } + + Context "Compare-ActiveDirectoryAccessRule with matching rules only" { + + $convertedAccessRule = ConvertTo-ActiveDirectoryAccessRule -AccessControlList $tempAcl -IdentityRef $identityRef + $compare = Compare-ActiveDirectoryAccessRule -Actual $accessRule -Expected $convertedAccessRule + + It 'Should return a pscustomobject' { + $convertedAccessRule.GetType().Name | Should Be "PSCustomObject" + } + + It "Should not have any ToBeRemoved Rules" { + $compare.ToBeRemoved.Count | Should Be 0 + } + + It "Should not have any Absent Rules" { + $compare.Absent.Count | Should Be 0 + } + + It "Should have 1 Rule" { + $compare.Rules.Count | Should Be 1 + } + + It "Returned rule should Match" { + $compare.Rules.Match | Should Be "True" + } + } + + Context "Compare-ActiveDirectoryAccessRule with multiple rules and Expected rule existing" { + + $collection = [System.Security.AccessControl.AuthorizationRuleCollection]::new() + $identity = Resolve-Identity -Identity "Everyone" + $identityRef = [System.Security.Principal.NTAccount]::new($identity.Name) + $identityRef2 = [System.Security.Principal.NTAccount]::new("BUILTIN\Users") + $accessRule = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($identityRef, [System.DirectoryServices.ActiveDirectoryRights]::Delete , [System.Security.AccessControl.AccessControlType]::Allow, ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit,[System.Security.AccessControl.InheritanceFlags]::ObjectInherit) , [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a") + $accessRule2 = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($identityRef, [System.DirectoryServices.ActiveDirectoryRights]::ReadControl , [System.Security.AccessControl.AccessControlType]::Allow, ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit,[System.Security.AccessControl.InheritanceFlags]::ObjectInherit) , [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a") + $accessRule3 = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($identityRef2, [System.DirectoryServices.ActiveDirectoryRights]::Delete , [System.Security.AccessControl.AccessControlType]::Allow, ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit,[System.Security.AccessControl.InheritanceFlags]::ObjectInherit) , [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a") + $collection.AddRule($accessRule) + $collection.AddRule($accessRule2) + $collection.AddRule($accessRule3) + $acl = @{Access = $collection} + + $convertedAccessRule = ConvertTo-ActiveDirectoryAccessRule -AccessControlList $tempAcl -IdentityRef $identityRef + $compare = Compare-ActiveDirectoryAccessRule -Actual $acl.Access -Expected $convertedAccessRule + + It 'Should return a pscustomobject' { + $convertedAccessRule.GetType().Name | Should Be "PSCustomObject" + } + + It "Should not have any ToBeRemoved Rules" { + $compare.ToBeRemoved.Count | Should Be 2 + } + + It "Should not have any Absent Rules" { + $compare.Absent.Count | Should Be 0 + } + + It "Should have 1 Rule" { + $compare.Rules.Count | Should Be 1 + } + + It "Returned rule should Match" { + $compare.Rules.Match | Should Be "True" + } + } + + Context "Compare-ActiveDirectoryAccessRule with multiple rules and Expected not existing existing" { + + $collection = [System.Security.AccessControl.AuthorizationRuleCollection]::new() + $identity = Resolve-Identity -Identity "Everyone" + $identityRefs = @([System.Security.Principal.NTAccount]::new($identity.Name),[System.Security.Principal.NTAccount]::new("BUILTIN\Users")) + + foreach($ref in $identityRefs) + { + $accessRule = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($ref, [System.DirectoryServices.ActiveDirectoryRights]::Delete , [System.Security.AccessControl.AccessControlType]::Deny, ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit,[System.Security.AccessControl.InheritanceFlags]::ObjectInherit) , [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a") + $collection.AddRule($accessRule) + } + + $acl = @{Access = $collection} + + $convertedAccessRule = ConvertTo-ActiveDirectoryAccessRule -AccessControlList $tempAcl -IdentityRef $identityRef + $compare = Compare-ActiveDirectoryAccessRule -Actual $acl.Access -Expected $convertedAccessRule + + It 'Should return a pscustomobject' { + $convertedAccessRule.GetType().Name | Should Be "PSCustomObject" + } + + It "Should not have any ToBeRemoved Rules" { + $compare.ToBeRemoved.Count | Should Be 2 + } + + It "Should not have any Absent Rules" { + $compare.Absent.Count | Should Be 0 + } + + It "Should have 1 Rule" { + $compare.Rules.Count | Should Be 1 + } + + It "Returned rule should Match" { + $compare.Rules.Match | Should Be "False" + } + } + } +} diff --git a/Tests/Unit/ActiveDirectoryAuditRuleEntry.Tests.ps1 b/Tests/Unit/ActiveDirectoryAuditRuleEntry.Tests.ps1 index f9a30c6..dcc5b1a 100644 --- a/Tests/Unit/ActiveDirectoryAuditRuleEntry.Tests.ps1 +++ b/Tests/Unit/ActiveDirectoryAuditRuleEntry.Tests.ps1 @@ -111,7 +111,7 @@ Import-Module "$($PSScriptRoot)\..\TestHelper.psm1" -Force Mock -CommandName Test-Path -MockWith { return $true } -ModuleName $DSCResourceName Mock -CommandName Assert-Module -MockWith {} -ModuleName $DSCResourceName Mock -CommandName Import-Module -MockWith {} -ParameterFilter {$Name -eq 'ActiveDirectory'}-ModuleName $DSCResourceName - Mock -CommandName Get-SchemaIdGuid -MockWith { return [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a" } -ModuleName $DSCResourceName + Mock -CommandName Get-DelegationRightsGuid -MockWith { return [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a" } -ModuleName $DSCResourceName Mock -CommandName Get-SchemaObjectName -MockWith { return "Pwd-Last-Set" } -ModuleName $DSCResourceName Mock -CommandName Get-Acl -MockWith { @@ -261,7 +261,7 @@ Import-Module "$($PSScriptRoot)\..\TestHelper.psm1" -Force Mock -CommandName Test-Path -MockWith { return $true } -ModuleName $DSCResourceName Mock -CommandName Assert-Module -MockWith {} -ModuleName $DSCResourceName Mock -CommandName Import-Module -MockWith {} -ParameterFilter {$Name -eq 'ActiveDirectory'} -ModuleName $DSCResourceName - Mock -CommandName Get-SchemaIdGuid -MockWith { return [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a" } -ModuleName $DSCResourceName + Mock -CommandName Get-DelegationRightsGuid -MockWith { return [guid]"52ea1a9a-be7e-4213-9e69-5f28cb89b56a" } -ModuleName $DSCResourceName Mock -CommandName Get-SchemaObjectName -MockWith { return "Pwd-Last-Set" } -ModuleName $DSCResourceName $Identity = Resolve-Identity -Identity "Everyone"