PSDscResources icon indicating copy to clipboard operation
PSDscResources copied to clipboard

Registry: Resource needs to delete all values under a key

Open zjalexander opened this issue 8 years ago • 3 comments

This scenario is from Bobby Reed for the Baseline Management scenario. Group Policies sometimes have a *delval command which clears all values under a key and then inserts a new key.

Imagine if I allowed a compromised and outdated cipher suite in the Registry Key. Without **delval, DSC would never catch it, as it would just ensure that the ALLOWED cipher suites were present. GROUP POLICY however, WOULD catch it because it is still processing the **delval properly.

That made me think of another discussion @kwirkykat had for the SecurityPolicy module: https://github.com/PowerShell/SecurityPolicyDsc/issues/28#issuecomment-288480885

I think the Registry resource needs something like an explicit “Include”/"Exclude" parameter along the lines the Group parameter Katie describes: https://github.com/powershell/psdscresources#group

zjalexander avatar May 19 '17 20:05 zjalexander

I’m re-thinking this slightly. I think this has to be an “Exclusive” property.

In the UserRightsAssignment resource you can use FORCE to ensure that only the members specified are members of the right, I think we should mirror that.

The reason why is that a MembersToInclude/Exclude will look very strange in a Registry Key resource block. The Group example only requires a string array because you just need to specify member names.

THIS KeysToInclude will need to contain a Registry Key Definition for Every instance.

Consider these examples:

Registry Test
{
   Key = "HKLM:\Software\Microsoft\7-Zip"
    # This is a KEY property so it has to be specified which makes it then odd to have to specify Member to Include as well.
    ValueName = "FastCompression"
    ValueData = 1
    ValueType = DWORD 
    # This just feels clunky because you would have to pass a valuename above because it's a key, but then you could specify other values as well? That would make it hard to troubleshoot.
    MembersToInclude = @(
        
        @{
            ValueName = "HighValue"
            ValueData = 1
            ValueType = DWORD
        }, 
        "AnotherValue" # So it should create another ValueName? Which ValueData etc.?
    )
    MembersToExclude = @(
        # Why should I need to specify all of this information, but if I don't it won't be consistent with above.
        @{
            ValueName = "Offset"
            ValueData = 1
            ValueType = DWORD
        }
        ,* # The Group Property doesn't accept wildcards, but this one would have to, it's the whole purpose of the change.
    )
}

Registry Test
{
    Key = "HKLM:\Software\Microsoft\7-Zip"
    ValueName = "FastCompression"
    ValueData = 1
    ValueType = DWORd
    Exclusive = $true # Could go with other names as well, but basically a property saying that this should be the only value in this key.
}

Registry Test
{
    Key = "HKLM:\Software\Microsoft\7-Zip"
    ValueName = "" # Even though Value Name is a Key, it can still be blank.  This has typically been used to remove a key.
    Exclusive = $true # Now this would wipe all values out of a key without erasing the key.
} 

Whether it's "Enforce", "Exclusive" or another word, I think a Flag is the best way to go. I have already successfully implemented an "Exclusive" flag and I am working on the Pester Tests now.

bobbytreed avatar May 30 '17 10:05 bobbytreed

@bobbytreed Can you put all of your code in a codeblock (with ```powershell). I imagine the middle got code blocked due to indentation, but your example is hard enough to read that I pasted it into a text editor.

I'm currently looking at this because I have a couple functions that I'm considering either breaking out into a RegistryPlus resource, or I'd like to see the feature implemeted as part of the actual Registry module.

My current implementation using a Script resource uses an AllowedValues property to provide a string[] of allowed Value Names that should be left alone, the rest deleted. You can see it here.

I agree that your initial example feels clunky. Then again, I feel like the implementation of ValueName as an empty string to target a Key is a bit clunky. This is a topic for a separate issue though.

I think an ideal implementation of removing excess registry Key Values would look something like this:

Registry 7Zip_FastCompression
{
    Key = 'HKLM:\Software\Microsoft\7-Zip'
    ValueName = 'FastCompression'
    ValueData = 1
    ValueType = 'DWORD'
}

Registry 7Zip_HighValue
{
    Key = 'HKLM:\Software\Microsoft\7-Zip'
    ValueName = 'HighValue'
    ValueData = 1
    ValueType = 'DWORD'
}

Registry 7Zip_RemoveExcessValueNames
{
    Key = 'HKLM:\Software\Microsoft\7-Zip'
    ValueName = '' # Ideally without this property
    RemoveExcessValueNames = $true
    DependsOn = @(
        '[Registry]7Zip_FastCompression',
        '[Registry]7Zip_HighValue'
    )
}

You could then do something like this to pull the Key/Value information out of the other Registry Resources based on the DependsOn value:

<#
    $Key, $DependsOn, and $RemoveExcessValueNames are from the respective
    `[Registry]7Zip_RemoveExcessValueNames` property
#>

if ($DependsOn)
{
    $DependentResources = Get-DscConfiguration `
        | Where-Object { $DependsOn -contains $_.ResourceId.Split(':')[0] }

    if ($DependentResources.ResourceId.StartsWith('[Registry]') -contains $false)
    {
        Throw "When using RemoveExcessValueNames, all resources referenced in DependsOn must be Registry resources."
    }

    if (($DependentResources.key | Sort-Object -Unique).Count -ne 1)
    {
        Throw "When using RemoveExcessValueNames, Registry resources referenced in DependsOn must all have the same Key."
    }

    if ($DependentResources.key[0] -ne $Key)
    {
        Throw "When using RemoveExcessValueNames, Registry resources referenced in DependsOn must have the same key as this resource."
    }
}

$AllowedValueNames = $DependentResources.ValueName
$RegKey = Get-Item -LiteralPath $Key
$NotAllowedValues = $RegKey.Property `
    | Where-Object { $AllowedValueNames -notcontains $_ }

foreach ($Value in $NotAllowedValues)
{
    Remove-ItemProperty -LiteralPath $Key -Name $Value -Force
}

Code logic should remove all values if a DependsOn is not supplied. I can work on a PR, but would like to know if my direction is amiable. Specifically the use of Get-DscConfiguration ... which I know won't work from a machine that's not applying these configs. I just am not real sure where to begin with exploring what DSC sees during building of a MOF file. :smirk:

Update: 2018 Apr 18

I'm looking through the work that @dlwyatt has done in DscFix, but I'm still reverse engineering his functions to try to figure out how he's able to check the properties of Resources for conflicts. Maybe Dave can provide some insight on how to best go about this?

Update: 2018 Apr 19

I'm not having luck implementing this because DependsOn isn't made available to Set-TargetResource or Test-TargetResource. Attempted to make it available in the schema.mof by adding this as a property descriptor:

[Write, Description("Make DependsOn available.")] String DependsOn[];

Unfortunately, it seemed to ignore that I was now asking for it. I could implement it like this:

Registry Test_RemoveExcessValueNames
{
    Key = 'HKLM:\Software\Microsoft\7-Zip'
    ValueName = '' # Ideally without this property
    RemoveExcessValueNames = @(
        'FastCompression',
        'HighValue'
    )
    DependsOn = @(
        '[Registry]Test_FastCompression',
        '[Registry]Test_HighValue'
    )
}

However, that's not very DRY. :unamused:

VertigoRay avatar Apr 16 '18 17:04 VertigoRay

This is being worked on in PR #64.

johlju avatar Apr 30 '18 12:04 johlju