dsccommunity / SecurityPolicyDsc

A wrapper around secedit.exe to configure local security policies
MIT License
177 stars 53 forks source link

SecurityPolicyDsc support for Guest Configuration #173

Open bdanse opened 2 years ago

bdanse commented 2 years ago

As discussed here.

Taking Gcpol into account. Though a bit counter intuitive with regards to DSC resources. A filter option could be a nice addition to include filter capability in the following modules:

The idea is based on what is already available in AzureWindowsBaseline gcpol package. found here

The package contains a module with 3 resources:

These resources are binary but, from what I understand, can not be reused without the complete module. I checked Microsoft, the module will not be made publicly available. A snippet of all 3 resources are included here:

instance of ASM_Registry as $ASM_Registry24ref { RuleId = "{F9C16B7A-4F7C-4947-A2BE-F47483DD2AC7}"; AzId = "AZ-WIN-00120"; BaselineId = "{982a79a8-1c46-4fdf-8cfd-60afedf7ad96}"; OriginalBaselineId = "{9c2bc3d1-8668-48e5-ac5f-281718d52174}"; Name = "Devices: Allow undock without having to log on"; Severity = "Informational"; Hive = "HKEY_LOCAL_MACHINE"; Path = "Software\Microsoft\Windows\CurrentVersion\Policies\System"; Value = "UndockWithoutLogon"; Type = "REG_DWORD"; ExpectedValue = "0"; RemediateValue = "0"; Remediate = true; AnalyzeOperation = "EQUALS"; ServerTypeFilter = "ServerType = [Domain Member, Workgroup Member]"; OSFilter = "OSVersion = [WS2008, WS2008R2, WS2012, WS2012R2, WS2016]"; Enabled = true; ResourceID = "Devices: Allow undock without having to log on"; ModuleName = "AzureOSBaseline"; ModuleVersion = "1.0"; ConfigurationName = "AzureOSBaseline"; };

instance of ASM_SecurityPolicy as $ASM_SecurityPolicy156ref { RuleId = "{a30f6d7d-f3dc-442c-8a1f-921123c6250c}"; AzId = "AZ-WIN-00184"; BaselineId = "{982a79a8-1c46-4fdf-8cfd-60afedf7ad96}"; OriginalBaselineId = "{9c2bc3d1-8668-48e5-ac5f-281718d52174}"; Name = "Bypass traverse checking"; Severity = "Critical"; Category = "Privilege Rights"; Key = "SeChangeNotifyPrivilege"; Type = "USERRIGHTS"; ExpectedValue = "Administrators, Authenticated Users, Backup Operators, Local Service, Network Service"; RemediateValue = "Administrators, Authenticated Users, Backup Operators, Local Service, Network Service"; Remediate = true; AnalyzeOperation = "LESSOREQUAL"; ServerTypeFilter = "ServerType = [Domain Member, Workgroup Member]"; OSFilter = "OSVersion = [WS2008, WS2008R2, WS2012, WS2012R2, WS2016]"; Enabled = true; ResourceID = "Bypass traverse checking"; ModuleName = "AzureOSBaseline"; ModuleVersion = "1.0"; ConfigurationName = "AzureOSBaseline"; };

instance of ASM_AuditPolicy as $ASM_AuditPolicy20ref { RuleId = "{7e4d9fe1-eb3f-49ac-bb5b-d417df7e6d6c}"; AzId = "CCE-37856-2"; BaselineId = "{982a79a8-1c46-4fdf-8cfd-60afedf7ad96}"; OriginalBaselineId = "{9c2bc3d1-8668-48e5-ac5f-281718d52174}"; Name = "Audit User Account Management"; Severity = "Critical"; Subcategory = "{0CCE9235-69AE-11D9-BED3-505054503030}"; ExpectedValue = "Success and Failure"; RemediateValue = "Success and Failure"; Remediate = true; AnalyzeOperation = "EQUALS"; ServerTypeFilter = "ServerType = [Domain Controller, Domain Member, Workgroup Member]"; OSFilter = "OSVersion = [WS2008, WS2008R2, WS2012, WS2012R2, WS2016]"; Enabled = true; ResourceID = "Audit User Account Management"; ModuleName = "AzureOSBaseline"; ModuleVersion = "1.0"; ConfigurationName = "AzureOSBaseline"; };

I would like to see what would be a viable solution to get these capability. Allowing a single package to service every OS, ServerType with a single policy. Add filter options to already existing resources or create a new module for GC on par with the Microsoft ASM resources.

gaelcolas commented 2 years ago

IMO, the best way is to make the DSC resources "clean" (no filter), because this does not translate well in the DSC configuration paradigm (we ought to define a state to converge to, specific to a node or configuration name. No ifs and buts.) GC is slightly different, but the base should be the same, so we should avoid repeating code. Best is to convert those MOF-based resource to class-based resource, and then extend via inheritance for GC resources. Here's this pattern in action: https://github.com/SynEdgy/nxtools/blob/main/source/Classes/ In this example the array of strings properties (not supported by GC) are replaced by single string with a value separator.

bdanse commented 2 years ago

I have been digging into the classes you mentioned. and LocalGroup contains the exact pattern you advice. To get a clear understanding of how it all flows back and forth between both classes, i have created a simple snippet. Even left out DSC parts.


class DscClass {

    [String] $Name
    [string] $Value

    DscClass ([string]$InputName, [string]$InputValue) {
        $this.Name = $InputName
        $this.Value = $InputValue
    }

    [DscClass] Get() {
        return $this
    }

}

class GC_DscClass : DscClass {

    GC_DscClass() {
    }

    GC_DscClass ([DscClass]$DscClass)  {
        $this.Name = $DscClass.Name
        $this.Value = $DscClass.Value
    }

}

$base = [DscClass]::new('Naam', 'Waarde')
$base
$derived = [GC_DscClass]::New($base)
$derived

My assumption is my order is wrong. Since the GC configuration will invoke GC_DscClass first. but not sure where the starting point within the GC_DscClass would be. Where does GC_DscClass ([DscClass]$DscClass) gets its value from or is it DSC specific?

gaelcolas commented 2 years ago

I'm not sure I understand your question, so apologies in advance if my answer doesn't help.

In this pattern, the DSC resource is a simple resource as we're accustomed to. For GC we need to extend/change the resource to support a slightly different paradigm more specialised to Azure Policy Guest Configuration. Instead of duplicating code, we want to base the GC resource on the same behaviour, but add properties such as OS Filter in your case or replace properties using array of strings with properties using a string of coma separated values.

As far as the objects are concerned once you have created them by 'instantiating a class', they just have all the properties and method of their inheritance 'chain'. In your example above, you don't change anything from the base class with your GC class, thus don't provide any value.

Also understand that there are no back and forth going on. It's only some DSC specific implementation that mandates the DSC (and GC) classes to have the get, set, test methods, and they must return respectively an object of type [class name], [void], [bool].

I think your confusion comes from my example which has one more 'layer' to it.

The GC_linuxGroup class, extends the nxGroup DSC resource.

The third class [nxLocalGroup]() is unrelated to DSC or GC resources, it's used by the functions of the module.

So to your example (on my phone so did not test):


class DscClass {

    [String] $Name
    [string] $Value

    DscClass ([string]$InputName, [string]$InputValue) {
        $this.Name = $InputName
        $this.Value = $InputValue
    }

    [DscClass] Get() {
        return $this
    }

    [Bool] Test().{
        $true
    }

    [DscClass] Get() {
        $this
    }
}

class GC_DscClass : DscClass {

    [String] $OtherProperty = 'someValue'

    GC_DscClass() {
    }

    GC_DscClass([string]$InputName, [string]$InputValue) {
        $this.Name = $InputName
        $this.Value = $InputValue
    }

    [GC_DscClass] Get() {
        $this
    }
}

$base = [DscClass]::new('Naam', 'Waarde')
$derived = [GC_DscClass]::New('Naam', 'Waarde')

# derived class has parents' methods
$derived.Test()

# derived class has overridden the get method to return a different type
$base.Get().GetType().ToString()
$derived.Get().GetType().ToString()

# derived class has an extra property
$derived.OtherProperty

Basically, without rewriting code, I created a new class with same functionality that I could ter and extend.

Depending on my use case (i.e. DSC or GC), I can use either. Sometimes GC and DSC can use the same class resource so this may not be needed and the configuration script would just use the DSC resource to compile the MOF. This pattern is only useful when there's a difference between the DSC usage and the GC usage. Best example is that GC does not (yet) support [string[]] property types to be set, so we extend and use a [string] and we split this in the GC class to set the [String[]] property of the base class...

I hope that helps.

bdanse commented 2 years ago

Thank you for you patience and explanation.

I have made a little concept module. here

2 classes : DscClass : works as one would expect from a DSC resource. GC_DscClass: inherits DscClass and will parse a yml string of allowed OS's and compares the current OS label to this list.

GC_DscClass Get shall only instantiate DscClass Get if conditions are met. if not provide the reason. GC_DscClass Test will return $true if the Filter conditions are not met. If conditions are met it will return the the result of the actual DscClass validation.