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 {} +")