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')