diff --git a/CHANGELOG.md b/CHANGELOG.md
index a05155c..e49da07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file.
+## Release 1.0.0
+
+**Features**
+- Rename disabled_administrator references and hiera to administrator as enabling is configurable
+- Update dependency versions
+
+**Bugfixes**
+- Remove references to legacy facts that were breaking testing
+- Improve Puppet 8 compatability
+
+
## Release 0.2.3
**Features**
diff --git a/README.md b/README.md
index 23e6864..0c30e3f 100644
--- a/README.md
+++ b/README.md
@@ -37,8 +37,8 @@ This module applies CIS benchmark hardening to:
Additional resources are also defined, including:
### Windows 10 / 11
-- remote desktop
- users
+- remote desktop
- firewall (limited)
@@ -59,7 +59,7 @@ Other Windows 10 / 11 parameters include:
### Defence in-depth
This module takes a defence in-depth approach, with the following built-in functions:
-- undefined users are purged (except system users)
+- undefined users can be optionally purged (except system users)
- where CIS recommendations have more than 1 acceptable setting, the more stringent is used
@@ -79,7 +79,7 @@ To use this module, `include cis_security_hardening_windows` in your Node Classi
-See example minimum hiera data [here](data/minimum.yaml)
+See example minimum hiera data [here](spec/fixtures/data/minimum.yaml)
## CIS Enforcement Levels
diff --git a/REFERENCE.md b/REFERENCE.md
index 2d2d09e..3c9f0c6 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -7,6 +7,8 @@
### 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
@@ -181,3 +183,93 @@ 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/minimum.yaml b/data/minimum.yaml
deleted file mode 100644
index 3ea4e94..0000000
--- a/data/minimum.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
----
-# Settings shared across Windows releases
-cis_security_hardening_windows::logon_banner: 'notice and consent banner'
-cis_security_hardening_windows::logon_message: 'all activities performed on this system will be monitored.'
-
-cis_security_hardening_windows::administrator_newname: 'NewAdministratorName'
-cis_security_hardening_windows::administrator_newpassword: 'Compl3xP4s$word'
-cis_security_hardening_windows::disabled_guest_newname: 'NewGuestName'
-
-cis_security_hardening_windows::users:
- 'User':
- groups: ['Administrators']
- password: 'Password12345!'
diff --git a/metadata.json b/metadata.json
index 571f2d6..fc2d3c1 100644
--- a/metadata.json
+++ b/metadata.json
@@ -1,6 +1,6 @@
{
"name": "canihavethisone-cis_security_hardening_windows",
- "version": "0.2.3",
+ "version": "1.0.0",
"author": "canihavethisone",
"summary": "Harden Windows 10 & 11 to CIS standards",
"license": "Apache-2.0",
diff --git a/spec/acceptance/cis_security_hardening_windows_spec.rb b/spec/acceptance/cis_security_hardening_windows_spec.rb
index c84bf9e..979bf0e 100644
--- a/spec/acceptance/cis_security_hardening_windows_spec.rb
+++ b/spec/acceptance/cis_security_hardening_windows_spec.rb
@@ -34,6 +34,7 @@
if File.file?("#{PROJECT_ROOT}/spec/acceptance/overrides.yaml")
print_stage('Copying environment specific hiera overrides from spec/acceptance/overrides.yaml to master')
scp_to(master, "#{PROJECT_ROOT}/spec/acceptance/overrides.yaml", "/etc/puppetlabs/code/environments/#{ENVIRONMENT}/data/overrides.yaml")
+ on(master, "echo -e \" - name: 'Override hiera'\\n path: 'overrides.yaml'\" >> /etc/puppetlabs/code/environments/#{ENVIRONMENT}/hiera.yaml")
end
# Chown and chmod testing environment
diff --git a/spec/acceptance/overrides.yaml b/spec/acceptance/overrides.yaml
index 66749d2..1e3be84 100644
--- a/spec/acceptance/overrides.yaml
+++ b/spec/acceptance/overrides.yaml
@@ -1,36 +1,30 @@
---
-# Overrides used for acceptance testing. Set according to your environment
-
-# This allows root user to ssh during acceptance tests
-cis_security_hardening_windows::enable_remote_desktop: true
-
-# Trusted subnets allowed in for RDP if enabled
-# cis_security_hardening_windows::trusted_rdp_subnets:
-# - 192.168.100.0/24
-# - 192.168.102.0/24
+## - This file provides minimum testing data for this module
# Banners must be specified
-cis_security_hardening_windows::logon_banner: 'notice and consent banner'
-cis_security_hardening_windows::logon_message: 'all activities performed on this system will be monitored.'
+cis_security_hardening_windows::logon_banner: 'notice and consent banner'
+cis_security_hardening_windows::logon_message: 'all activities performed on this system will be monitored.'
-# CIS recommend that the administrator and guest accounts are disabled and renamed
+# CIS recommend that the administrator and guest accounts are renamed and guest disabled
cis_security_hardening_windows::administrator_newname: 'NewAdministratorName'
cis_security_hardening_windows::administrator_newpassword: 'Compl3xP4s$word'
-cis_security_hardening_windows::disabled_guest_newname: 'NewGuestName'
+cis_security_hardening_windows::disabled_guest_newname: 'NewGuestName'
-# A user must be specified
+# A user must be specified as Administrator is disabled. Root is also present for acceptance test ssh connection
cis_security_hardening_windows::users:
'User':
- groups: ['Administrators']
- password: 'Password12345!'
- 'root':
- groups: ['Administrators']
- password: 'Password123'
+ groups: ['Administrators']
+ password: 'Password12345!'
+# 'root':
+# groups: ['Administrators']
+# password: 'Password123'
-# Some exclude rules to test that the optout works
+# Add some exclude rules to test that the optout works
cis_security_hardening_windows::cis_exclude_rules:
- "(L1) Ensure 'Accounts: Block Microsoft accounts' is set to 'Users can't add or log on with Microsoft accounts'" # registry
-# - "(L1) Ensure 'Accounts: Administrator account status' is set to 'Disabled'" # secpol
+
+# This allows root user to ssh during acceptance tests
+cis_security_hardening_windows::enable_remote_desktop: true
# Local accounts must be allowed remote access for acceptance tests, so reduce this to 'guests' only
cis_security_hardening_windows::cis_secpol:
diff --git a/spec/acceptance/shared_examples/windows_tests.rb.testing b/spec/acceptance/shared_examples/windows_tests.rb.paralleltesting
similarity index 97%
rename from spec/acceptance/shared_examples/windows_tests.rb.testing
rename to spec/acceptance/shared_examples/windows_tests.rb.paralleltesting
index 18e91d5..97ee2ed 100644
--- a/spec/acceptance/shared_examples/windows_tests.rb.testing
+++ b/spec/acceptance/shared_examples/windows_tests.rb.paralleltesting
@@ -1,113 +1,113 @@
-require 'parallel'
-
-# Define the number of threads to run in parallel
-MAX_PARALLEL = 5
-
-shared_examples 'windows tests' do |agent:, _agent_ip:|
- # Users tests
- describe user('user') do
- it { is_expected.to exist }
- end
-
- # Load registry YAML data
- registry_yaml_data = {}
- registry_yaml_files = Dir["./data/windows/#{agent['version']}/*.yaml"]
- registry_yaml_files.each { |file| registry_yaml_data.merge!(YAML.load_file(file)) }
-
- # Load exclusion YAML data
- exclude_yaml_data = YAML.load_file('./spec/acceptance/overrides.yaml')
-
- # Hash titles and combined data
- registry_hash_titles = [
- 'cis_security_hardening_windows::cis_level_1',
- 'cis_security_hardening_windows::cis_level_2',
- 'cis_security_hardening_windows::cis_nextgen',
- 'cis_security_hardening_windows::cis_bitlocker',
- '(cis_security_hardening_windows::cis_standalone_optout).to_h', # Standalone optouts should not be included but is allowed to verify its absence
- ]
- registry_combined_data = registry_hash_titles.map { |title| registry_yaml_data[title] || {} }.reduce(&:merge)
-
- # Exclude hash titles and combined data
- exclude_keys = exclude_yaml_data['cis_security_hardening_windows::cis_exclude_rules']
-
- # Remove exclude keys from registry combined data
- exclude_keys.each { |key| registry_combined_data.delete(key) }
-
- # Default properties
- default_properties = { 'type' => 'dword', 'data' => 1 }
- previous_title = nil
-
- # Some exclusions are required as acceptance tests require remote access to be enabled, and a few others
- exclusion_patterns = [
- %r{Named Pipes that can be accessed anonymously}, # Yet to manage paths within array in data
- %r{Remotely accessible registry paths and sub-paths}, # Yet to manage paths within array in data
- %r{Turn off background refresh of Group Policy is set}, # Is set to absent in hiera
- %r{Require user authentication for remote connections by using Network Level Authentication},
- %r{Remote Desktop Services \(TermService\)},
- %r{Allow users to connect remotely by using Remote Desktop Services},
- %r{Remote Desktop Services UserMode Port Redirector},
- ]
-
- # Iterate over combined data
- Parallel.map(registry_combined_data, in_threads: 10) do |title, hash|
- #registry_combined_data.each_in_parallel do |title, hash|
- # Skip the iteration if the title matches any pattern in the exclusion list due to remote requirements for testing and complex data values
- next if exclusion_patterns.any? { |pattern| title.match?(pattern) }
-
- #Parallel.map(hash, in_threads: 10) do |regkey, properties|
- hash.each_in_parallel do |regkey, properties|
- properties = default_properties.merge(properties)
- extracted_key, extracted_value = regkey.match(%r{^(.*)\\([^\\]*)$})&.captures
-
- # Check if properties['data'] is an array before processing
- if properties['data'].is_a?(Array)
- # Surround each component with single quotes and join with commas
- properties['data'] = properties['data'].map { |element| "'#{element}'" }.join(',')
- end
-
- # Ensure that properties['data'] is a string before proceeding or calling gsub
- properties['data'] = properties['data'].to_s unless properties['data'].is_a?(String)
-
- # Execute PowerShell script remotely
- powershell_script = <<-SCRIPT
- $registryPath = '#{extracted_key}'
- $registryKey = '#{extracted_value}'
- $expectedValue = '#{properties['data']}'
-
- try {
- $actualValue = (Get-Item "Registry::$registryPath").GetValue($registryKey)
- $success = ($actualValue -eq $expectedValue)
- if ($success) {
- Write-Output "Registry key: $registryKey with value: $actualValue matches expected value: $expectedValue"
- exit 0 # Set exit code to 0 for success
- } else {
- Write-Output "Registry key: $registryKey with value: $actualValue does not match expected value: $expectedValue"
- exit 1
- }
- } catch {
- Write-Output "Error occurred: $_"
- exit 1
- }
- SCRIPT
-
- execute_powershell_script_on(agent, powershell_script)
- end
- end
-
- # Networking - profile is private
- describe command('Get-NetConnectionProfile | select NetworkCategory -ExpandProperty NetworkCategory') do
- its(:stdout) { is_expected.to match(%r{Private}) }
- end
-
- # Secpol - renamed administrator disabled
- describe command('Get-LocalUser | Where-Object {$_.SID -like "S-1-5-21-*-500"} | Select -ExpandProperty Enabled') do
- its(:stdout) { is_expected.to match(%r{False}) }
- end
-
- # Exclude rules - ensure that exclusions in overrides are applying correctly
- # Registry - online accounts allowed
- # "(L1) Ensure 'Accounts: Block Microsoft accounts' is set to 'Users can't add or log on with Microsoft accounts'":
- describe windows_registry_key('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System') do
- it { is_expected.not_to have_property_value('NoConnectedUser', :type_dword, '3') }
- end
-end
+require 'parallel'
+
+# Define the number of threads to run in parallel
+MAX_PARALLEL = 5
+
+shared_examples 'windows tests' do |agent:, _agent_ip:|
+ # Users tests
+ describe user('user') do
+ it { is_expected.to exist }
+ end
+
+ # Load registry YAML data
+ registry_yaml_data = {}
+ registry_yaml_files = Dir["./data/windows/#{agent['version']}/*.yaml"]
+ registry_yaml_files.each { |file| registry_yaml_data.merge!(YAML.load_file(file)) }
+
+ # Load exclusion YAML data
+ exclude_yaml_data = YAML.load_file('./spec/acceptance/overrides.yaml')
+
+ # Hash titles and combined data
+ registry_hash_titles = [
+ 'cis_security_hardening_windows::cis_level_1',
+ 'cis_security_hardening_windows::cis_level_2',
+ 'cis_security_hardening_windows::cis_nextgen',
+ 'cis_security_hardening_windows::cis_bitlocker',
+ '(cis_security_hardening_windows::cis_standalone_optout).to_h', # Standalone optouts should not be included but is allowed to verify its absence
+ ]
+ registry_combined_data = registry_hash_titles.map { |title| registry_yaml_data[title] || {} }.reduce(&:merge)
+
+ # Exclude hash titles and combined data
+ exclude_keys = exclude_yaml_data['cis_security_hardening_windows::cis_exclude_rules']
+
+ # Remove exclude keys from registry combined data
+ exclude_keys.each { |key| registry_combined_data.delete(key) }
+
+ # Default properties
+ default_properties = { 'type' => 'dword', 'data' => 1 }
+ previous_title = nil
+
+ # Some exclusions are required as acceptance tests require remote access to be enabled, and a few others
+ exclusion_patterns = [
+ %r{Named Pipes that can be accessed anonymously}, # Yet to manage paths within array in data
+ %r{Remotely accessible registry paths and sub-paths}, # Yet to manage paths within array in data
+ %r{Turn off background refresh of Group Policy is set}, # Is set to absent in hiera
+ %r{Require user authentication for remote connections by using Network Level Authentication},
+ %r{Remote Desktop Services \(TermService\)},
+ %r{Allow users to connect remotely by using Remote Desktop Services},
+ %r{Remote Desktop Services UserMode Port Redirector},
+ ]
+
+ # Iterate over combined data
+ Parallel.map(registry_combined_data, in_threads: 10) do |title, hash|
+ #registry_combined_data.each_in_parallel do |title, hash|
+ # Skip the iteration if the title matches any pattern in the exclusion list due to remote requirements for testing and complex data values
+ next if exclusion_patterns.any? { |pattern| title.match?(pattern) }
+
+ #Parallel.map(hash, in_threads: 10) do |regkey, properties|
+ hash.each_in_parallel do |regkey, properties|
+ properties = default_properties.merge(properties)
+ extracted_key, extracted_value = regkey.match(%r{^(.*)\\([^\\]*)$})&.captures
+
+ # Check if properties['data'] is an array before processing
+ if properties['data'].is_a?(Array)
+ # Surround each component with single quotes and join with commas
+ properties['data'] = properties['data'].map { |element| "'#{element}'" }.join(',')
+ end
+
+ # Ensure that properties['data'] is a string before proceeding or calling gsub
+ properties['data'] = properties['data'].to_s unless properties['data'].is_a?(String)
+
+ # Execute PowerShell script remotely
+ powershell_script = <<-SCRIPT
+ $registryPath = '#{extracted_key}'
+ $registryKey = '#{extracted_value}'
+ $expectedValue = '#{properties['data']}'
+
+ try {
+ $actualValue = (Get-Item "Registry::$registryPath").GetValue($registryKey)
+ $success = ($actualValue -eq $expectedValue)
+ if ($success) {
+ Write-Output "Registry key: $registryKey with value: $actualValue matches expected value: $expectedValue"
+ exit 0 # Set exit code to 0 for success
+ } else {
+ Write-Output "Registry key: $registryKey with value: $actualValue does not match expected value: $expectedValue"
+ exit 1
+ }
+ } catch {
+ Write-Output "Error occurred: $_"
+ exit 1
+ }
+ SCRIPT
+
+ execute_powershell_script_on(agent, powershell_script)
+ end
+ end
+
+ # Networking - profile is private
+ describe command('Get-NetConnectionProfile | select NetworkCategory -ExpandProperty NetworkCategory') do
+ its(:stdout) { is_expected.to match(%r{Private}) }
+ end
+
+ # Secpol - renamed administrator disabled
+ describe command('Get-LocalUser | Where-Object {$_.SID -like "S-1-5-21-*-500"} | Select -ExpandProperty Enabled') do
+ its(:stdout) { is_expected.to match(%r{False}) }
+ end
+
+ # Exclude rules - ensure that exclusions in overrides are applying correctly
+ # Registry - online accounts allowed
+ # "(L1) Ensure 'Accounts: Block Microsoft accounts' is set to 'Users can't add or log on with Microsoft accounts'":
+ describe windows_registry_key('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System') do
+ it { is_expected.not_to have_property_value('NoConnectedUser', :type_dword, '3') }
+ end
+end
diff --git a/spec/classes/cis_security_hardening_windows_spec.rb b/spec/classes/cis_security_hardening_windows_spec.rb
index 0a3b6cd..a31d561 100644
--- a/spec/classes/cis_security_hardening_windows_spec.rb
+++ b/spec/classes/cis_security_hardening_windows_spec.rb
@@ -4,9 +4,8 @@
describe 'cis_security_hardening_windows' do
on_supported_os.each do |os, facts|
context "on #{os}" do
- # let(:facts) { facts.merge(testcase: 'spec', classname: 'cis_security_hardening_windows') }
- if facts[:osfamily] == 'windows'
- case facts[:operatingsystemrelease]
+ if facts[:os]['family'] == 'windows'
+ case facts[:os]['release']['major']
when '10'
let(:facts) do
facts.merge(
@@ -160,7 +159,7 @@
# Initialize an empty hash to store combined YAML data
combined_yaml_data = {}
# List of YAML files to load, dynamically using the Windows release version
- yaml_files = Dir["./data/windows/#{facts[:operatingsystemrelease]}/*.yaml"]
+ yaml_files = Dir["./data/windows/#{facts[:os]['release']['major']}/*.yaml"]
# Iterate over each YAML file and merge its data into the combined hash
yaml_files.each do |file|
diff --git a/spec/default_facts.yml b/spec/default_facts.yml
index f54acf9..5b210f7 100644
--- a/spec/default_facts.yml
+++ b/spec/default_facts.yml
@@ -18,11 +18,4 @@ networking:
dhcp: "192.168.0.1"
profile_state_fact: on
in_policy_fact: "BlockInbound"
-out_policy_fact: "BlockOutbound"
-
-# Values for custom facts
-networking_dns:
- nameserver: ['1.1.1.1','2.2.2.2']
-windows:
- release: '10'
-# display_version: "22H2"
+out_policy_fact: "AllowOutbound"
diff --git a/spec/fixtures/data/minimum.yaml b/spec/fixtures/data/minimum.yaml
new file mode 100644
index 0000000..cb5ceea
--- /dev/null
+++ b/spec/fixtures/data/minimum.yaml
@@ -0,0 +1,17 @@
+---
+## - This file provides minimum testing data for this module
+
+# Banners must be specified
+cis_security_hardening_windows::logon_banner: 'notice and consent banner'
+cis_security_hardening_windows::logon_message: 'all activities performed on this system will be monitored.'
+
+# CIS recommend that the administrator and guest accounts are renamed and guest disabled
+cis_security_hardening_windows::administrator_newname: 'NewAdministratorName'
+cis_security_hardening_windows::administrator_newpassword: 'Compl3xP4s$word'
+cis_security_hardening_windows::disabled_guest_newname: 'NewGuestName'
+
+# A user must be specified as Administrator is disabled
+cis_security_hardening_windows::users:
+ 'User':
+ groups: ['Administrators']
+ password: 'Password12345!'
diff --git a/spec/fixtures/data/spec.yaml b/spec/fixtures/data/spec.yaml
deleted file mode 100644
index 8031150..0000000
--- a/spec/fixtures/data/spec.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
----
-## - This file provides minumum testing data for this module
-
-# Settings shared across Windows releases
-cis_security_hardening_windows::logon_banner: 'notice and consent banner'
-cis_security_hardening_windows::logon_message: 'all activities performed on this system will be monitored.'
-
-# CIS recommend that the administrator and guest accounts are disabled and renamed
-cis_security_hardening_windows::administrator_newname: 'NewAdministratorName'
-cis_security_hardening_windows::administrator_newpassword: 'Compl3xP4s$word'
-cis_security_hardening_windows::disabled_guest_newname: 'NewGuestName'
-
-cis_security_hardening_windows::users:
- 'User':
- groups: ['Administrators']
- password: 'Password12345!'
diff --git a/spec/fixtures/hiera.yaml b/spec/fixtures/hiera.yaml
index ed8d552..c76bcd9 100644
--- a/spec/fixtures/hiera.yaml
+++ b/spec/fixtures/hiera.yaml
@@ -6,8 +6,8 @@ defaults:
hierarchy:
- - name: "testing hiera"
- path: "minimum.yaml"
+ - name: "Minimum testing hiera"
+ path: "../spec/fixtures/data/minimum.yaml"
- name: "Windows globbing"
glob: "windows/*.yaml"
diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb
index 0706fa4..9bbf13f 100644
--- a/spec/spec_helper_acceptance.rb
+++ b/spec/spec_helper_acceptance.rb
@@ -26,32 +26,17 @@
## Set unique environment variable if static-master, otherwise use production
CLASS = 'canihavethisone/cis_security_hardening_windows'.freeze
MASTER_IP = master.get_ip
-MASTER_NODE_NAME = master.node_name
+MASTER_FQDN = master.node_name
+PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+TEST_FILES = File.expand_path(File.join(File.dirname(__FILE__), 'acceptance', 'files'))
+DEPENDENCY_LIST = 'fixtures'.freeze
ENVIRONMENT = if master['hypervisor'] == 'none'
agents[0].hostname
else
'production'
end
-PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
-TEST_FILES = File.expand_path(File.join(File.dirname(__FILE__), 'acceptance', 'files'))
-DEPENDENCY_LIST = 'fixtures'.freeze
-HIERA_OVERRIDES = "---
-cis_security_hardening_windows::users:
- 'root':
- groups: ['Administrators']
- password: 'Password123'
-cis_security_hardening_windows::cis_secpol:
- 'Deny access to this computer from the network':
- policy_value: 'Guests'".freeze
-
-HIERA_YAML = "---
-version: 5
-hierarchy:
- - name: 'overrides'
- path: 'overrides.yaml'".freeze
-
-# Configuration
+## Configuration
CONFIG = {
puppet_agent_version: ENV['PUPPET_AGENT_VERSION'] || '7.27.0',
puppetserver_version: ENV['PUPPETSERVER_VERSION'] || '7.14.0',
@@ -69,19 +54,19 @@
## Print stage headings
def print_stage(h)
puts "\n\n"
- puts "\e[0;32m---------------------------------------------------------------------------------\e[0m"
+ puts "\e[0;32m-------------------------------------------------------------------------------------------\e[0m"
puts "\e[0;36m#{h}\e[0m"
- puts "\e[0;32m---------------------------------------------------------------------------------\e[0m"
+ puts "\e[0;32m-------------------------------------------------------------------------------------------\e[0m"
puts "\n"
end
-# As each dependency is installed from fixtures, add the latest version to an array (uses the 5th line of output so that only primary dependencies are written to metadata.json
+## As each dependency is installed from fixtures, add the latest version to an array (uses the 5th line of output so that only primary dependencies are written to metadata.json
def compile_dependency_versions(output)
dep_arr = output.lines[4]&.split(' ')
ALL_DEPS.push({ dep_name: dep_arr[1], dep_ver: dep_arr[2][9..-6] }) unless dep_arr.nil?
end
-# Update dependencies in metadata
+## Update dependencies in metadata
def write_metadata_dot_json(dependencies)
dep_set = []
metadata = File.read(PROJECT_ROOT + '/metadata.json')
@@ -143,7 +128,7 @@ def install_puppet_agent(agent)
def agent_opts(_host)
{
main: { color: 'ansi' },
- agent: { ssldir: '$vardir/ssl', server: MASTER_NODE_NAME, environment: ENVIRONMENT },
+ agent: { ssldir: '$vardir/ssl', server: MASTER_FQDN, environment: ENVIRONMENT },
}
end
@@ -177,15 +162,16 @@ def setup_puppet_on(_host, opts = {})
opts = { agent: true }.merge(opts)
return unless opts[:agent]
agents.each do |agent|
- print_stage("Configuring agent at #{agent.get_ip} #{agent.hostname} #{agent}")
- # on(agent, puppet('resource', 'host', MASTER_NODE_NAME, 'ensure=present', "ip=#{MASTER_IP}"))
+ agent_fqdn = agent.node_name
+ print_stage("Configuring agent at #{agent.get_ip} #{agent_fqdn}")
+ # on(agent, puppet('resource', 'host', MASTER_FQDN, 'ensure=present', "ip=#{MASTER_IP}"))
agent['type'] = 'aio'
puppet_opts = agent_opts(master.to_s)
## On el- or centos
case agent['platform']
when %r{el-|centos}
## Set class under test to console display. Requires restart of tty1 serivce to display without logon or reboot
- on(agent, "echo -e 'You are running an acceptance test of \e[1;32m#{CLASS}\e[0m\n\non this AGENT\t\e[1;36m#{agent.node_name}\t#{agent.ip}\e[0m\nfrom MASTER\t\e[1;34m#{MASTER_NODE_NAME}\t#{MASTER_IP}\e[0m\n\n' | tee /etc/motd /etc/issue")
+ on(agent, "echo -e 'You are running an acceptance test of \e[1;32m#{CLASS}\e[0m\n\non this AGENT\t\e[1;36m#{agent_fqdn}\t#{agent.ip}\e[0m\nfrom MASTER\t\e[1;34m#{MASTER_FQDN}\t#{MASTER_IP}\e[0m\n\n' | tee /etc/motd /etc/issue")
on(agent, 'systemctl restart getty@tty1')
## Check if puppet-agent is installed, otherwise install it
result = on(agent, 'rpm -qa | grep puppet-agent', acceptable_exit_codes: [0, 1])
@@ -194,12 +180,12 @@ def setup_puppet_on(_host, opts = {})
end
configure_puppet_on(agent, puppet_opts)
stop_firewall_on agent
- print_stage("Disabling Puppet service so only manual runs occur on #{agent}")
+ print_stage("Disabling Puppet service so only manual runs occur on #{agent_fqdn}")
on(agent, 'systemctl disable puppet --now', acceptable_exit_codes: [0])
- on(agent, "echo '#{MASTER_IP} #{MASTER_NODE_NAME}' >> /etc/hosts")
+ on(agent, "echo '#{MASTER_IP} #{MASTER_FQDN}' >> /etc/hosts")
## On windows
when %r{windows}
- print_stage("Disabling Windows Update service to prevent updates during testing on #{agent}")
+ print_stage("Disabling Windows Update service to prevent updates during testing on #{agent_fqdn}")
## Disable and force kill Windows Update service if running
on(agent, powershell('Set-Service wuauserv -StartupType Disabled'))
on(agent, powershell("taskkill /f /t /fi 'SERVICES eq wuauserv'"), acceptable_exit_codes: [0, 1])
@@ -210,12 +196,11 @@ def setup_puppet_on(_host, opts = {})
on(agent, powershell('Invoke-WebRequest https://downloads.puppetlabs.com/windows/puppet7/puppet-agent-x64-latest.msi -OutFile c:\\puppet-agent-x64-latest.msi; Start-Process msiexec -ArgumentList \'/qn /norestart /i c:\\puppet-agent-x64-latest.msi\' -Wait'))
end
# Configure_puppet_on(agent, puppet_opts)
- on(agent, powershell("Set-Content -path c:\\ProgramData\\PuppetLabs\\puppet\\etc\\puppet.conf -Value \"[agent]`r`nserver=#{MASTER_NODE_NAME}`r`nenvironment=#{ENVIRONMENT}\""))
- print_stage("Disabling Puppet service so only manual runs occur on #{agent}")
+ on(agent, powershell("Set-Content -path c:\\ProgramData\\PuppetLabs\\puppet\\etc\\puppet.conf -Value \"[agent]`r`nserver=#{MASTER_FQDN}`r`nenvironment=#{ENVIRONMENT}\""))
+ print_stage("Disabling Puppet service so only manual runs occur on #{agent_fqdn}")
on(agent, powershell('Set-Service puppet -StartupType Disabled; Stop-Service puppet -Force'))
- on(agent, powershell("Add-Content -path c:\\windows\\system32\\drivers\\etc\\hosts -Value \"#{MASTER_IP}`t#{MASTER_NODE_NAME}\""))
+ on(agent, powershell("Add-Content -path c:\\windows\\system32\\drivers\\etc\\hosts -Value \"#{MASTER_IP}`t#{MASTER_FQDN}\""))
end
- # on(master, "echo '#{agent.node_name}' >> /etc/puppetlabs/puppet/autosign.conf")
end
on(master, "echo '*' > /etc/puppetlabs/puppet/autosign.conf")
end
@@ -223,14 +208,14 @@ def setup_puppet_on(_host, opts = {})
## Setup Puppetserver
def setup_puppetserver_on(host, _opts = {})
# opts = { master => true, agent: false }.merge(opts)
- print_stage("Configuring master at #{MASTER_IP} #{MASTER_NODE_NAME} #{host}")
+ print_stage("Configuring master at #{MASTER_IP} #{MASTER_FQDN} #{host}")
## Set the puppetserver to know it is its own master, so commands like 'puppetserver ca list' work
on(master, 'puppet config set server `hostname`')
## Set class under test to console display. Requires restart of tty1 serivce to display without logon or reboot
agent_names = agents.map do |agent|
"#{agent['roles'].first.gsub('agent_', '').ljust(20)}#{agent.node_name.ljust(30)}#{agent.ip.ljust(40)}"
end
- on host, "echo -e 'You are running an acceptance test of \e[1;32m#{CLASS}\e[0m\n\nfrom this MASTER\n\e[1;34m#{master['roles'].first.ljust(20)}#{MASTER_NODE_NAME.ljust(30)}#{MASTER_IP.ljust(40)}\e[0m\n\nto AGENTS\n\e[1;36m#{agent_names.join("\n")}\e[0m\n\n' | tee /etc/motd /etc/issue"
+ on host, "echo -e 'You are running an acceptance test of \e[1;32m#{CLASS}\e[0m\n\nfrom this MASTER\n\e[1;34m#{master['roles'].first.ljust(20)}#{MASTER_FQDN.ljust(30)}#{MASTER_IP.ljust(40)}\e[0m\n\nto AGENTS\n\e[1;36m#{agent_names.join("\n")}\e[0m\n\n' | tee /etc/motd /etc/issue"
on host, 'systemctl restart getty@tty1'
## Create folder for module and dependencies
on(master, "install -d -o puppet -g puppet /etc/puppetlabs/code/environments/#{ENVIRONMENT}/{modules,data,manifests}")
@@ -244,9 +229,6 @@ def setup_puppetserver_on(host, _opts = {})
install_puppetserver master
end
install_modules_on master
- ## Add Windows overrides for Bitvise firewall rule and root user on master (note that root user appears to be a system user on Windows according to Puppet anyway!)
- # on(master, "echo -e \"#{HIERA_OVERRIDES}\" > /etc/puppetlabs/code/environments/#{ENVIRONMENT}/data/overrides.yaml")
- on(master, "echo -e \"#{HIERA_YAML}\" > /etc/puppetlabs/code/environments/#{ENVIRONMENT}/hiera.yaml")
## Generate puppet types on master to overcome issue with some windows types on initial runs
on(master, "/opt/puppetlabs/puppet/bin/puppet generate types --environment #{ENVIRONMENT}")
on master, puppet('resource', 'service', 'puppetserver', 'ensure=running')
@@ -255,12 +237,8 @@ def setup_puppetserver_on(host, _opts = {})
## Copy test module and install dependencies on Puppetserver
def install_modules_on(host)
- # scp_to(host, PROJECT_ROOT, "/etc/puppetlabs/code/environments/#{ENVIRONMENT}/modules")
- print_stage("Copying module and installing dependencies on master at #{MASTER_IP} #{MASTER_NODE_NAME}")
+ print_stage("Copying module and installing dependencies on master at #{MASTER_IP} #{MASTER_FQDN}")
install_dependencies_from DEPENDENCY_LIST
- ## Alternative method to install deps does not support --target-dir, host[:default_module_install_opts] hash override not working
- # set host['default_module_install_opts'] = {"target-dir /etc/puppetlabs/code/environments/test/modules"}
- # install_module_dependencies_on(host)
copy_module_to(host, source: PROJECT_ROOT, target_module_path: "/etc/puppetlabs/code/environments/#{ENVIRONMENT}/modules/", protocol: 'rsync')
on(master, "echo -e 'modulepath = /etc/puppetlabs/code/environments/#{ENVIRONMENT}/modules' > /etc/puppetlabs/code/environments/#{ENVIRONMENT}/environment.conf")
on(master, 'puppet module list --tree')
@@ -333,10 +311,10 @@ def install_dependencies_from(list)
## Actions after suite
c.after :suite do
if master['hypervisor'] == 'none'
- print_stage("Cleaning up static-master at #{MASTER_IP} #{MASTER_NODE_NAME}")
+ print_stage("Cleaning up static-master at #{MASTER_IP} #{MASTER_FQDN}")
## Delete accumulating lines in sshd_conf and /etc/hosts when reusing master
on(master, "sed -i '/PermitUserEnvironment yes/d' /etc/ssh/sshd_config")
- on(master, "echo -e \"127.0.0.1\tlocalhost localhost.localdomain\n#{MASTER_IP}\t#{MASTER_NODE_NAME}\" > /etc/hosts")
+ on(master, "echo -e \"127.0.0.1\tlocalhost localhost.localdomain\n#{MASTER_IP}\t#{MASTER_FQDN}\" > /etc/hosts")
unless ENV['BEAKER_destroy'] == 'no'
## Clean up environment and certificates when using static-master
on(master, "find /etc/puppetlabs/code/environments/#{ENVIRONMENT} ! -name production -type d -exec rm -rf {} +")