simp-beaker-helpers 1.18.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.fixtures.yml +8 -0
  3. data/.gitignore +8 -0
  4. data/.gitlab-ci.yml +163 -0
  5. data/.rspec +4 -0
  6. data/.rubocop.yml +546 -0
  7. data/.travis.yml +36 -0
  8. data/CHANGELOG.md +231 -0
  9. data/Gemfile +51 -0
  10. data/LICENSE +27 -0
  11. data/README.md +543 -0
  12. data/Rakefile +151 -0
  13. data/files/pki/clean.sh +1 -0
  14. data/files/pki/make.sh +101 -0
  15. data/files/pki/template_ca.cnf +259 -0
  16. data/files/pki/template_host.cnf +263 -0
  17. data/files/puppet-agent-versions.yaml +46 -0
  18. data/lib/simp/beaker_helpers.rb +1231 -0
  19. data/lib/simp/beaker_helpers/constants.rb +25 -0
  20. data/lib/simp/beaker_helpers/inspec.rb +328 -0
  21. data/lib/simp/beaker_helpers/snapshot.rb +156 -0
  22. data/lib/simp/beaker_helpers/ssg.rb +383 -0
  23. data/lib/simp/beaker_helpers/version.rb +5 -0
  24. data/lib/simp/beaker_helpers/windows.rb +16 -0
  25. data/lib/simp/rake/beaker.rb +269 -0
  26. data/simp-beaker-helpers.gemspec +38 -0
  27. data/spec/acceptance/nodesets/default.yml +32 -0
  28. data/spec/acceptance/suites/default/check_puppet_version_spec.rb +23 -0
  29. data/spec/acceptance/suites/default/enable_fips_spec.rb +23 -0
  30. data/spec/acceptance/suites/default/fixture_modules_spec.rb +22 -0
  31. data/spec/acceptance/suites/default/install_simp_deps_repo_spec.rb +43 -0
  32. data/spec/acceptance/suites/default/nodesets +1 -0
  33. data/spec/acceptance/suites/default/pki_tests_spec.rb +55 -0
  34. data/spec/acceptance/suites/default/set_hieradata_on_spec.rb +33 -0
  35. data/spec/acceptance/suites/default/write_hieradata_to_spec.rb +33 -0
  36. data/spec/acceptance/suites/fips_from_fixtures/00_default_spec.rb +63 -0
  37. data/spec/acceptance/suites/fips_from_fixtures/metadata.yml +2 -0
  38. data/spec/acceptance/suites/fips_from_fixtures/nodesets +1 -0
  39. data/spec/acceptance/suites/offline/00_default_spec.rb +165 -0
  40. data/spec/acceptance/suites/offline/README +2 -0
  41. data/spec/acceptance/suites/offline/nodesets/default.yml +26 -0
  42. data/spec/acceptance/suites/puppet_collections/00_default_spec.rb +25 -0
  43. data/spec/acceptance/suites/puppet_collections/metadata.yml +2 -0
  44. data/spec/acceptance/suites/puppet_collections/nodesets/default.yml +30 -0
  45. data/spec/acceptance/suites/snapshot/00_snapshot_test_spec.rb +82 -0
  46. data/spec/acceptance/suites/snapshot/10_general_usage_spec.rb +56 -0
  47. data/spec/acceptance/suites/snapshot/nodesets +1 -0
  48. data/spec/acceptance/suites/windows/00_default_spec.rb +119 -0
  49. data/spec/acceptance/suites/windows/metadata.yml +2 -0
  50. data/spec/acceptance/suites/windows/nodesets/default.yml +33 -0
  51. data/spec/acceptance/suites/windows/nodesets/win2016.yml +35 -0
  52. data/spec/acceptance/suites/windows/nodesets/win2019.yml +34 -0
  53. data/spec/lib/simp/beaker_helpers_spec.rb +216 -0
  54. data/spec/spec_helper.rb +100 -0
  55. data/spec/spec_helper_acceptance.rb +25 -0
  56. metadata +243 -0
@@ -0,0 +1,263 @@
1
+ #
2
+ # OpenSSL example configuration file.
3
+ # This is mostly being used for generation of certificate requests.
4
+ #
5
+
6
+ # This definition stops the following lines choking if HOME isn't
7
+ # defined.
8
+ HOME = .
9
+ RANDFILE = $ENV::HOME/.rnd
10
+
11
+ # Extra OBJECT IDENTIFIER info:
12
+ #oid_file = $ENV::HOME/.oid
13
+ oid_section = new_oids
14
+
15
+ # To use this configuration file with the "-extfile" option of the
16
+ # "openssl x509" utility, name here the section containing the
17
+ # X.509v3 extensions to use:
18
+ # extensions =
19
+ # (Alternatively, use a configuration file that has only
20
+ # X.509v3 extensions in its main [= default] section.)
21
+
22
+ [ new_oids ]
23
+
24
+ # We can add new OIDs in here for use by 'ca' and 'req'.
25
+ # Add a simple OID like this:
26
+ # testoid1=1.2.3.4
27
+ # Or use config file substitution like this:
28
+ # testoid2=${testoid1}.5.6
29
+
30
+ ####################################################################
31
+ [ ca ]
32
+ default_ca = CA_default # The default ca section
33
+
34
+ ####################################################################
35
+ [ CA_default ]
36
+
37
+ dir = ./demoCA # Where everything is kept
38
+ certs = $dir/certs # Where the issued certs are kept
39
+ crl_dir = $dir/crl # Where the issued crl are kept
40
+ database = $dir/index.txt # database index file.
41
+ new_certs_dir = $dir/newcerts # default place for new certs.
42
+
43
+ certificate = $dir/cacert.pem # The CA certificate
44
+ serial = $dir/serial # The current serial number
45
+ crl = $dir/crl.pem # The current CRL
46
+ private_key = $dir/private/cakey.pem# The private key
47
+ RANDFILE = $dir/private/.rand # private random number file
48
+
49
+ x509_extensions = usr_cert # The extentions to add to the cert
50
+
51
+ input_password = test
52
+ output_password = test
53
+
54
+ # Comment out the following two lines for the "traditional"
55
+ # (and highly broken) format.
56
+ name_opt = ca_default # Subject Name options
57
+ cert_opt = ca_default # Certificate field options
58
+
59
+ # Extension copying option: use with caution.
60
+ # copy_extensions = copy
61
+
62
+ # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
63
+ # so this is commented out by default to leave a V1 CRL.
64
+ # crl_extensions = crl_ext
65
+
66
+ default_days = 365 # how long to certify for
67
+ default_crl_days= 30 # how long before next CRL
68
+ default_md = sha256 # which md to use.
69
+ preserve = no # keep passed DN ordering
70
+
71
+ # A few difference way of specifying how similar the request should look
72
+ # For type CA, the listed attributes must be the same, and the optional
73
+ # and supplied fields are just that :-)
74
+ policy = policy_anything
75
+
76
+ # For the CA policy
77
+ [ policy_match ]
78
+ countryName = match
79
+ stateOrProvinceName = match
80
+ organizationName = match
81
+ organizationalUnitName = optional
82
+ commonName = supplied
83
+ emailAddress = optional
84
+
85
+ # For the 'anything' policy
86
+ # At this point in time, you must list all acceptable 'object'
87
+ # types.
88
+ [ policy_anything ]
89
+ countryName = optional
90
+ stateOrProvinceName = optional
91
+ localityName = optional
92
+ organizationName = optional
93
+ organizationalUnitName = optional
94
+ commonName = supplied
95
+ emailAddress = optional
96
+
97
+ ####################################################################
98
+ [ req ]
99
+ default_bits = 2048
100
+ default_keyfile = privkey.pem
101
+ distinguished_name = req_distinguished_name
102
+ attributes = req_attributes
103
+ x509_extensions = v3_ca # The extentions to add to the self signed cert
104
+
105
+ # Passwords for private keys if not present they will be prompted for
106
+ # input_password = secret
107
+ # output_password = secret
108
+
109
+ # This sets a mask for permitted string types. There are several options.
110
+ # default: PrintableString, T61String, BMPString.
111
+ # pkix : PrintableString, BMPString.
112
+ # utf8only: only UTF8Strings.
113
+ # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
114
+ # MASK:XXXX a literal mask value.
115
+ # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings
116
+ # so use this option with caution!
117
+ string_mask = nombstr
118
+
119
+ # req_extensions = v3_req # The extensions to add to a certificate request
120
+
121
+ [ req_distinguished_name ]
122
+ countryName = Country Name (2 letter code)
123
+ countryName_default = ZZ
124
+ countryName_min = 2
125
+ countryName_max = 2
126
+
127
+ #stateOrProvinceName = State or Province Name (full name)
128
+ #stateOrProvinceName_default = Berkshire
129
+
130
+ #localityName = Locality Name (eg, city)
131
+ #localityName_default = Newbury
132
+
133
+ 0.organizationName = Organization Name (eg, company)
134
+ 0.organizationName_default = Fake Org
135
+
136
+ # we can do this but it is not needed normally :-)
137
+ #1.organizationName = Second Organization Name (eg, company)
138
+ #1.organizationName_default = World Wide Web Pty Ltd
139
+
140
+ organizationalUnitName = Organizational Unit Name (eg, section)
141
+ organizationalUnitName_default = Hosts
142
+
143
+ commonName = Common Name (eg, your name or your server\'s hostname)
144
+ commonName_max = 64
145
+ commonName_default = #HOSTNAME#
146
+
147
+ #emailAddress = Email Address
148
+ #emailAddress_max = 64
149
+
150
+ # SET-ex3 = SET extension number 3
151
+
152
+ [ req_attributes ]
153
+ #challengePassword = A challenge password
154
+ #challengePassword_min = 4
155
+ #challengePassword_max = 20
156
+ #challengePassword_default = password
157
+
158
+ unstructuredName = Fake Cert
159
+
160
+ [ usr_cert ]
161
+
162
+ # These extensions are added when 'ca' signs a request.
163
+
164
+ # This goes against PKIX guidelines but some CAs do it and some software
165
+ # requires this to avoid interpreting an end user certificate as a CA.
166
+
167
+ basicConstraints=CA:FALSE
168
+
169
+ # Here are some examples of the usage of nsCertType. If it is omitted
170
+ # the certificate can be used for anything *except* object signing.
171
+
172
+ # This is OK for an SSL server.
173
+ #nsCertType = server
174
+
175
+ # For an object signing certificate this would be used.
176
+ # nsCertType = objsign
177
+
178
+ # For normal client use this is typical
179
+ # nsCertType = client, email
180
+ # nsCertType = client
181
+
182
+ # and for everything including object signing:
183
+ nsCertType = server, client, email, objsign
184
+
185
+ # This is typical in keyUsage for a client certificate.
186
+ keyUsage = nonRepudiation, digitalSignature, keyEncipherment
187
+
188
+ # This will be displayed in Netscape's comment listbox.
189
+ #nsComment = "Test Certificate"
190
+
191
+ # PKIX recommendations harmless if included in all certificates.
192
+ subjectKeyIdentifier=hash
193
+ authorityKeyIdentifier=keyid,issuer:always
194
+
195
+ # This stuff is for subjectAltName and issuerAltname.
196
+ # Import the email address.
197
+ # subjectAltName=email:copy
198
+ # An alternative to produce certificates that aren't
199
+ # deprecated according to PKIX.
200
+ # subjectAltName=email:move
201
+ # subjectAltName = #ALTNAMES#
202
+
203
+ # Copy subject details
204
+ # issuerAltName=issuer:copy
205
+
206
+ #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
207
+ #nsBaseUrl
208
+ #nsRevocationUrl
209
+ #nsRenewalUrl
210
+ #nsCaPolicyUrl
211
+ #nsSslServerName
212
+
213
+ [ v3_req ]
214
+
215
+ # Extensions to add to a certificate request
216
+
217
+ basicConstraints = CA:FALSE
218
+ keyUsage = nonRepudiation, digitalSignature, keyEncipherment
219
+
220
+ [ v3_ca ]
221
+
222
+
223
+ # Extensions for a typical CA
224
+
225
+
226
+ # PKIX recommendation.
227
+
228
+ subjectKeyIdentifier=hash
229
+
230
+ authorityKeyIdentifier=keyid:always,issuer:always
231
+
232
+ # This is what PKIX recommends but some broken software chokes on critical
233
+ # extensions.
234
+ #basicConstraints = critical,CA:true
235
+ # So we do this instead.
236
+ basicConstraints = CA:true
237
+
238
+ # Key usage: this is typical for a CA certificate. However since it will
239
+ # prevent it being used as an test self-signed certificate it is best
240
+ # left out by default.
241
+ # keyUsage = cRLSign, keyCertSign
242
+
243
+ # Some might want this also
244
+ # nsCertType = sslCA, emailCA
245
+
246
+ # Include email address in subject alt name: another PKIX recommendation
247
+ # subjectAltName=email:copy
248
+ # Copy issuer details
249
+ # issuerAltName=issuer:copy
250
+
251
+ # DER hex encoding of an extension: beware experts only!
252
+ # obj=DER:02:03
253
+ # Where 'obj' is a standard or added object
254
+ # You can even override a supported extension:
255
+ # basicConstraints= critical, DER:30:03:01:01:FF
256
+
257
+ [ crl_ext ]
258
+
259
+ # CRL extensions.
260
+ # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
261
+
262
+ # issuerAltName=issuer:copy
263
+ authorityKeyIdentifier=keyid:always,issuer:always
@@ -0,0 +1,46 @@
1
+ ---
2
+ # `puppet` agent to `puppet-agent` version mappings
3
+ #
4
+ # - https://docs.puppet.com/puppet/latest/about_agent.html
5
+ #
6
+ version_mappings:
7
+ '4.10.12': '1.10.14'
8
+ '4.10.11': '1.10.12'
9
+ '4.10.10': '1.10.10'
10
+ '4.10.9': '1.10.9'
11
+ '4.10.8': '1.10.8'
12
+ '4.10.7': '1.10.7'
13
+ '4.10.6': '1.10.6'
14
+ '4.10.5': '1.10.5'
15
+ '4.10.4': '1.10.4'
16
+ '4.10.3': '1.10.3'
17
+ '4.10.2': '1.10.2'
18
+ '4.10.1': '1.10.1'
19
+ '4.10.0': '1.10.0'
20
+ '4.9.4': '1.9.3'
21
+ '4.9.3': '1.9.2'
22
+ '4.9.2': '1.9.1'
23
+ '4.9.0': '1.9.0'
24
+ '4.8.2': '1.8.3'
25
+ '4.8.1': '1.8.2'
26
+ '4.8.0': '1.8.0'
27
+ '4.7.1': '1.7.2'
28
+ '4.7.0': '1.7.1'
29
+ '4.6.2': '1.6.2'
30
+ '4.6.1': '1.6.1'
31
+ '4.6.0': '1.6.0'
32
+ '4.5.2': '1.5.3'
33
+ '4.5.1': '1.5.1'
34
+ '4.5.0': '1.5.0'
35
+ '4.4.2': '1.4.2'
36
+ '4.4.1': '1.4.1'
37
+ '4.4.0': '1.4.0'
38
+ '4.3.2': '1.3.6'
39
+ '4.3.1': '1.3.2'
40
+ '4.3.0': '1.3.0'
41
+ '4.2.3': '1.2.7'
42
+ '4.2.2': '1.2.6'
43
+ '4.2.1': '1.2.2'
44
+ '4.2.0': '1.2.1'
45
+ '4.1.0': '1.1.1'
46
+ '4.0.0': '1.0.1'
@@ -0,0 +1,1231 @@
1
+ require 'beaker-puppet'
2
+ require 'bundler'
3
+
4
+ module Simp; end
5
+
6
+ module Simp::BeakerHelpers
7
+ include BeakerPuppet
8
+
9
+ require 'simp/beaker_helpers/constants'
10
+ require 'simp/beaker_helpers/inspec'
11
+ require 'simp/beaker_helpers/snapshot'
12
+ require 'simp/beaker_helpers/ssg'
13
+ require 'simp/beaker_helpers/version'
14
+
15
+ # Stealing this from the Ruby 2.5 Dir::Tmpname workaround from Rails
16
+ def self.tmpname
17
+ t = Time.new.strftime("%Y%m%d")
18
+ "simp-beaker-helpers-#{t}-#{$$}-#{rand(0x100000000).to_s(36)}.tmp"
19
+ end
20
+
21
+ def is_windows?(sut)
22
+ sut[:platform] =~ /windows/i
23
+ end
24
+
25
+ # We can't cache this because it may change during a run
26
+ def fips_enabled(sut)
27
+ return on( sut,
28
+ 'cat /proc/sys/crypto/fips_enabled 2>/dev/null',
29
+ :accept_all_exit_codes => true
30
+ ).output.strip == '1'
31
+ end
32
+
33
+ # Figure out the best method to copy files to a host and use it
34
+ #
35
+ # Will create the directories leading up to the target if they don't exist
36
+ def copy_to(sut, src, dest, opts={})
37
+ unless fips_enabled(sut) || @has_rsync
38
+ %x{which rsync 2>/dev/null}.strip
39
+
40
+ @has_rsync = !$?.nil? && $?.success?
41
+ end
42
+
43
+ sut.mkdir_p(File.dirname(dest))
44
+
45
+ if sut[:hypervisor] == 'docker'
46
+ exclude_list = []
47
+ if opts.has_key?(:ignore) && !opts[:ignore].empty?
48
+ opts[:ignore].each do |value|
49
+ exclude_list << "--exclude '#{value}'"
50
+ end
51
+ end
52
+
53
+ # Work around for breaking changes in beaker-docker
54
+ if sut.host_hash[:docker_container]
55
+ container_id = sut.host_hash[:docker_container].id
56
+ else
57
+ container_id = sut.host_hash[:docker_container_id]
58
+ end
59
+ %x(tar #{exclude_list.join(' ')} -hcf - -C "#{File.dirname(src)}" "#{File.basename(src)}" | docker exec -i "#{container_id}" tar -C "#{dest}" -xf -)
60
+ elsif @has_rsync && sut.check_for_command('rsync')
61
+ # This makes rsync_to work like beaker and scp usually do
62
+ exclude_hack = %(__-__' -L --exclude '__-__)
63
+
64
+ # There appears to be a single copy of 'opts' that gets passed around
65
+ # through all of the different hosts so we're going to make a local deep
66
+ # copy so that we don't destroy the world accidentally.
67
+ _opts = Marshal.load(Marshal.dump(opts))
68
+ _opts[:ignore] ||= []
69
+ _opts[:ignore] << exclude_hack
70
+
71
+ if File.directory?(src)
72
+ dest = File.join(dest, File.basename(src)) if File.directory?(src)
73
+ sut.mkdir_p(dest)
74
+ end
75
+
76
+ # End rsync hackery
77
+
78
+ begin
79
+ rsync_to(sut, src, dest, _opts)
80
+ rescue
81
+ # Depending on what is getting tested, a new SSH session might not
82
+ # work. In this case, we fall back to SSH.
83
+ #
84
+ # The rsync failure is quite fast so this doesn't affect performance as
85
+ # much as shoving a bunch of data over the ssh session.
86
+ scp_to(sut, src, dest, opts)
87
+ end
88
+ else
89
+ scp_to(sut, src, dest, opts)
90
+ end
91
+ end
92
+
93
+ # use the `puppet fact` face to look up facts on an SUT
94
+ def pfact_on(sut, fact_name)
95
+ facts_json = on(sut,'puppet facts find xxx').output
96
+ facts = JSON.parse(facts_json).fetch( 'values' )
97
+ facts.fetch(fact_name)
98
+ end
99
+
100
+ # Returns the modulepath on the SUT, as an Array
101
+ def puppet_modulepath_on(sut, environment='production')
102
+ splitchar = ':'
103
+ splitchar = ';' if is_windows?(sut)
104
+
105
+ (
106
+ sut.puppet_configprint['modulepath'].split(splitchar) +
107
+ sut.puppet_configprint['basemodulepath'].split(splitchar)
108
+ ).uniq
109
+ end
110
+
111
+ # Return the default environment path
112
+ def puppet_environment_path_on(sut, environment='production')
113
+ File.dirname(sut.puppet_configprint['manifest'])
114
+ end
115
+
116
+ # Return the path to the 'spec/fixtures' directory
117
+ def fixtures_path
118
+ return @fixtures_path if @fixtures_path
119
+
120
+ STDERR.puts ' ** fixtures_path' if ENV['BEAKER_helpers_verbose']
121
+ dir = RSpec.configuration.default_path
122
+ dir = File.join('.', 'spec') unless dir
123
+
124
+ dir = File.join(File.expand_path(dir), 'fixtures')
125
+
126
+ if File.directory?(dir)
127
+ @fixtures_path = dir
128
+ return @fixtures_path
129
+ else
130
+ raise("Could not find fixtures directory at '#{dir}'")
131
+ end
132
+ end
133
+
134
+ # Locates .fixture.yml in or above this directory.
135
+ def fixtures_yml_path
136
+ return @fixtures_yml_path if @fixtures_yml_path
137
+
138
+ STDERR.puts ' ** fixtures_yml_path' if ENV['BEAKER_helpers_verbose']
139
+
140
+ if ENV['FIXTURES_YML']
141
+ fixtures_yml = ENV['FIXTURES_YML']
142
+ else
143
+ fixtures_yml = ''
144
+ dir = '.'
145
+ while( fixtures_yml.empty? && File.expand_path(dir) != '/' ) do
146
+ file = File.expand_path( '.fixtures.yml', dir )
147
+ STDERR.puts " ** fixtures_yml_path: #{file}" if ENV['BEAKER_helpers_verbose']
148
+ if File.exists? file
149
+ fixtures_yml = file
150
+ break
151
+ end
152
+ dir = "#{dir}/.."
153
+ end
154
+ end
155
+
156
+ raise 'ERROR: cannot locate .fixtures.yml!' if fixtures_yml.empty?
157
+
158
+ STDERR.puts " ** fixtures_yml_path:finished (file: '#{file}')" if ENV['BEAKER_helpers_verbose']
159
+
160
+ @fixtures_yml_path = fixtures_yml
161
+
162
+ return @fixtures_yml_path
163
+ end
164
+
165
+
166
+ # returns an Array of puppet modules declared in .fixtures.yml
167
+ def pupmods_in_fixtures_yml
168
+ return @pupmods_in_fixtures_yml if @pupmods_in_fixtures_yml
169
+
170
+ STDERR.puts ' ** pupmods_in_fixtures_yml' if ENV['BEAKER_helpers_verbose']
171
+ fixtures_yml = fixtures_yml_path
172
+ data = YAML.load_file( fixtures_yml )
173
+ repos = data.fetch('fixtures').fetch('repositories', {}).keys || []
174
+ symlinks = data.fetch('fixtures').fetch('symlinks', {}).keys || []
175
+ STDERR.puts ' ** pupmods_in_fixtures_yml: finished' if ENV['BEAKER_helpers_verbose']
176
+
177
+ @pupmods_in_fixtures_yml = (repos + symlinks)
178
+
179
+ return @pupmods_in_fixtures_yml
180
+ end
181
+
182
+
183
+ # Ensures that the fixture modules (under `spec/fixtures/modules`) exists.
184
+ # if any fixture modules are missing, run 'rake spec_prep' to populate the
185
+ # fixtures/modules
186
+ def ensure_fixture_modules
187
+ STDERR.puts " ** ensure_fixture_modules" if ENV['BEAKER_helpers_verbose']
188
+ unless ENV['BEAKER_spec_prep'] == 'no'
189
+ puts "== checking prepped modules from .fixtures.yml"
190
+ puts " -- (use BEAKER_spec_prep=no to disable)"
191
+ missing_modules = []
192
+ pupmods_in_fixtures_yml.each do |pupmod|
193
+ STDERR.puts " ** -- ensure_fixture_modules: '#{pupmod}'" if ENV['BEAKER_helpers_verbose']
194
+ mod_root = File.expand_path( "spec/fixtures/modules/#{pupmod}", File.dirname( fixtures_yml_path ))
195
+ missing_modules << pupmod unless File.directory? mod_root
196
+ end
197
+ puts " -- #{missing_modules.size} modules need to be prepped"
198
+ unless missing_modules.empty?
199
+ cmd = 'bundle exec rake spec_prep'
200
+ puts " -- running spec_prep: '#{cmd}'"
201
+ %x(#{cmd})
202
+ else
203
+ puts " == all fixture modules present"
204
+ end
205
+ end
206
+ STDERR.puts " ** -- ensure_fixture_modules: finished" if ENV['BEAKER_helpers_verbose']
207
+ end
208
+
209
+
210
+ # Copy the local fixture modules (under `spec/fixtures/modules`) onto each SUT
211
+ def copy_fixture_modules_to( suts = hosts, opts = {})
212
+ ensure_fixture_modules
213
+
214
+ opts[:pluginsync] = opts.fetch(:pluginsync, true)
215
+
216
+ unless ENV['BEAKER_copy_fixtures'] == 'no'
217
+ parallel = (ENV['BEAKER_SIMP_parallel'] == 'yes')
218
+ block_on(suts, :run_in_parallel => parallel) do |sut|
219
+ STDERR.puts " ** copy_fixture_modules_to: '#{sut}'" if ENV['BEAKER_helpers_verbose']
220
+
221
+ # Use spec_prep to provide modules (this supports isolated networks)
222
+ unless ENV['BEAKER_use_fixtures_dir_for_modules'] == 'no'
223
+
224
+ # NOTE: As a result of BKR-723, which does not look easy to fix, we
225
+ # cannot rely on `copy_module_to()` to choose a sane default for
226
+ # `target_module_path`. This workaround queries each SUT's
227
+ # `modulepath` and targets the first one.
228
+ target_module_path = puppet_modulepath_on(sut).first
229
+
230
+ mod_root = File.expand_path( "spec/fixtures/modules", File.dirname( fixtures_yml_path ))
231
+
232
+ Dir.chdir(mod_root) do
233
+ # Have to do things the slow way on Windows
234
+ if is_windows?(sut)
235
+ Dir.glob('*') do |module_dir|
236
+ if File.directory?(module_dir)
237
+ copy_module_to( sut, {
238
+ :source => module_dir,
239
+ :module_name => module_dir,
240
+ :target_module_path => target_module_path
241
+ })
242
+ end
243
+ end
244
+ else
245
+ begin
246
+ tarfile = "#{Simp::BeakerHelpers.tmpname}.tar"
247
+
248
+ excludes = PUPPET_MODULE_INSTALL_IGNORE.map do |x|
249
+ x = "--exclude '*/#{x}'"
250
+ end.join(' ')
251
+
252
+ %x(tar -ch #{excludes} -f #{tarfile} *)
253
+
254
+ if File.exist?(tarfile)
255
+ copy_to(sut, tarfile, target_module_path, opts)
256
+ else
257
+ fail("Error: module tar file '#{tarfile}' could not be created at #{mod_root}")
258
+ end
259
+
260
+ on(sut, "cd #{target_module_path} && tar -xf #{File.basename(tarfile)}")
261
+ ensure
262
+ FileUtils.remove_entry(tarfile, true)
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
269
+ STDERR.puts ' ** copy_fixture_modules_to: finished' if ENV['BEAKER_helpers_verbose']
270
+
271
+ # sync custom facts from the new modules to each SUT's factpath
272
+ pluginsync_on(suts) if opts[:pluginsync]
273
+ end
274
+
275
+
276
+ # Configure and reboot SUTs into FIPS mode
277
+ def enable_fips_mode_on( suts = hosts )
278
+ puts '== configuring FIPS mode on SUTs'
279
+ puts ' -- (use BEAKER_fips=no to disable)'
280
+ parallel = (ENV['BEAKER_SIMP_parallel'] == 'yes')
281
+
282
+ block_on(suts, :run_in_parallel => parallel) do |sut|
283
+ if is_windows?(sut)
284
+ puts " -- SKIPPING #{sut} because it is windows"
285
+ next
286
+ end
287
+
288
+ puts " -- enabling FIPS on '#{sut}'"
289
+
290
+ # We need to use FIPS compliant algorithms and keylengths as per the FIPS
291
+ # certification.
292
+ on(sut, 'puppet config set digest_algorithm sha256')
293
+ on(sut, 'puppet config set keylength 2048')
294
+
295
+ # We need to be able to get back into our system!
296
+ # Make these safe for all systems, even old ones.
297
+ # TODO Use simp-ssh Puppet module appropriately (i.e., in a fashion
298
+ # that doesn't break vagrant access and is appropriate for
299
+ # typical module tests.)
300
+ fips_ssh_ciphers = [ 'aes256-ctr','aes192-ctr','aes128-ctr']
301
+ on(sut, %(sed -i '/Ciphers /d' /etc/ssh/sshd_config))
302
+ on(sut, %(echo 'Ciphers #{fips_ssh_ciphers.join(',')}' >> /etc/ssh/sshd_config))
303
+
304
+ fips_enable_modulepath = ''
305
+
306
+ if pupmods_in_fixtures_yml.include?('fips')
307
+ copy_fixture_modules_to(sut)
308
+ else
309
+ # If we don't already have the simp-fips module installed
310
+ #
311
+ # Use the simp-fips Puppet module to set FIPS up properly:
312
+ # Download the appropriate version of the module and its dependencies from PuppetForge.
313
+ # TODO provide a R10k download option in which user provides a Puppetfile
314
+ # with simp-fips and its dependencies
315
+ on(sut, 'mkdir -p /root/.beaker_fips/modules')
316
+
317
+ fips_enable_modulepath = '--modulepath=/root/.beaker_fips/modules'
318
+
319
+ module_install_cmd = 'puppet module install simp-fips --target-dir=/root/.beaker_fips/modules'
320
+
321
+ if ENV['BEAKER_fips_module_version']
322
+ module_install_cmd += " --version #{ENV['BEAKER_fips_module_version']}"
323
+ end
324
+
325
+ on(sut, module_install_cmd)
326
+ end
327
+
328
+ # Enable FIPS and then reboot to finish.
329
+ on(sut, %(puppet apply --verbose #{fips_enable_modulepath} -e "class { 'fips': enabled => true }"))
330
+
331
+ # Work around Vagrant and cipher restrictions in EL8+
332
+ #
333
+ # Hopefully, Vagrant will update the used ciphers at some point but who
334
+ # knows when that will be
335
+ opensshserver_config = '/etc/crypto-policies/back-ends/opensshserver.config'
336
+ if file_exists_on(sut, opensshserver_config)
337
+ on(sut, "sed --follow-symlinks -i 's/PubkeyAcceptedKeyTypes=/PubkeyAcceptedKeyTypes=ssh-rsa,/' #{opensshserver_config}")
338
+ end
339
+
340
+ sut.reboot
341
+ end
342
+ end
343
+
344
+ # Collect all 'yum_repos' entries from the host nodeset.
345
+ # The acceptable format is as follows:
346
+ # yum_repos:
347
+ # <repo_name>:
348
+ # url: <URL>
349
+ # gpgkeys:
350
+ # - <URL to GPGKEY1>
351
+ # - <URL to GPGKEY2>
352
+ def enable_yum_repos_on( suts = hosts )
353
+ parallel = (ENV['BEAKER_SIMP_parallel'] == 'yes')
354
+ block_on(suts, :run_in_parallel => parallel) do |sut|
355
+ if sut['yum_repos']
356
+ sut['yum_repos'].each_pair do |repo, metadata|
357
+ repo_manifest = create_yum_resource( repo, metadata)
358
+
359
+ apply_manifest_on(sut, repo_manifest, :catch_failures => true)
360
+ end
361
+ end
362
+ end
363
+ end
364
+
365
+ def create_yum_resource( repo, metadata )
366
+ repo_attrs = [
367
+ :assumeyes,
368
+ :bandwidth,
369
+ :cost,
370
+ :deltarpm_metadata_percentage,
371
+ :deltarpm_percentage,
372
+ :descr,
373
+ :enabled,
374
+ :enablegroups,
375
+ :exclude,
376
+ :failovermethod,
377
+ :gpgcakey,
378
+ :gpgcheck,
379
+ :http_caching,
380
+ :include,
381
+ :includepkgs,
382
+ :keepalive,
383
+ :metadata_expire,
384
+ :metalink,
385
+ :mirrorlist,
386
+ :mirrorlist_expire,
387
+ :priority,
388
+ :protect,
389
+ :provider,
390
+ :proxy,
391
+ :proxy_password,
392
+ :proxy_username,
393
+ :repo_gpgcheck,
394
+ :retries,
395
+ :s3_enabled,
396
+ :skip_if_unavailable,
397
+ :sslcacert,
398
+ :sslclientcert,
399
+ :sslclientkey,
400
+ :sslverify,
401
+ :target,
402
+ :throttle,
403
+ :timeout
404
+ ]
405
+
406
+ repo_manifest = %(yumrepo { #{repo}:)
407
+
408
+ repo_manifest_opts = []
409
+
410
+ # Legacy Support
411
+ urls = !metadata[:url].nil? ? metadata[:url] : metadata[:baseurl]
412
+ if urls
413
+ repo_manifest_opts << 'baseurl => ' + '"' + Array(urls).flatten.join('\n ').gsub('$','\$') + '"'
414
+ end
415
+
416
+ # Legacy Support
417
+ gpgkeys = !metadata[:gpgkeys].nil? ? metadata[:gpgkeys] : metadata[:gpgkey]
418
+ if gpgkeys
419
+ repo_manifest_opts << 'gpgkey => ' + '"' + Array(gpgkeys).flatten.join('\n ').gsub('$','\$') + '"'
420
+ end
421
+
422
+ repo_attrs.each do |attr|
423
+ if metadata[attr]
424
+ repo_manifest_opts << "#{attr} => '#{metadata[attr]}'"
425
+ end
426
+ end
427
+
428
+ repo_manifest = repo_manifest + %(\n#{repo_manifest_opts.join(",\n")}) + "\n}\n"
429
+ end
430
+
431
+ def linux_errata( sut )
432
+ # We need to be able to flip between server and client without issue
433
+ on sut, 'puppet resource group puppet gid=52'
434
+ on sut, 'puppet resource user puppet comment="Puppet" gid="52" uid="52" home="/var/lib/puppet" managehome=true'
435
+
436
+ # Make sure we have a domain on our host
437
+ current_domain = fact_on(sut, 'domain').strip
438
+ hostname = fact_on(sut, 'hostname').strip
439
+
440
+ if current_domain.empty?
441
+ new_fqdn = hostname + '.beaker.test'
442
+
443
+ on(sut, "sed -i 's/#{hostname}.*/#{new_fqdn} #{hostname}/' /etc/hosts")
444
+ on(sut, "echo '#{new_fqdn}' > /etc/hostname", :accept_all_exit_codes => true)
445
+ on(sut, "hostname #{new_fqdn}", :accept_all_exit_codes => true)
446
+
447
+ if sut.file_exist?('/etc/sysconfig/network')
448
+ on(sut, "sed -s '/HOSTNAME=/d' /etc/sysconfig/network")
449
+ on(sut, "echo 'HOSTNAME=#{new_fqdn}' >> /etc/sysconfig/network")
450
+ end
451
+ end
452
+
453
+ if fact_on(sut, 'domain').strip.empty?
454
+ fail("Error: hosts must have an FQDN, got domain='#{current_domain}'")
455
+ end
456
+
457
+ # This may not exist in docker so just skip the whole thing
458
+ if sut.file_exist?('/etc/ssh')
459
+ # SIMP uses a central ssh key location so we prep that spot in case we
460
+ # flip to the SIMP SSH module.
461
+ on(sut, 'mkdir -p /etc/ssh/local_keys')
462
+ on(sut, 'chown -R root:root /etc/ssh/local_keys')
463
+ on(sut, 'chmod 755 /etc/ssh/local_keys')
464
+
465
+ user_info = on(sut, 'getent passwd').stdout.lines
466
+
467
+ # Hash of user => home_dir
468
+ # Exclude silly directories
469
+ # * /
470
+ # * /dev/*
471
+ # * /s?bin
472
+ # * /proc
473
+ user_info = Hash[
474
+ user_info.map do |u|
475
+ u.strip!
476
+ u = u.split(':')
477
+ u[5] =~ %r{^(/|/dev/.*|/s?bin/?.*|/proc/?.*)$} ? [nil] : [u[0], u[5]]
478
+ end
479
+ ]
480
+
481
+ user_info.keys.each do |user|
482
+ src_file = "#{user_info[user]}/.ssh/authorized_keys"
483
+ tgt_file = "/etc/ssh/local_keys/#{user}"
484
+
485
+ on(sut, %{if [ -f "#{src_file}" ]; then cp -a -f "#{src_file}" "#{tgt_file}" && chmod 644 "#{tgt_file}"; fi}, :silent => true)
486
+ end
487
+ end
488
+
489
+ # SIMP uses structured facts, therefore stringify_facts must be disabled
490
+ unless ENV['BEAKER_stringify_facts'] == 'yes'
491
+ on sut, 'puppet config set stringify_facts false'
492
+ end
493
+
494
+ # Occasionally we run across something similar to BKR-561, so to ensure we
495
+ # at least have the host defaults:
496
+ #
497
+ # :hieradatadir is used as a canary here; it isn't the only missing key
498
+ unless sut.host_hash.key? :hieradatadir
499
+ configure_type_defaults_on(sut)
500
+ end
501
+
502
+ if fact_on(sut, 'osfamily') == 'RedHat'
503
+ if fact_on(sut, 'operatingsystem') == 'RedHat'
504
+ RSpec.configure do |c|
505
+ c.before(:all) do
506
+ rhel_rhsm_subscribe(sut)
507
+ end
508
+
509
+ c.after(:all) do
510
+ rhel_rhsm_unsubscribe(sut)
511
+ end
512
+ end
513
+ end
514
+
515
+ enable_yum_repos_on(sut)
516
+
517
+ # net-tools required for netstat utility being used by be_listening
518
+ if fact_on(sut, 'operatingsystemmajrelease') == '7'
519
+ pp = <<-EOS
520
+ package { 'net-tools': ensure => installed }
521
+ EOS
522
+ apply_manifest_on(sut, pp, :catch_failures => false)
523
+ end
524
+
525
+ # Clean up YUM prior to starting our test runs.
526
+ on(sut, 'yum clean all')
527
+ end
528
+ end
529
+
530
+ # Register a RHEL system with a development license
531
+ #
532
+ # Must set BEAKER_RHSM_USER and BEAKER_RHSM_PASS environment variables or pass them in as
533
+ # parameters
534
+ def rhel_rhsm_subscribe(sut, *opts)
535
+ require 'securerandom'
536
+
537
+ rhsm_opts = {
538
+ :username => ENV['BEAKER_RHSM_USER'],
539
+ :password => ENV['BEAKER_RHSM_PASS'],
540
+ :system_name => "#{sut}_beaker_#{Time.now.to_i}_#{SecureRandom.uuid}",
541
+ :repo_list => {
542
+ '7' => [
543
+ 'rhel-7-server-extras-rpms',
544
+ 'rhel-7-server-optional-rpms',
545
+ 'rhel-7-server-rh-common-rpms',
546
+ 'rhel-7-server-rpms',
547
+ 'rhel-7-server-supplementary-rpms'
548
+ ],
549
+ '8' => [
550
+ 'rhel-8-for-x86_64-baseos-rpms',
551
+ 'rhel-8-for-x86_64-supplementary-rpms'
552
+ ]
553
+ }
554
+ }
555
+
556
+ if opts && opts.is_a?(Hash)
557
+ rhsm_opts.merge!(opts)
558
+ end
559
+
560
+ os = fact_on(sut, 'operatingsystem').strip
561
+ os_release = fact_on(sut, 'operatingsystemmajrelease').strip
562
+
563
+ if os == 'RedHat'
564
+ unless rhsm_opts[:username] && rhsm_opts[:password]
565
+ fail("You must set BEAKER_RHSM_USER and BEAKER_RHSM_PASS environment variables to register RHEL systems")
566
+ end
567
+
568
+ sub_status = on(sut, 'subscription-manager status', :accept_all_exit_codes => true)
569
+ unless sub_status.exit_code == 0
570
+ logger.info("Registering #{sut} via subscription-manager")
571
+ on(sut, %{subscription-manager register --auto-attach --name='#{rhsm_opts[:system_name]}' --username='#{rhsm_opts[:username]}' --password='#{rhsm_opts[:password]}'}, :silent => true)
572
+ end
573
+
574
+ if rhsm_opts[:repo_list][os_release]
575
+ rhel_repo_enable(sut, rhsm_opts[:repo_list][os_release])
576
+ else
577
+ logger.warn("simp-beaker-helpers:#{__method__} => Default repos for RHEL '#{os_release}' not found")
578
+ end
579
+
580
+ # Ensure that all users can access the entitlements since we don't know
581
+ # who we'll be running jobs as (often not root)
582
+ on(sut, 'chmod -R ugo+rX /etc/pki/entitlement', :accept_all_exit_codes => true)
583
+ end
584
+ end
585
+
586
+ def sosreport(sut, dest='sosreports')
587
+ sut.install_package('sos')
588
+ on(sut, 'sosreport --batch')
589
+
590
+ files = on(sut, 'ls /var/tmp/sosreport* /tmp/sosreport* 2>/dev/null', :accept_all_exit_codes => true).output.lines.map(&:strip)
591
+
592
+ FileUtils.mkdir_p(dest)
593
+
594
+ files.each do |file|
595
+ scp_from(sut, file, File.absolute_path(dest))
596
+ end
597
+ end
598
+
599
+ def rhel_repo_enable(sut, repos)
600
+ Array(repos).each do |repo|
601
+ on(sut, %{subscription-manager repos --enable #{repo}})
602
+ end
603
+ end
604
+
605
+ def rhel_repo_disable(sut, repos)
606
+ Array(repos).each do |repo|
607
+ on(sut, %{subscription-manager repos --disable #{repo}}, :accept_all_exit_codes => true)
608
+ end
609
+ end
610
+
611
+ def rhel_rhsm_unsubscribe(sut)
612
+ on(sut, %{subscription-manager unregister}, :accept_all_exit_codes => true)
613
+ end
614
+
615
+ # Apply known OS fixes we need to run Beaker on each SUT
616
+ def fix_errata_on( suts = hosts )
617
+ parallel = (ENV['BEAKER_SIMP_parallel'] == 'yes')
618
+ block_on(suts, :run_in_parallel => parallel) do |sut|
619
+ if is_windows?(sut)
620
+ # Load the Windows requirements
621
+ require 'simp/beaker_helpers/windows'
622
+
623
+ # Install the necessary windows certificate for testing
624
+ #
625
+ # https://petersouter.xyz/testing-windows-with-beaker-without-cygwin/
626
+ geotrust_global_ca = <<~EOM.freeze
627
+ -----BEGIN CERTIFICATE-----
628
+ MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
629
+ MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
630
+ YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
631
+ EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
632
+ R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
633
+ 9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
634
+ fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
635
+ iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
636
+ 1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
637
+ bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
638
+ MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
639
+ ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
640
+ uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
641
+ Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
642
+ tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
643
+ PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
644
+ hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
645
+ 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
646
+ -----END CERTIFICATE-----
647
+ EOM
648
+
649
+ install_cert_on_windows(sut, 'geotrustglobal', geotrust_global_ca)
650
+ else
651
+ linux_errata(sut)
652
+ end
653
+ end
654
+
655
+ # Configure and reboot SUTs into FIPS mode
656
+ if ENV['BEAKER_fips'] == 'yes'
657
+ enable_fips_mode_on(suts)
658
+ end
659
+ end
660
+
661
+ # Generate a fake openssl CA + certs for each host on a given SUT
662
+ #
663
+ # The directory structure is the same as what FakeCA drops into keydist/
664
+ #
665
+ # NOTE: This generates everything within an SUT and copies it back out.
666
+ # This is because it is assumed the SUT will have the appropriate
667
+ # openssl in its environment, which may not be true of the host.
668
+ def run_fake_pki_ca_on( ca_sut = master, suts = hosts, local_dir = '' )
669
+ puts "== Fake PKI CA"
670
+ pki_dir = File.expand_path( "../../files/pki", File.dirname(__FILE__))
671
+ host_dir = '/root/pki'
672
+
673
+ ca_sut.mkdir_p(host_dir)
674
+ Dir[ File.join(pki_dir, '*') ].each{|f| copy_to( ca_sut, f, host_dir)}
675
+
676
+ # Collect network information from all SUTs
677
+ #
678
+ # We need this so that we don't insert any common IP addresses into certs
679
+ suts_network_info = {}
680
+
681
+ hosts.each do |host|
682
+ fqdn = fact_on(host, 'fqdn').strip
683
+
684
+ host_entry = { fqdn => [] }
685
+
686
+ # Ensure that all interfaces are active prior to collecting data
687
+ activate_interfaces(host) unless ENV['BEAKER_no_fix_interfaces']
688
+
689
+ # Gather the IP Addresses for the host to embed in the cert
690
+ interfaces = fact_on(host, 'interfaces').strip.split(',')
691
+ interfaces.each do |interface|
692
+ ipaddress = fact_on(host, "ipaddress_#{interface}")
693
+
694
+ next if ipaddress.nil? || ipaddress.empty? || ipaddress.start_with?('127.')
695
+
696
+ host_entry[fqdn] << ipaddress.strip
697
+
698
+ unless host_entry[fqdn].empty?
699
+ suts_network_info[fqdn] = host_entry[fqdn]
700
+ end
701
+ end
702
+ end
703
+
704
+ # Get all of the repeated SUT IP addresses:
705
+ # 1. Create a hash of elements that have a key that is the value and
706
+ # elements that are the same value
707
+ # 2. Grab all elements that have more than one value (therefore, were
708
+ # repeated)
709
+ # 3. Pull out an Array of all of the common element keys for future
710
+ # comparison
711
+ common_ip_addresses = suts_network_info
712
+ .values.flatten
713
+ .group_by{ |x| x }
714
+ .select{|k,v| v.size > 1}
715
+ .keys
716
+
717
+ # generate PKI certs for each SUT
718
+ Dir.mktmpdir do |dir|
719
+ pki_hosts_file = File.join(dir, 'pki.hosts')
720
+
721
+ File.open(pki_hosts_file, 'w') do |fh|
722
+ suts_network_info.each do |fqdn, ipaddresses|
723
+ fh.puts ([fqdn] + (ipaddresses - common_ip_addresses)) .join(',')
724
+ end
725
+ end
726
+
727
+ copy_to(ca_sut, pki_hosts_file, host_dir)
728
+ # generate certs
729
+ on(ca_sut, "cd #{host_dir}; cat #{host_dir}/pki.hosts | xargs bash make.sh")
730
+ end
731
+
732
+ # if a local_dir was provided, copy everything down to it
733
+ unless local_dir.empty?
734
+ FileUtils.mkdir_p local_dir
735
+ scp_from( ca_sut, host_dir, local_dir )
736
+ end
737
+ end
738
+
739
+ # Copy a single SUT's PKI certs (with cacerts) onto an SUT.
740
+ #
741
+ # This simulates the result of pki::copy
742
+ #
743
+ # The directory structure is:
744
+ #
745
+ # SUT_BASE_DIR/
746
+ # pki/
747
+ # cacerts/cacerts.pem
748
+ # # This is a copy of cacerts.pem since cacerts.pem is a
749
+ # # collection of the CA certificates in pupmod-simp-pki
750
+ # cacerts/simp_auto_ca.pem
751
+ # public/fdqn.pub
752
+ # private/fdqn.pem
753
+ def copy_pki_to(sut, local_pki_dir, sut_base_dir = '/etc/pki/simp-testing')
754
+ fqdn = fact_on(sut, 'fqdn')
755
+ sut_pki_dir = File.join( sut_base_dir, 'pki' )
756
+ local_host_pki_tree = File.join(local_pki_dir,'pki','keydist',fqdn)
757
+ local_cacert = File.join(local_pki_dir,'pki','demoCA','cacert.pem')
758
+
759
+ sut.mkdir_p("#{sut_pki_dir}/public")
760
+ sut.mkdir_p("#{sut_pki_dir}/private")
761
+ sut.mkdir_p("#{sut_pki_dir}/cacerts")
762
+ copy_to(sut, "#{local_host_pki_tree}/#{fqdn}.pem", "#{sut_pki_dir}/private/")
763
+ copy_to(sut, "#{local_host_pki_tree}/#{fqdn}.pub", "#{sut_pki_dir}/public/")
764
+
765
+ copy_to(sut, local_cacert, "#{sut_pki_dir}/cacerts/simp_auto_ca.pem")
766
+
767
+ # NOTE: to match pki::copy, 'cacert.pem' is copied to 'cacerts.pem'
768
+ copy_to(sut, local_cacert, "#{sut_pki_dir}/cacerts/cacerts.pem")
769
+
770
+ # Need to hash all of the CA certificates so that apps can use them
771
+ # properly! This must happen on the host itself since it needs to match
772
+ # the native hashing algorithms.
773
+ hash_cmd = <<-EOM.strip
774
+ cd #{sut_pki_dir}/cacerts; \
775
+ for x in *; do \
776
+ if [ ! -h "$x" ]; then \
777
+ `openssl x509 -in $x >/dev/null 2>&1`; \
778
+ if [ $? -eq 0 ]; then \
779
+ hash=`openssl x509 -in $x -hash | head -1`; \
780
+ ln -sf $x $hash.0; \
781
+ fi; \
782
+ fi; \
783
+ done
784
+ EOM
785
+
786
+ on(sut, hash_cmd)
787
+ end
788
+
789
+ # Copy a CA keydist/ directory of CA+host certs into an SUT
790
+ #
791
+ # This simulates the output of FakeCA's gencerts_nopass.sh to keydist/
792
+ def copy_keydist_to( ca_sut = master, host_keydist_dir = nil )
793
+ if !host_keydist_dir
794
+ modulepath = puppet_modulepath_on(ca_sut)
795
+
796
+ host_keydist_dir = "#{modulepath.first}/pki/files/keydist"
797
+ end
798
+ on ca_sut, "rm -rf #{host_keydist_dir}/*"
799
+ ca_sut.mkdir_p(host_keydist_dir)
800
+ on ca_sut, "cp -pR /root/pki/keydist/. #{host_keydist_dir}/"
801
+ on ca_sut, "chgrp -R puppet #{host_keydist_dir}"
802
+ end
803
+
804
+
805
+ # Activate all network interfaces on the target system
806
+ #
807
+ # This is generally needed if the upstream vendor does not activate all
808
+ # interfaces by default (EL7 for example)
809
+ #
810
+ # Can be passed any number of hosts either singly or as an Array
811
+ def activate_interfaces(hosts)
812
+ parallel = (ENV['BEAKER_SIMP_parallel'] == 'yes')
813
+ block_on(hosts, :run_in_parallel => parallel) do |host|
814
+ if host[:platform] =~ /windows/
815
+ puts " -- SKIPPING #{host} because it is windows"
816
+ next
817
+ end
818
+
819
+ interfaces_fact = retry_on(host,'facter interfaces', verbose: true).stdout
820
+
821
+ interfaces = interfaces_fact.strip.split(',')
822
+ interfaces.delete_if { |x| x =~ /^lo/ }
823
+
824
+ interfaces.each do |iface|
825
+ if fact_on(host, "ipaddress_#{iface}").strip.empty?
826
+ on(host, "ifup #{iface}", :accept_all_exit_codes => true)
827
+ end
828
+ end
829
+ end
830
+ end
831
+
832
+ ## Inline Hiera Helpers ##
833
+ ## These will be integrated into core Beaker at some point ##
834
+
835
+ # Set things up for the inline hieradata functions 'set_hieradata_on'
836
+ # and 'clear_temp_hieradata'
837
+ #
838
+ #
839
+ require 'rspec'
840
+ RSpec.configure do |c|
841
+ c.before(:all) do
842
+ @temp_hieradata_dirs = @temp_hieradata_dirs || []
843
+ end
844
+
845
+ # We can't guarantee that the upstream vendor isn't disabling interfaces so
846
+ # we need to turn them on at each context run
847
+ c.before(:context) do
848
+ activate_interfaces(hosts) unless ENV['BEAKER_no_fix_interfaces']
849
+ end
850
+
851
+ c.after(:all) do
852
+ clear_temp_hieradata
853
+ end
854
+ end
855
+
856
+ # Return the contents of a file on the remote host
857
+ #
858
+ # @param sut [Host] the host upon which to operate
859
+ # @param path [String] the path to the target file
860
+ # @param trim [Boolean] remove leading and trailing whitespace
861
+ #
862
+ # @return [String, nil] the contents of the remote file
863
+ def file_content_on(sut, path, trim=true)
864
+ file_content = nil
865
+
866
+ if file_exists_on(sut, path)
867
+ Dir.mktempdir do |dir|
868
+ scp_from(host, path, dir)
869
+
870
+ file_content = File.read(File.basename(path))
871
+ end
872
+ end
873
+
874
+ return file_content
875
+ end
876
+
877
+ # Retrieve the default hiera.yaml path
878
+ #
879
+ # @param sut [Host] one host to act upon
880
+ #
881
+ # @returns [Hash] path to the default environment's hiera.yaml
882
+ def hiera_config_path_on(sut)
883
+ File.join(puppet_environment_path_on(sut), 'hiera.yaml')
884
+ end
885
+
886
+ # Retrieve the default environment hiera.yaml
887
+ #
888
+ # @param sut [Host] one host to act upon
889
+ #
890
+ # @returns [Hash] content of the default environment's hiera.yaml
891
+ def get_hiera_config_on(sut)
892
+ file_content_on(sut, hiera_config_path_on(sut))
893
+ end
894
+
895
+ # Updates the default environment hiera.yaml
896
+ #
897
+ # @param sut [Host] One host to act upon
898
+ # @param hiera_yaml [Hash, String] The data to place into hiera.yaml
899
+ #
900
+ # @returns [void]
901
+ def set_hiera_config_on(sut, hiera_yaml)
902
+ hiera_yaml = hiera_yaml.to_yaml if hiera_yaml.is_a?(Hash)
903
+
904
+ create_remote_file(sut, hiera_config_path_on(sut), hiera_yaml)
905
+ end
906
+
907
+ # Writes a YAML file in the Hiera :datadir of a Beaker::Host.
908
+ #
909
+ # @note This is useless unless Hiera is configured to use the data file.
910
+ # @see `#set_hiera_config_on`
911
+ #
912
+ # @param sut [Array<Host>, String, Symbol] One or more hosts to act upon.
913
+ #
914
+ # @param hieradata [Hash, String] The full hiera data structure to write to
915
+ # the system.
916
+ #
917
+ # @param terminus [String] DEPRECATED - This will be removed in a future
918
+ # release and currently has no effect.
919
+ #
920
+ # @return [Nil]
921
+ #
922
+ # @note This creates a tempdir on the host machine which should be removed
923
+ # using `#clear_temp_hieradata` in the `after(:all)` hook. It may also be
924
+ # retained for debugging purposes.
925
+ #
926
+ def write_hieradata_to(sut, hieradata, terminus = 'deprecated')
927
+ @temp_hieradata_dirs ||= []
928
+ data_dir = Dir.mktmpdir('hieradata')
929
+ @temp_hieradata_dirs << data_dir
930
+
931
+ fh = File.open(File.join(data_dir, 'common.yaml'), 'w')
932
+ if hieradata.is_a?(String)
933
+ fh.puts(hieradata)
934
+ else
935
+ fh.puts(hieradata.to_yaml)
936
+ end
937
+ fh.close
938
+
939
+ copy_hiera_data_to sut, File.join(data_dir, 'common.yaml')
940
+ end
941
+
942
+ # A shim to stand in for the now deprecated copy_hiera_data_to function
943
+ #
944
+ # @param sut [Host] One host to act upon
945
+ #
946
+ # @param [Path] File containing hiera data
947
+ def copy_hiera_data_to(sut, path)
948
+ copy_to(sut, path, hiera_datadir(sut))
949
+ end
950
+
951
+ # A shim to stand in for the now deprecated hiera_datadir function
952
+ #
953
+ # Note: This may not work if you've shoved data somewhere that is not the
954
+ # default and/or are manipulating the default hiera.yaml.
955
+ #
956
+ # @param sut [Host] One host to act upon
957
+ #
958
+ # @returns [String] Path to the Hieradata directory on the target system
959
+ def hiera_datadir(sut)
960
+ # This output lets us know where Hiera is configured to look on the system
961
+ puppet_lookup_info = on(sut, 'puppet lookup --explain test__simp__test').output.strip.lines
962
+
963
+ if sut.puppet_configprint['manifest'].nil? || sut.puppet_configprint['manifest'].empty?
964
+ fail("No output returned from `puppet config print manifest` on #{sut}")
965
+ end
966
+
967
+ puppet_env_path = puppet_environment_path_on(sut)
968
+
969
+ # We'll just take the first match since Hiera will find things there
970
+ puppet_lookup_info = puppet_lookup_info.grep(/Path "/).grep(Regexp.new(puppet_env_path))
971
+
972
+ # Grep always returns an Array
973
+ if puppet_lookup_info.empty?
974
+ fail("Could not determine hiera data directory under #{puppet_env_path} on #{sut}")
975
+ end
976
+
977
+ # Snag the actual path without the extra bits
978
+ puppet_lookup_info = puppet_lookup_info.first.strip.split('"').last
979
+
980
+ # Make the parent directories exist
981
+ sut.mkdir_p(File.dirname(puppet_lookup_info))
982
+
983
+ # We just want the data directory name
984
+ datadir_name = puppet_lookup_info.split(puppet_env_path).last
985
+
986
+ # Grab the file separator to add back later
987
+ file_sep = datadir_name[0]
988
+
989
+ # Snag the first entry (this is the data directory)
990
+ datadir_name = datadir_name.split(file_sep)[1]
991
+
992
+ # Constitute the full path to the data directory
993
+ datadir_path = puppet_env_path + file_sep + datadir_name
994
+
995
+ # Return the path to the data directory
996
+ return datadir_path
997
+ end
998
+
999
+ # Write the provided data structure to Hiera's :datadir and configure Hiera to
1000
+ # use that data exclusively.
1001
+ #
1002
+ # @note This is authoritative. It manages both Hiera data and configuration,
1003
+ # so it may not be used with other Hiera data sources.
1004
+ #
1005
+ # @param sut [Array<Host>, String, Symbol] One or more hosts to act upon.
1006
+ #
1007
+ # @param heradata [Hash, String] The full hiera data structure to write to
1008
+ # the system.
1009
+ #
1010
+ # @param terminus [String] DEPRECATED - Will be removed in a future release.
1011
+ # All hieradata is written to the first discovered path via 'puppet
1012
+ # lookup'
1013
+ #
1014
+ # @return [Nil]
1015
+ #
1016
+ def set_hieradata_on(sut, hieradata, terminus = 'deprecated')
1017
+ write_hieradata_to sut, hieradata
1018
+ end
1019
+
1020
+
1021
+ # Clean up all temporary hiera data files.
1022
+ #
1023
+ # Meant to be called from after(:all)
1024
+ def clear_temp_hieradata
1025
+ if @temp_hieradata_dirs && !@temp_hieradata_dirs.empty?
1026
+ @temp_hieradata_dirs.each do |data_dir|
1027
+ if File.exists?(data_dir)
1028
+ FileUtils.rm_r(data_dir)
1029
+ end
1030
+ end
1031
+ end
1032
+ end
1033
+
1034
+
1035
+ # pluginsync custom facts for all modules
1036
+ def pluginsync_on( suts = hosts )
1037
+ puts "== pluginsync_on'" if ENV['BEAKER_helpers_verbose']
1038
+ pluginsync_manifest =<<-PLUGINSYNC_MANIFEST
1039
+ file { $::settings::libdir:
1040
+ ensure => directory,
1041
+ source => 'puppet:///plugins',
1042
+ recurse => true,
1043
+ purge => true,
1044
+ backup => false,
1045
+ noop => false
1046
+ }
1047
+ PLUGINSYNC_MANIFEST
1048
+ parallel = (ENV['BEAKER_SIMP_parallel'] == 'yes')
1049
+ apply_manifest_on(hosts, pluginsync_manifest, :run_in_parallel => parallel)
1050
+ end
1051
+
1052
+
1053
+ # Looks up latest `puppet-agent` version by the version of its `puppet` gem
1054
+ #
1055
+ # @param puppet_version [String] target Puppet gem version. Works with
1056
+ # Gemfile comparison syntax (e.g., '4.0', '= 4.2', '~> 4.3.1', '> 5.1, < 5.5')
1057
+ #
1058
+ # @return [String,Nil] the `puppet-agent` version or nil
1059
+ #
1060
+ def latest_puppet_agent_version_for( puppet_version )
1061
+ return nil if puppet_version.nil?
1062
+
1063
+ require 'rubygems/requirement'
1064
+ require 'rubygems/version'
1065
+ require 'yaml'
1066
+
1067
+ _puppet_version = puppet_version.strip.split(',')
1068
+
1069
+
1070
+ @agent_version_table ||= YAML.load_file(
1071
+ File.expand_path(
1072
+ '../../files/puppet-agent-versions.yaml',
1073
+ File.dirname(__FILE__)
1074
+ )).fetch('version_mappings')
1075
+ _pair = @agent_version_table.find do |k,v|
1076
+ Gem::Requirement.new(_puppet_version).satisfied_by?(Gem::Version.new(k))
1077
+ end
1078
+ result = _pair ? _pair.last : nil
1079
+
1080
+ # If we didn't get a match, go look for published rubygems
1081
+ unless result
1082
+ puppet_gems = nil
1083
+
1084
+ Bundler.with_clean_env do
1085
+ puppet_gems = %x(gem search -ra -e puppet).match(/\((.+)\)/)
1086
+ end
1087
+
1088
+ if puppet_gems
1089
+ puppet_gems = puppet_gems[1].split(/,?\s+/).select{|x| x =~ /^\d/}
1090
+
1091
+ # If we don't have a full version string, we need to massage it for the
1092
+ # match.
1093
+ begin
1094
+ if _puppet_version.size == 1
1095
+ Gem::Version.new(_puppet_version[0])
1096
+ if _puppet_version[0].count('.') < 2
1097
+ _puppet_version = "~> #{_puppet_version[0]}"
1098
+ end
1099
+ end
1100
+ rescue ArgumentError
1101
+ # this means _puppet_version is not just a version, but a version
1102
+ # specifier such as "= 5.2.3", "<= 5.1", "> 4", "~> 4.10.7"
1103
+ end
1104
+
1105
+ result = puppet_gems.find do |ver|
1106
+ Gem::Requirement.new(_puppet_version).satisfied_by?(Gem::Version.new(ver))
1107
+ end
1108
+ end
1109
+ end
1110
+
1111
+ return result
1112
+ end
1113
+
1114
+ # returns hash with :puppet_install_version, :puppet_collection,
1115
+ # and :puppet_install_type keys determined from environment variables,
1116
+ # host settings, and/or defaults
1117
+ #
1118
+ # NOTE: BEAKER_PUPPET_AGENT_VERSION or PUPPET_INSTALL_VERSION or
1119
+ # PUPPET_VERSION takes precedence over BEAKER_PUPPET_COLLECTION
1120
+ # or host.options['puppet_collection'], when both a puppet
1121
+ # install version and a puppet collection are specified. This is
1122
+ # because the puppet install version can specify more precise
1123
+ # version information than is available from a puppet collection.
1124
+ def get_puppet_install_info
1125
+ # The first match is internal Beaker and the second is legacy SIMP
1126
+ puppet_install_version = ENV['BEAKER_PUPPET_AGENT_VERSION'] || ENV['PUPPET_INSTALL_VERSION'] || ENV['PUPPET_VERSION']
1127
+
1128
+ if puppet_install_version and !puppet_install_version.strip.empty?
1129
+ puppet_agent_version = latest_puppet_agent_version_for(puppet_install_version.strip)
1130
+ end
1131
+
1132
+ if puppet_agent_version.nil?
1133
+ if puppet_collection = (ENV['BEAKER_PUPPET_COLLECTION'] || host.options['puppet_collection'])
1134
+ if puppet_collection =~ /puppet(\d+)/
1135
+ puppet_install_version = "~> #{$1}"
1136
+ puppet_agent_version = latest_puppet_agent_version_for(puppet_install_version)
1137
+ else
1138
+ raise("Error: Puppet Collection '#{puppet_collection}' must match /puppet(\\d+)/")
1139
+ end
1140
+ else
1141
+ puppet_agent_version = latest_puppet_agent_version_for(DEFAULT_PUPPET_AGENT_VERSION)
1142
+ end
1143
+ end
1144
+
1145
+ if puppet_collection.nil?
1146
+ base_version = puppet_agent_version.to_i
1147
+ puppet_collection = "puppet#{base_version}" if base_version >= 5
1148
+ end
1149
+
1150
+ {
1151
+ :puppet_install_version => puppet_agent_version,
1152
+ :puppet_collection => puppet_collection,
1153
+ :puppet_install_type => ENV.fetch('PUPPET_INSTALL_TYPE', 'agent')
1154
+ }
1155
+ end
1156
+
1157
+
1158
+ # Replacement for `install_puppet` in spec_helper_acceptance.rb
1159
+ def install_puppet
1160
+ install_info = get_puppet_install_info
1161
+
1162
+ # In case Beaker needs this info internally
1163
+ ENV['PUPPET_INSTALL_VERSION'] = install_info[:puppet_install_version]
1164
+ if install_info[:puppet_collection]
1165
+ ENV['BEAKER_PUPPET_COLLECTION'] = install_info[:puppet_collection]
1166
+ end
1167
+
1168
+ require 'beaker/puppet_install_helper'
1169
+
1170
+ run_puppet_install_helper(install_info[:puppet_install_type], install_info[:puppet_install_version])
1171
+ end
1172
+
1173
+ # Configure all SIMP repos on a host and enable all but those listed in the disable list
1174
+ #
1175
+ # @param sut Host on which to configure SIMP repos
1176
+ # @param disable List of SIMP repos to disable
1177
+ # @raise if disable contains an invalid repo name.
1178
+ #
1179
+ # Examples:
1180
+ # install_simp_repos( myhost ) # install all the repos an enable them.
1181
+ # install_simp_repos( myhost, ['simp']) # install the repos but disable the simp repo.
1182
+ #
1183
+ # Current set of valid SIMP repo names:
1184
+ # 'simp'
1185
+ # 'simp_deps'
1186
+ #
1187
+ def install_simp_repos(sut, disable = [] )
1188
+ repos = {
1189
+ 'simp' => {
1190
+ :baseurl => 'https://packagecloud.io/simp-project/6_X/el/$releasever/$basearch',
1191
+ :gpgkey => ['https://raw.githubusercontent.com/NationalSecurityAgency/SIMP/master/GPGKEYS/RPM-GPG-KEY-SIMP',
1192
+ 'https://download.simp-project.com/simp/GPGKEYS/RPM-GPG-KEY-SIMP-6'
1193
+ ],
1194
+ :gpgcheck => 1,
1195
+ :sslverify => 1,
1196
+ :sslcacert => '/etc/pki/tls/certs/ca-bundle.crt',
1197
+ :metadata_expire => 300
1198
+ },
1199
+ 'simp_deps' => {
1200
+ :baseurl => 'https://packagecloud.io/simp-project/6_X_Dependencies/el/$releasever/$basearch',
1201
+ :gpgkey => ['https://raw.githubusercontent.com/NationalSecurityAgency/SIMP/master/GPGKEYS/RPM-GPG-KEY-SIMP',
1202
+ 'https://download.simp-project.com/simp/GPGKEYS/RPM-GPG-KEY-SIMP-6',
1203
+ 'https://yum.puppet.com/RPM-GPG-KEY-puppetlabs',
1204
+ 'https://yum.puppet.com/RPM-GPG-KEY-puppet',
1205
+ 'https://apt.postgresql.org/pub/repos/yum/RPM-GPG-KEY-PGDG-96',
1206
+ 'https://artifacts.elastic.co/GPG-KEY-elasticsearch',
1207
+ 'https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana',
1208
+ 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-$releasever'
1209
+ ],
1210
+ :gpgcheck => 1,
1211
+ :sslverify => 1,
1212
+ :sslcacert => '/etc/pki/tls/certs/ca-bundle.crt',
1213
+ :metadata_expire => 300
1214
+ }
1215
+ }
1216
+ # Verify that the repos passed to disable are in the list of valid repos
1217
+ disable.each { |d|
1218
+ unless repos.has_key?(d)
1219
+ raise("ERROR: install_simp_repo - disable contains invalid SIMP repo '#{d}'.")
1220
+ end
1221
+ }
1222
+ repo_manifest = ''
1223
+ repos.each { | repo, metadata|
1224
+ metadata[:enabled] = disable.include?(repo) ? 0 : 1
1225
+ repo_manifest << create_yum_resource(repo, metadata)
1226
+ }
1227
+ apply_manifest_on(sut, repo_manifest, :catch_failures => true)
1228
+ end
1229
+ end
1230
+
1231
+