diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c692888..51a23ab 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -54,6 +54,6 @@ jobs:
cache-version: 2
bundler: '2.1.0'
- name: Validate
- run: bundle exec rake rubocop syntax lint metadata_lint
+ run: bundle exec rake syntax lint metadata_lint
- name: Run tests
run: bundle exec rake parallel_spec
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94bb642..29d64ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,26 @@
All notable changes to this project will be documented in this file.
+## Release 0.2.1
+
+**Features**
+
+**Bugfixes**
+- Correct references in readme
+
+**Known Issues**
+
+
+## Release 0.2.0
+
+**Features**
+- Enhance user management including local Administrator account
+
+**Bugfixes**
+
+**Known Issues**
+
+
## Release 0.1.1
**Features**
diff --git a/README.md b/README.md
index 4a3f401..f9bdec5 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,10 @@ Other Windows 10 / 11 parameters include:
- cis_exclude_rules
- catalog_no_cache
- clear_temp_files
+- enable_administrator
+- purge_unmanaged_users
- performance_powerscheme
+- enable_remote_desktop
### Defence in-depth
@@ -67,12 +70,12 @@ To use this module, `include cis_security_hardening_windows` in your Node Classi
**At minimum, the following hiera must be provided** to the module:
#### Windows 10 / 11:
-- `cis_security_hardening_windows::windows::logon_banner` (string)
-- `cis_security_hardening_windows::windows::logon_message` (string)
-- `cis_security_hardening_windows::windows::disabled_administrator_newname` (string)
-- `cis_security_hardening_windows::windows::disabled_administrator_newpassword` (string)
-- `cis_security_hardening_windows::windows::disabled_guest_newname` (string)
-- `cis_security_hardening_windows::windows::users` (hash) is required as built-in administrator will be disabled
+- `cis_security_hardening_windows::logon_banner` (string)
+- `cis_security_hardening_windows::logon_message` (string)
+- `cis_security_hardening_windows::disabled_administrator_newname` (string)
+- `cis_security_hardening_windows::disabled_administrator_newpassword` (string)
+- `cis_security_hardening_windows::disabled_guest_newname` (string)
+- `cis_security_hardening_windows::users` (hash) is required as built-in administrator will be disabled
@@ -86,26 +89,26 @@ See example minimum hiera data [here](data/minimum.yaml)
- Comments in module hiera identify the objective of each setting however CIS reference numbers are not shown as they are subject to change
- Profile Type, Enforcement Level (1 or 2 (1+2)), BitLocker (BL), NextGen (NG) and HKCU policy inclusion are parameterised:
```yaml
- cis_security_hardening_windows::windows::cis_profile_type: 'domain'
- cis_security_hardening_windows::windows::cis_enforcement_level: 2
- cis_security_hardening_windows::windows::cis_include_bitlocker: true
- cis_security_hardening_windows::windows::cis_include_nextgen: true
- cis_security_hardening_windows::windows::cis_include_hkcu: true
+ cis_security_hardening_windows:cis_profile_type: 'domain'
+ cis_security_hardening_windows::cis_enforcement_level: 2
+ cis_security_hardening_windows::cis_include_bitlocker: true
+ cis_security_hardening_windows::cis_include_nextgen: true
+ cis_security_hardening_windows::cis_include_hkcu: true
```
- A reference list of rules enforced via the system registry is in the hiera folder for each Windows version, eg [here](data/windows/11/cis_include_rules.txt). Note that some additional rules are applied by Local Security Policy and Audit Policy resources however.
- Individual controls can be overridden by any of the following methods:
- - creating a optional hiera **array** for `cis_security_hardening_windows::windows::cis_exclude_rules` containing rule titles to be subtracted from the default included hashes (note however that some rules are enforced by the local_security_policy or windows_firewall modules):
+ - creating a optional hiera **array** for `cis_security_hardening_windows::cis_exclude_rules` containing rule titles to be subtracted from the default included hashes (note however that some rules are enforced by the local_security_policy or windows_firewall modules):
```yaml
- cis_security_hardening_windows::windows::cis_exclude_rules:
+ cis_security_hardening_windows::cis_exclude_rules:
- "(L1) Ensure 'Allow users to enable online speech recognition services is set to 'Disabled'"
- "(L1) Ensure 'Configure Solicited Remote Assistance' is set to 'Disabled'"
```
- creating a hiera hash containing registry keys with different values at a higher precedence (eg domain or node) and titled any of:
```yaml
- cis_security_hardening_windows::windows::cis_level_1 (or windows_standalone)
- cis_security_hardening_windows::windows::cis_level_2 (or windows_standalone)
- cis_security_hardening_windows::windows::cis_bitlocker (or windows_standalone)
- cis_security_hardening_windows::windows::cis_nextgen (or windows_standalone)
+ cis_security_hardening_windows::cis_level_1 (or windows_standalone)
+ cis_security_hardening_windows::cis_level_2 (or windows_standalone)
+ cis_security_hardening_windows::cis_bitlocker (or windows_standalone)
+ cis_security_hardening_windows::cis_nextgen (or windows_standalone)
```
- other methods such as resource collectors to override registry key values if wrapping this module into your own classes
diff --git a/REFERENCE.md b/REFERENCE.md
index 68d25eb..2d2d09e 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -7,8 +7,6 @@
### Classes
* [`cis_security_hardening_windows`](#cis_security_hardening_windows): Windows main class. The entry point with most parameters processed here. It applies CIS hardening
-* [`cis_security_hardening_windows::cis`](#cis_security_hardening_windows--cis): Windows cis class. It is called from the cis_security_hardening_windows class. Params are derived from in-module hiera and can be excluded.
-* [`cis_security_hardening_windows::remote_desktop`](#cis_security_hardening_windows--remote_desktop): Windows remote_desktop class. It is called from the cis_security_hardening_windows class when $allow_remote_desktop is true.
## Classes
@@ -30,6 +28,7 @@ include cis_security_hardening_windows
The following parameters are available in the `cis_security_hardening_windows` class:
* [`users`](#-cis_security_hardening_windows--users)
+* [`purge_unmanaged_users`](#-cis_security_hardening_windows--purge_unmanaged_users)
* [`cis_profile_type`](#-cis_security_hardening_windows--cis_profile_type)
* [`cis_enforcement_level`](#-cis_security_hardening_windows--cis_enforcement_level)
* [`cis_include_bitlocker`](#-cis_security_hardening_windows--cis_include_bitlocker)
@@ -37,6 +36,7 @@ The following parameters are available in the `cis_security_hardening_windows` c
* [`cis_exclude_rules`](#-cis_security_hardening_windows--cis_exclude_rules)
* [`cis_include_hkcu`](#-cis_security_hardening_windows--cis_include_hkcu)
* [`misc_registry`](#-cis_security_hardening_windows--misc_registry)
+* [`enable_administrator`](#-cis_security_hardening_windows--enable_administrator)
* [`enable_remote_desktop`](#-cis_security_hardening_windows--enable_remote_desktop)
* [`trusted_rdp_subnets`](#-cis_security_hardening_windows--trusted_rdp_subnets)
* [`remote_local_accounts`](#-cis_security_hardening_windows--remote_local_accounts)
@@ -53,6 +53,14 @@ Any users to create
Default value: `lookup( 'users', Hash, 'deep', {})`
+##### `purge_unmanaged_users`
+
+Data type: `Boolean`
+
+If unmanaged users should be purged. Requires users hash to be defined
+
+Default value: `lookup( 'purge_unmanaged_users', Boolean, undef, false )`
+
##### `cis_profile_type`
Data type: `Enum['domain', 'standalone']`
@@ -109,6 +117,14 @@ Lookup of misc registry items to apply. Currently sets Puppet logging to event
Default value: `lookup( 'misc_registry', Hash, 'deep', {})`
+##### `enable_administrator`
+
+Data type: `Boolean`
+
+If the local adminsitrator account is enabled. Note that account must be renamed if enabled or not
+
+Default value: `lookup( 'enable_administrator', Boolean, undef, false )`
+
##### `enable_remote_desktop`
Data type: `Boolean`
@@ -165,93 +181,3 @@ Do not cache the puppet catalog on disk, as passwords and other values are in pl
Default value: `lookup( 'catalog_no_cache', Boolean, undef, false )`
-### `cis_security_hardening_windows::cis`
-
-Windows cis class. It is called from the cis_security_hardening_windows class. Params are derived from in-module hiera and can be excluded.
-
-#### Examples
-
-##### Declaring the class
-
-```puppet
-include cis_security_hardening_windows
-```
-
-#### Parameters
-
-The following parameters are available in the `cis_security_hardening_windows::cis` class:
-
-* [`cis_profile_type`](#-cis_security_hardening_windows--cis--cis_profile_type)
-* [`cis_enforcement_level`](#-cis_security_hardening_windows--cis--cis_enforcement_level)
-* [`cis_include_bitlocker`](#-cis_security_hardening_windows--cis--cis_include_bitlocker)
-* [`cis_include_nextgen`](#-cis_security_hardening_windows--cis--cis_include_nextgen)
-* [`cis_exclude_rules`](#-cis_security_hardening_windows--cis--cis_exclude_rules)
-* [`cis_include_hkcu`](#-cis_security_hardening_windows--cis--cis_include_hkcu)
-
-##### `cis_profile_type`
-
-Data type: `Enum['domain', 'standalone']`
-
-Apply domain or standalone CIS benchmark
-
-##### `cis_enforcement_level`
-
-Data type: `Integer[1, 2]`
-
-CIS level to apply. Level 2 includes level 1
-
-##### `cis_include_bitlocker`
-
-Data type: `Boolean`
-
-If cis bitlocker rules should be included
-
-##### `cis_include_nextgen`
-
-Data type: `Boolean`
-
-If cis nextgen rules should be included
-
-##### `cis_exclude_rules`
-
-Data type: `Hash`
-
-Lookup of optional array for cis_exclude_rules (to opt out of included rules)
-
-##### `cis_include_hkcu`
-
-Data type: `Boolean`
-
-If true, lgpo is used to import group policy objects for HKCU as puppetlabs/registry cannot apply them
-
-### `cis_security_hardening_windows::remote_desktop`
-
-Windows remote_desktop class. It is called from the cis_security_hardening_windows class when $allow_remote_desktop is true.
-
-#### Examples
-
-##### Declaring the class
-
-```puppet
-include cis_security_hardening_windows
-```
-
-#### Parameters
-
-The following parameters are available in the `cis_security_hardening_windows::remote_desktop` class:
-
-* [`trusted_rdp_subnets`](#-cis_security_hardening_windows--remote_desktop--trusted_rdp_subnets)
-* [`remote_local_accounts`](#-cis_security_hardening_windows--remote_desktop--remote_local_accounts)
-
-##### `trusted_rdp_subnets`
-
-Data type: `Array`
-
-Trusted subnets for inbound rdp connections for firewall rules. Undef will be converted to 'any'
-
-##### `remote_local_accounts`
-
-Data type: `Boolean`
-
-If local accounts are permitted to connect remotely. Required if not domain joined
-
diff --git a/data/windows/misc.yaml b/data/windows/misc.yaml
index a7a733d..857962a 100644
--- a/data/windows/misc.yaml
+++ b/data/windows/misc.yaml
@@ -9,14 +9,6 @@ cis_security_hardening_windows::misc_registry:
data: 'C:\Program Files\Puppet Labs\Puppet\puppet\bin\puppetres.dll'
type: 'expand'
-# Turn on Controlled Folder Acess
-# 'HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Windows Defender Exploit Guard\Controlled Folder Access\EnableControlledFolderAccess':
-# ensure: present
-
-# Turn on Core Isolation
-# 'HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity\Enabled':
-# ensure: present
-
## ----------- from STIG ----------- ##
# The Server Message Block (SMB) v1 protocol must be disabled on the SMB client
diff --git a/data/windows/secpol.yaml b/data/windows/secpol.yaml
index fdf87c5..b248169 100644
--- a/data/windows/secpol.yaml
+++ b/data/windows/secpol.yaml
@@ -11,10 +11,10 @@ cis_security_hardening_windows::cis_secpol_level_1:
'Accounts: Rename guest account':
policy_value: '"%{lookup("cis_security_hardening_windows::disabled_guest_newname")}"'
-## This was removed in CIS 22H2 v2.0.0, however leaving in place as it can be overridden
- "(L1) Ensure 'Accounts: Administrator account status' is set to 'Disabled'":
- 'Accounts: Administrator account status':
- policy_value: '0'
+## This was removed in CIS 22H2 v2.0.0, can now be overridden in hiera
+# "(L1) Ensure 'Accounts: Administrator account status' is set to 'Disabled'":
+# 'Accounts: Administrator account status':
+# policy_value: '0'
"(L1) Ensure 'Accounts: Guest account status' is set to 'Disabled'":
'Accounts: Guest account status':
diff --git a/data/windows/users.yaml b/data/windows/users.yaml
index d4899e0..356e291 100644
--- a/data/windows/users.yaml
+++ b/data/windows/users.yaml
@@ -1,5 +1,5 @@
---
-cis_security_hardening_windows::users:
+cis_security_hardening_windows::system_users:
# comments are only required for matching builtin accounts
# passwords should be unique and not defaulted
# groups should be individaully managed and not defaulted
diff --git a/manifests/cis.pp b/manifests/cis.pp
index f98b692..638f18b 100644
--- a/manifests/cis.pp
+++ b/manifests/cis.pp
@@ -1,5 +1,4 @@
-#
-# Windows cis class. It is called from the cis_security_hardening_windows class. Params are derived from in-module hiera and can be excluded.
+## Windows cis class. It is called from the cis_security_hardening_windows class. Params are derived from in-module hiera and can be excluded.
#
# @example Declaring the class
# include cis_security_hardening_windows
diff --git a/manifests/init.pp b/manifests/init.pp
index a1ae67a..89e9913 100644
--- a/manifests/init.pp
+++ b/manifests/init.pp
@@ -1,12 +1,11 @@
-#
-# Windows main class. The entry point with most parameters processed here.
+## Windows main class. The entry point with most parameters processed here.
# It applies CIS hardening
#
# @example Declaring the class
# include cis_security_hardening_windows
#
-#
# @param [Hash] users Any users to create
+# @param [Boolean] purge_unmanaged_users If unmanaged users should be purged. Requires users hash to be defined
# @param [Enum['domain', 'standalone']] cis_profile_type Apply domain or standalone CIS benchmark
# @param [Integer[1, 2]] cis_enforcement_level CIS level to apply. Level 2 includes level 1
# @param [Boolean] cis_include_bitlocker If cis bitlocker rules should be included
@@ -14,6 +13,7 @@
# @param [Hash] cis_exclude_rules Lookup of optional hash for cis_exclude_rules (to opt out of included rules)
# @param [Boolean] cis_include_hkcu If true, CIS defined local group policy objects are copied in for users as puppetlabs/registry cannot apply HKCU
# @param [Hash] misc_registry Lookup of misc registry items to apply. Currently sets Puppet logging to event viewer and disables SMB1
+# @param [Boolean] enable_administrator If the local adminsitrator account is enabled. Note that account must be renamed if enabled or not
# @param [Boolean] enable_remote_desktop If true the RDP service will be enabled and firewall rule created (false)
# @param [Array] trusted_rdp_subnets Trusted subnets for inbound rdp connections for firewall rules. Undef will be converted to 'any'
# @param [Boolean] remote_local_accounts If true and RDP is enabled, this allows local user accounts to connect remotely. Required if not domain joined (true)
@@ -26,6 +26,7 @@
# Variable ( 'Name', Type, Merge, Default )
# --------------------------------------------------------------------------------------------------------------
$users = lookup( 'users', Hash, 'deep', {}),
+ $purge_unmanaged_users = lookup( 'purge_unmanaged_users', Boolean, undef, false ),
$cis_profile_type = lookup( 'cis_profile_type', Enum['domain', 'standalone'], undef, 'domain' ),
$cis_enforcement_level = lookup( 'cis_enforcement_level', Integer[1, 2], undef, 2 ),
$cis_include_bitlocker = lookup( 'cis_include_bitlocker', Boolean, undef, true ),
@@ -33,6 +34,7 @@
$cis_exclude_rules = lookup( 'cis_exclude_rules', Array, 'deep', []),
$cis_include_hkcu = lookup( 'cis_include_hkcu', Boolean, undef, true ),
$misc_registry = lookup( 'misc_registry', Hash, 'deep', {}),
+ $enable_administrator = lookup( 'enable_administrator', Boolean, undef, false ),
$enable_remote_desktop = lookup( 'enable_remote_desktop', Boolean, undef, false ),
$trusted_rdp_subnets = lookup( 'trusted_rdp_subnets', Array, undef, []),
$remote_local_accounts = lookup( 'remote_local_accounts', Boolean, undef, true ),
@@ -46,11 +48,21 @@
fail("Your windows release ${facts['windows']['release']} is not yet supported")
}
- # Fail if users not defined
- if !$users {
+ # Fail if administrator disabled and users not defined
+ if !$enable_administrator and $users.empty {
fail('At least 1 user must be defined as Administrator will be disabled.')
}
+ # Fail if purge_unmanaged_users enabled and users not defined
+ if $purge_unmanaged_users and $users.empty {
+ fail('You cannot purge unmanaged users without defining a users hash to manage users.')
+ }
+
+ # Ensure that the local_security_policy for local Administrator is set
+ local_security_policy { 'Accounts: Administrator account status':
+ policy_value => $enable_administrator ? { true => 1, default => 0, } #lint:ignore:selector_inside_resource
+ }
+
# Configure Remote Desktop agent if true
if $enable_remote_desktop {
class { 'cis_security_hardening_windows::remote_desktop':
@@ -92,10 +104,16 @@
}
}
- # User setting & purge (with defaults). Puppet 'unless_system_user' detection is incomplete in windows, so system users are defined in module hiera
# Users need to be created after secpol has run (within CIS class) due to issue with renaming administrator (can only be done once)
- resources { 'user': purge => true, unless_system_user => true }
- $users.each |String $key, $value| {
+ # Puppet 'unless_system_user' detection is incomplete in windows, so system_users are defined in module hiera
+ if $purge_unmanaged_users {
+ $users_real = $users + lookup(cis_security_hardening_windows::system_users)
+ } else {
+ $users_real = $users
+ }
+
+ resources { 'user': purge => $purge_unmanaged_users, unless_system_user => $purge_unmanaged_users }
+ $users_real.each |String $key, $value| {
user { $key:
* => $value,
membership => 'inclusive',
@@ -103,7 +121,7 @@
}
if $catalog_no_cache {
- # Delete the puppet catalog if it exists. This should only occur on the first run as caching is unset by the following ini_setting
+ # Delete the puppet catalog if it exists. This should only occur until a service restart as caching is unset by the following ini_setting
tidy { 'delete puppet catalog':
path => 'C:/ProgramData/PuppetLabs/puppet/cache/client_data/catalog',
recurse => 1,
diff --git a/metadata.json b/metadata.json
index dece3d0..b2df0aa 100644
--- a/metadata.json
+++ b/metadata.json
@@ -1,6 +1,6 @@
{
"name": "canihavethisone-cis_security_hardening_windows",
- "version": "0.1.1",
+ "version": "0.2.1",
"author": "canihavethisone",
"summary": "Harden Windows 10 & 11 to CIS standards",
"license": "Apache-2.0",
@@ -33,7 +33,7 @@
},
{
"name": "puppetlabs-stdlib",
- "version_requirement": ">= 9.5.0 < 10.0.0"
+ "version_requirement": ">= 9.6.0 < 10.0.0"
},
{
"name": "webalex-windows_firewall",
diff --git a/spec/classes/cis_security_hardening_windows_spec.rb b/spec/classes/cis_security_hardening_windows_spec.rb
index b37716f..0cff6d5 100644
--- a/spec/classes/cis_security_hardening_windows_spec.rb
+++ b/spec/classes/cis_security_hardening_windows_spec.rb
@@ -31,7 +31,7 @@
describe 'With defaults' do
# Write out catalogue
- # it { File.write('cis_security_hardening_windows_catalog_dump.json', JSON.pretty_generate(catalogue.to_resource)) }
+ # it { File.write('cis_security_hardening_windows_defaults_catalog_dump.json', JSON.pretty_generate(catalogue.to_resource)) }
# compile with deps & create class
# it { pp catalogue.resources }
@@ -41,13 +41,20 @@
it { is_expected.not_to contain_class('cis_security_hardening_windows::remote_desktop') }
## Users
- users = ['Administrator', 'DefaultAccount', 'Guest', 'WDAGUtilityAccount', 'User']
+ users = ['Administrator', 'DefaultAccount', 'Guest', 'WDAGUtilityAccount']
users.each do |name|
it do
- is_expected.to contain_user(name)
+ is_expected.not_to contain_user(name)
end
end
+ # Local Security Policy
+ it do
+ is_expected.to contain_local_security_policy('Accounts: Administrator account status').with(
+ 'policy_value' => '0',
+ )
+ end
+
# Resources
it do
is_expected.to contain_resources('user')
@@ -71,10 +78,12 @@
let(:params) do
{ 'catalog_no_cache' => true,
'performance_powerscheme' => true,
- 'clear_temp_files' => true }
+ 'clear_temp_files' => true,
+ 'enable_administrator' => true,
+ 'purge_unmanaged_users' => true, }
end
# Write out catalogue
- # it { File.write('cis_security_hardening_windows_catalog_dump.json', JSON.pretty_generate(catalogue.to_resource)) }
+ # it { File.write('cis_security_hardening_windows_misc_catalog_dump.json', JSON.pretty_generate(catalogue.to_resource)) }
# compile with deps & create class
# it { pp catalogue.resources }
@@ -91,6 +100,13 @@
end
end
+ # Local Security Policy
+ it do
+ is_expected.to contain_local_security_policy('Accounts: Administrator account status').with(
+ 'policy_value' => '1',
+ )
+ end
+
## Execs
execs = ['clear_user_temp', 'clear_windows_temp', 'grouppolicy dir attributes', 'power_scheme_high']
execs.each do |name|
@@ -240,7 +256,7 @@
end
end
- ## Secpol
+ ## Local Security Policy
# List of YAML files to load, dynamically using the Windows release version
yaml_file = YAML.load_file('./data/windows/secpol.yaml')
@@ -274,7 +290,7 @@
end
end
- ## Auditpol
+ ## Audit Policy
# List of YAML files to load, dynamically using the Windows release version
yaml_file = YAML.load_file('./data/windows/auditpol.yaml')