simp-beaker-helpers 1.18.8

Sign up to get free protection for your applications and to get access to all the features.
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
+