puppet 6.3.0-universal-darwin → 6.4.0-universal-darwin

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/CODEOWNERS +30 -0
  3. data/Gemfile.lock +9 -9
  4. data/lib/puppet.rb +13 -0
  5. data/lib/puppet/application/agent.rb +8 -12
  6. data/lib/puppet/application/device.rb +2 -3
  7. data/lib/puppet/application/filebucket.rb +6 -1
  8. data/lib/puppet/application/ssl.rb +102 -55
  9. data/lib/puppet/configurer.rb +8 -7
  10. data/lib/puppet/defaults.rb +3 -1
  11. data/lib/puppet/file_system.rb +24 -4
  12. data/lib/puppet/file_system/file_impl.rb +25 -0
  13. data/lib/puppet/file_system/jruby.rb +23 -0
  14. data/lib/puppet/file_system/windows.rb +84 -0
  15. data/lib/puppet/indirector/rest.rb +4 -2
  16. data/lib/puppet/loaders.rb +1 -0
  17. data/lib/puppet/network/http.rb +1 -0
  18. data/lib/puppet/network/http/base_pool.rb +18 -0
  19. data/lib/puppet/network/http/connection.rb +49 -17
  20. data/lib/puppet/network/http/nocache_pool.rb +9 -4
  21. data/lib/puppet/network/http/pool.rb +10 -11
  22. data/lib/puppet/network/http/session.rb +3 -2
  23. data/lib/puppet/network/http_pool.rb +32 -0
  24. data/lib/puppet/pops/loader/generic_plan_instantiator.rb +28 -0
  25. data/lib/puppet/pops/loader/loader_paths.rb +46 -10
  26. data/lib/puppet/pops/loader/module_loaders.rb +10 -3
  27. data/lib/puppet/provider/file/windows.rb +49 -1
  28. data/lib/puppet/provider/package/windows.rb +5 -1
  29. data/lib/puppet/reports/http.rb +2 -1
  30. data/lib/puppet/rest/client.rb +7 -3
  31. data/lib/puppet/rest/routes.rb +9 -44
  32. data/lib/puppet/ssl.rb +6 -0
  33. data/lib/puppet/ssl/error.rb +26 -0
  34. data/lib/puppet/ssl/host.rb +9 -92
  35. data/lib/puppet/ssl/ssl_context.rb +30 -0
  36. data/lib/puppet/ssl/ssl_provider.rb +232 -0
  37. data/lib/puppet/ssl/state_machine.rb +261 -0
  38. data/lib/puppet/ssl/validator.rb +1 -0
  39. data/lib/puppet/ssl/validator/default_validator.rb +1 -0
  40. data/lib/puppet/ssl/validator/no_validator.rb +2 -0
  41. data/lib/puppet/ssl/verifier.rb +134 -0
  42. data/lib/puppet/ssl/verifier_adapter.rb +48 -0
  43. data/lib/puppet/test/test_helper.rb +2 -1
  44. data/lib/puppet/type/exec.rb +30 -6
  45. data/lib/puppet/type/file/mode.rb +6 -1
  46. data/lib/puppet/type/file/source.rb +2 -2
  47. data/lib/puppet/type/filebucket.rb +12 -8
  48. data/lib/puppet/type/user.rb +14 -1
  49. data/lib/puppet/util/connection.rb +10 -5
  50. data/lib/puppet/util/feature.rb +11 -2
  51. data/lib/puppet/util/http_proxy.rb +3 -2
  52. data/lib/puppet/util/pidlock.rb +1 -1
  53. data/lib/puppet/util/ssl.rb +1 -10
  54. data/lib/puppet/util/windows/security.rb +29 -8
  55. data/lib/puppet/version.rb +1 -1
  56. data/lib/puppet/x509.rb +7 -0
  57. data/lib/puppet/x509/cert_provider.rb +286 -0
  58. data/lib/puppet/x509/pem_store.rb +55 -0
  59. data/locales/ja/puppet.po +740 -590
  60. data/locales/puppet.pot +433 -208
  61. data/man/man5/puppet.conf.5 +6 -3
  62. data/man/man8/puppet-agent.8 +1 -1
  63. data/man/man8/puppet-apply.8 +1 -1
  64. data/man/man8/puppet-catalog.8 +1 -1
  65. data/man/man8/puppet-config.8 +1 -1
  66. data/man/man8/puppet-describe.8 +1 -1
  67. data/man/man8/puppet-device.8 +1 -1
  68. data/man/man8/puppet-doc.8 +1 -1
  69. data/man/man8/puppet-epp.8 +1 -1
  70. data/man/man8/puppet-facts.8 +1 -1
  71. data/man/man8/puppet-filebucket.8 +6 -2
  72. data/man/man8/puppet-generate.8 +1 -1
  73. data/man/man8/puppet-help.8 +1 -1
  74. data/man/man8/puppet-key.8 +1 -1
  75. data/man/man8/puppet-lookup.8 +1 -1
  76. data/man/man8/puppet-man.8 +1 -1
  77. data/man/man8/puppet-module.8 +1 -1
  78. data/man/man8/puppet-node.8 +1 -1
  79. data/man/man8/puppet-parser.8 +1 -1
  80. data/man/man8/puppet-plugin.8 +1 -1
  81. data/man/man8/puppet-report.8 +1 -1
  82. data/man/man8/puppet-resource.8 +1 -1
  83. data/man/man8/puppet-script.8 +1 -1
  84. data/man/man8/puppet-ssl.8 +5 -1
  85. data/man/man8/puppet-status.8 +1 -1
  86. data/man/man8/puppet.8 +2 -2
  87. data/spec/fixtures/ssl/127.0.0.1-key.pem +67 -0
  88. data/spec/fixtures/ssl/127.0.0.1.pem +48 -0
  89. data/spec/fixtures/ssl/bad-basic-constraints.pem +59 -0
  90. data/spec/fixtures/ssl/bad-int-basic-constraints.pem +59 -0
  91. data/spec/fixtures/ssl/ca.pem +59 -0
  92. data/spec/fixtures/ssl/crl.pem +30 -0
  93. data/spec/fixtures/ssl/encrypted-key.pem +70 -0
  94. data/spec/fixtures/ssl/intermediate-agent-crl.pem +31 -0
  95. data/spec/fixtures/ssl/intermediate-agent.pem +60 -0
  96. data/spec/fixtures/ssl/intermediate-crl.pem +36 -0
  97. data/spec/fixtures/ssl/intermediate.pem +60 -0
  98. data/spec/fixtures/ssl/netlock-arany-utf8.pem +23 -0
  99. data/spec/fixtures/ssl/pluto-key.pem +67 -0
  100. data/spec/fixtures/ssl/pluto.pem +44 -0
  101. data/spec/fixtures/ssl/request-key.pem +67 -0
  102. data/spec/fixtures/ssl/request.pem +39 -0
  103. data/spec/fixtures/ssl/revoked-key.pem +67 -0
  104. data/spec/fixtures/ssl/revoked.pem +44 -0
  105. data/spec/fixtures/ssl/signed-key.pem +67 -0
  106. data/spec/fixtures/ssl/signed.pem +44 -0
  107. data/spec/fixtures/ssl/tampered-cert.pem +44 -0
  108. data/spec/fixtures/ssl/tampered-csr.pem +39 -0
  109. data/spec/integration/network/http_pool_spec.rb +222 -0
  110. data/spec/integration/provider/file/windows_spec.rb +162 -0
  111. data/spec/integration/rest/client_spec.rb +73 -0
  112. data/spec/integration/type/file_spec.rb +0 -19
  113. data/spec/lib/puppet/test_ca.rb +87 -50
  114. data/spec/lib/puppet_spec/fixtures.rb +20 -0
  115. data/spec/lib/puppet_spec/https.rb +84 -0
  116. data/spec/unit/application/agent_spec.rb +29 -30
  117. data/spec/unit/application/device_spec.rb +12 -49
  118. data/spec/unit/application/ssl_spec.rb +24 -38
  119. data/spec/unit/configurer_spec.rb +11 -11
  120. data/spec/unit/file_system/uniquefile_spec.rb +6 -0
  121. data/spec/unit/file_system_spec.rb +214 -0
  122. data/spec/unit/indirector/rest_spec.rb +3 -3
  123. data/spec/unit/network/http/connection_spec.rb +30 -90
  124. data/spec/unit/network/http/factory_spec.rb +1 -0
  125. data/spec/unit/network/http/nocache_pool_spec.rb +8 -8
  126. data/spec/unit/network/http/pool_spec.rb +63 -33
  127. data/spec/unit/network/http/session_spec.rb +8 -1
  128. data/spec/unit/network/http_pool_spec.rb +36 -0
  129. data/spec/unit/pops/loaders/loader_spec.rb +26 -1
  130. data/spec/unit/provider/package/windows_spec.rb +12 -1
  131. data/spec/unit/reports/http_spec.rb +7 -7
  132. data/spec/unit/rest/client_spec.rb +4 -6
  133. data/spec/unit/ssl/host_spec.rb +39 -33
  134. data/spec/unit/ssl/ssl_provider_spec.rb +428 -0
  135. data/spec/unit/ssl/state_machine_spec.rb +502 -0
  136. data/spec/unit/ssl/verifier_spec.rb +123 -0
  137. data/spec/unit/type/exec_spec.rb +63 -0
  138. data/spec/unit/type/file/source_spec.rb +5 -5
  139. data/spec/unit/type/filebucket_spec.rb +8 -6
  140. data/spec/unit/util/feature_spec.rb +2 -2
  141. data/spec/unit/util/storage_spec.rb +19 -19
  142. data/spec/unit/x509/cert_provider_spec.rb +527 -0
  143. data/spec/unit/x509/pem_store_spec.rb +160 -0
  144. data/tasks/generate_cert_fixtures.rake +158 -0
  145. metadata +78 -4
  146. data/MAINTAINERS +0 -47
  147. data/lib/puppet/rest/ssl_context.rb +0 -13
@@ -0,0 +1,502 @@
1
+ require 'spec_helper'
2
+ require 'webmock/rspec'
3
+ require 'puppet_spec/files'
4
+
5
+ require 'puppet/ssl'
6
+
7
+ describe Puppet::SSL::StateMachine, unless: Puppet::Util::Platform.jruby? do
8
+ include PuppetSpec::Files
9
+
10
+ before(:each) do
11
+ WebMock.disable_net_connect!
12
+
13
+ Net::HTTP.any_instance.stubs(:start)
14
+ Net::HTTP.any_instance.stubs(:finish)
15
+ end
16
+
17
+ let(:machine) { described_class.new }
18
+ let(:cacert_pem) { cacert.to_pem }
19
+ let(:cacert) { cert_fixture('ca.pem') }
20
+ let(:cacerts) { [cacert] }
21
+
22
+ let(:crl_pem) { crl.to_pem }
23
+ let(:crl) { crl_fixture('crl.pem') }
24
+ let(:crls) { [crl] }
25
+
26
+ let(:private_key) { key_fixture('signed-key.pem') }
27
+ let(:client_cert) { cert_fixture('signed.pem') }
28
+
29
+ before(:each) do
30
+ WebMock.disable_net_connect!
31
+
32
+ Net::HTTP.any_instance.stubs(:start)
33
+ Net::HTTP.any_instance.stubs(:finish)
34
+ end
35
+
36
+ context 'when ensuring CA certs and CRLs' do
37
+ it 'returns an SSLContext with the loaded CA certs and CRLs' do
38
+ Puppet::X509::CertProvider.any_instance.stubs(:load_cacerts).returns(cacerts)
39
+ Puppet::X509::CertProvider.any_instance.stubs(:load_crls).returns(crls)
40
+
41
+ ssl_context = machine.ensure_ca_certificates
42
+
43
+ expect(ssl_context[:cacerts]).to eq(cacerts)
44
+ expect(ssl_context[:crls]).to eq(crls)
45
+ expect(ssl_context[:verify_peer]).to eq(true)
46
+ end
47
+ end
48
+
49
+ context 'when ensuring a client cert' do
50
+ it 'returns an SSLContext with the loaded CA certs, CRLs, private key and client cert' do
51
+ Puppet::X509::CertProvider.any_instance.stubs(:load_cacerts).returns(cacerts)
52
+ Puppet::X509::CertProvider.any_instance.stubs(:load_crls).returns(crls)
53
+ Puppet::X509::CertProvider.any_instance.stubs(:load_private_key).returns(private_key)
54
+ Puppet::X509::CertProvider.any_instance.stubs(:load_client_cert).returns(client_cert)
55
+
56
+ ssl_context = machine.ensure_client_certificate
57
+
58
+ expect(ssl_context[:cacerts]).to eq(cacerts)
59
+ expect(ssl_context[:crls]).to eq(crls)
60
+ expect(ssl_context[:verify_peer]).to eq(true)
61
+ expect(ssl_context[:private_key]).to eq(private_key)
62
+ expect(ssl_context[:client_cert]).to eq(client_cert)
63
+ end
64
+ end
65
+
66
+ context 'NeedCACerts' do
67
+ let(:state) { Puppet::SSL::StateMachine::NeedCACerts.new(machine) }
68
+
69
+ before :each do
70
+ Puppet[:localcacert] = tmpfile('needcacerts')
71
+ end
72
+
73
+ it 'transitions to NeedCRLs state' do
74
+ Puppet::X509::CertProvider.any_instance.stubs(:load_cacerts).returns(cacerts)
75
+
76
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::NeedCRLs)
77
+ end
78
+
79
+ it 'loads existing CA certs' do
80
+ Puppet::X509::CertProvider.any_instance.stubs(:load_cacerts).returns(cacerts)
81
+
82
+ st = state.next_state
83
+ expect(st.ssl_context[:cacerts]).to eq(cacerts)
84
+ end
85
+
86
+ it 'fetches and saves CA certs' do
87
+ Puppet::X509::CertProvider.any_instance.stubs(:load_cacerts).returns(nil)
88
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: cacert_pem)
89
+
90
+ st = state.next_state
91
+ expect(st.ssl_context[:cacerts].map(&:to_pem)).to eq(cacerts.map(&:to_pem))
92
+ expect(File).to be_exist(Puppet[:localcacert])
93
+ end
94
+
95
+ it "does not verify the server's cert if there are no local CA certs" do
96
+ Puppet::X509::CertProvider.any_instance.stubs(:load_cacerts).returns(nil)
97
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: cacert_pem)
98
+ Puppet::X509::CertProvider.any_instance.stubs(:save_cacerts)
99
+
100
+ Net::HTTP.any_instance.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
101
+
102
+ state.next_state
103
+ end
104
+
105
+ it 'raises if the server returns 404' do
106
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 404)
107
+
108
+ expect {
109
+ state.next_state
110
+ }.to raise_error(Puppet::Error, /CA certificate is missing from the server/)
111
+ end
112
+
113
+ it 'raises if there is a different error' do
114
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: [500, 'Internal Server Error'])
115
+
116
+ expect {
117
+ state.next_state
118
+ }.to raise_error(Puppet::Error, /Could not download CA certificate: Internal Server Error/)
119
+ end
120
+
121
+ it 'raises if CA certs are invalid' do
122
+ Puppet::X509::CertProvider.any_instance.stubs(:load_cacerts).returns(nil)
123
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: '')
124
+
125
+ expect {
126
+ state.next_state
127
+ }.to raise_error(OpenSSL::X509::CertificateError)
128
+ end
129
+
130
+ it 'does not save invalid CA certs' do
131
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: <<~END)
132
+ -----BEGIN CERTIFICATE-----
133
+ MIIBpDCCAQ2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRUZXN0
134
+ END
135
+
136
+ state.next_state rescue OpenSSL::X509::CertificateError
137
+
138
+ expect(File).to_not exist(Puppet[:localcacert])
139
+ end
140
+ end
141
+
142
+ context 'NeedCRLs' do
143
+ let(:ssl_context) { Puppet::SSL::SSLContext.new(cacerts: cacerts)}
144
+ let(:state) { Puppet::SSL::StateMachine::NeedCRLs.new(machine, ssl_context) }
145
+
146
+ before :each do
147
+ Puppet[:hostcrl] = tmpfile('needcrls')
148
+ end
149
+
150
+ it 'transitions to NeedKey state' do
151
+ Puppet::X509::CertProvider.any_instance.stubs(:load_crls).returns(crls)
152
+
153
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::NeedKey)
154
+ end
155
+
156
+ it 'loads existing CRLs' do
157
+ Puppet::X509::CertProvider.any_instance.stubs(:load_crls).returns(crls)
158
+
159
+ st = state.next_state
160
+ expect(st.ssl_context[:crls]).to eq(crls)
161
+ end
162
+
163
+ it 'fetches and saves CRLs' do
164
+ Puppet::X509::CertProvider.any_instance.stubs(:load_crls).returns(nil)
165
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: crl_pem)
166
+
167
+ st = state.next_state
168
+ expect(st.ssl_context[:crls].map(&:to_pem)).to eq(crls.map(&:to_pem))
169
+ expect(File).to be_exist(Puppet[:hostcrl])
170
+ end
171
+
172
+ it "verifies the server's certificate when fetching the CRL" do
173
+ Puppet::X509::CertProvider.any_instance.stubs(:load_crls).returns(nil)
174
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: crl_pem)
175
+ Puppet::X509::CertProvider.any_instance.stubs(:save_crls)
176
+
177
+ Net::HTTP.any_instance.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
178
+
179
+ state.next_state
180
+ end
181
+
182
+ it 'raises if the server returns 404' do
183
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 404)
184
+
185
+ expect {
186
+ state.next_state
187
+ }.to raise_error(Puppet::Error, /CRL is missing from the server/)
188
+ end
189
+
190
+ it 'raises if there is a different error' do
191
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: [500, 'Internal Server Error'])
192
+
193
+ expect {
194
+ state.next_state
195
+ }.to raise_error(Puppet::Error, /Could not download CRLs: Internal Server Error/)
196
+ end
197
+
198
+ it 'raises if CRLs are invalid' do
199
+ Puppet::X509::CertProvider.any_instance.stubs(:load_crls).returns(nil)
200
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: '')
201
+
202
+ expect {
203
+ state.next_state
204
+ }.to raise_error(OpenSSL::X509::CRLError)
205
+ end
206
+
207
+ it 'does not save invalid CRLs' do
208
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: <<~END)
209
+ -----BEGIN X509 CRL-----
210
+ MIIBCjB1AgEBMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNVBAMMB1Rlc3QgQ0EXDTcw
211
+ END
212
+
213
+ state.next_state rescue OpenSSL::X509::CRLError
214
+
215
+ expect(File).to_not exist(Puppet[:hostcrl])
216
+ end
217
+
218
+ it 'skips CRL download when revocation is disabled' do
219
+ Puppet[:certificate_revocation] = false
220
+
221
+ Puppet::X509::CertProvider.any_instance.expects(:load_crls).never
222
+ Puppet::Rest::Routes.expects(:get_crls).never
223
+
224
+ state.next_state
225
+
226
+ expect(File).to_not exist(Puppet[:hostcrl])
227
+ end
228
+ end
229
+
230
+ context 'when ensuring a client cert' do
231
+ context 'in state NeedKey' do
232
+ let(:ssl_context) { Puppet::SSL::SSLContext.new(cacerts: cacerts, crls: crls)}
233
+ let(:state) { Puppet::SSL::StateMachine::NeedKey.new(machine, ssl_context) }
234
+
235
+ it 'loads an existing private key and passes it to the next state' do
236
+ Puppet::X509::CertProvider.any_instance.stubs(:load_private_key).returns(private_key)
237
+
238
+ st = state.next_state
239
+ expect(st).to be_instance_of(Puppet::SSL::StateMachine::NeedSubmitCSR)
240
+ expect(st.private_key).to eq(private_key)
241
+ end
242
+
243
+ it 'loads a matching private key and cert' do
244
+ Puppet::X509::CertProvider.any_instance.stubs(:load_private_key).returns(private_key)
245
+ Puppet::X509::CertProvider.any_instance.stubs(:load_client_cert).returns(client_cert)
246
+
247
+ st = state.next_state
248
+ expect(st).to be_instance_of(Puppet::SSL::StateMachine::Done)
249
+ end
250
+
251
+ it 'raises if the client cert is mismatched' do
252
+ Puppet::X509::CertProvider.any_instance.stubs(:load_private_key).returns(private_key)
253
+ Puppet::X509::CertProvider.any_instance.stubs(:load_client_cert).returns(cert_fixture('tampered-cert.pem'))
254
+
255
+ expect {
256
+ state.next_state
257
+ }.to raise_error(Puppet::SSL::SSLError, %r{The certificate for '/CN=signed' does not match its private key})
258
+ end
259
+
260
+ it 'generates a new private key, saves it and passes it to the next state' do
261
+ Puppet::X509::CertProvider.any_instance.stubs(:load_private_key).returns(nil)
262
+ Puppet::X509::CertProvider.any_instance.expects(:save_private_key)
263
+
264
+ st = state.next_state
265
+ expect(st).to be_instance_of(Puppet::SSL::StateMachine::NeedSubmitCSR)
266
+ expect(st.private_key).to be_instance_of(OpenSSL::PKey::RSA)
267
+ end
268
+
269
+ it 'raises an error if it fails to load the key' do
270
+ Puppet::X509::CertProvider.any_instance.stubs(:load_private_key).raises(OpenSSL::PKey::RSAError)
271
+
272
+ expect {
273
+ state.next_state
274
+ }.to raise_error(OpenSSL::PKey::RSAError)
275
+ end
276
+ end
277
+
278
+ context 'in state NeedSubmitCSR' do
279
+ let(:ssl_context) { Puppet::SSL::SSLContext.new(cacerts: cacerts, crls: crls)}
280
+ let(:state) { Puppet::SSL::StateMachine::NeedSubmitCSR.new(machine, ssl_context, private_key) }
281
+
282
+ def write_csr_attributes(data)
283
+ csr_yaml = tmpfile('state_machine_csr')
284
+ File.write(csr_yaml, YAML.dump(data))
285
+ csr_yaml
286
+ end
287
+
288
+ before :each do
289
+ Puppet::X509::CertProvider.any_instance.stubs(:save_request)
290
+ end
291
+
292
+ it 'submits the CSR and transitions to NeedCert' do
293
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).to_return(status: 200)
294
+
295
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::NeedCert)
296
+ end
297
+
298
+ it 'saves the CSR and transitions to NeedCert' do
299
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).to_return(status: 200)
300
+
301
+ Puppet::X509::CertProvider.any_instance.expects(:save_request).with(Puppet[:certname], instance_of(OpenSSL::X509::Request))
302
+
303
+ state.next_state
304
+ end
305
+
306
+ it 'includes DNS alt names' do
307
+ Puppet[:dns_alt_names] = "one,IP:192.168.0.1,DNS:two.com"
308
+
309
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).with do |request|
310
+ csr = Puppet::SSL::CertificateRequest.from_instance(OpenSSL::X509::Request.new(request.body))
311
+ expect(
312
+ csr.subject_alt_names
313
+ ).to contain_exactly('DNS:one', 'IP Address:192.168.0.1', 'DNS:two.com', "DNS:#{Puppet[:certname]}")
314
+ end.to_return(status: 200)
315
+
316
+ state.next_state
317
+ end
318
+
319
+ it 'includes CSR attributes' do
320
+ Puppet[:csr_attributes] = write_csr_attributes(
321
+ 'custom_attributes' => {
322
+ '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info',
323
+ '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info'
324
+ }
325
+ )
326
+
327
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).with do |request|
328
+ csr = Puppet::SSL::CertificateRequest.from_instance(OpenSSL::X509::Request.new(request.body))
329
+ expect(
330
+ csr.custom_attributes
331
+ ).to contain_exactly(
332
+ {'oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'CSR specific info'},
333
+ {'oid' => '1.3.6.1.4.1.34380.1.2.2', 'value' => 'more CSR specific info'}
334
+ )
335
+ end.to_return(status: 200)
336
+
337
+ state.next_state
338
+ end
339
+
340
+ it 'includes CSR extension requests' do
341
+ Puppet[:csr_attributes] = write_csr_attributes(
342
+ {
343
+ 'extension_requests' => {
344
+ '1.3.6.1.4.1.34380.1.1.31415' => 'pi',
345
+ '1.3.6.1.4.1.34380.1.1.2718' => 'e',
346
+ }
347
+ }
348
+ )
349
+
350
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).with do |request|
351
+ csr = Puppet::SSL::CertificateRequest.from_instance(OpenSSL::X509::Request.new(request.body))
352
+ expect(
353
+ csr.request_extensions
354
+ ).to contain_exactly(
355
+ {'oid' => '1.3.6.1.4.1.34380.1.1.31415', 'value' => 'pi'},
356
+ {'oid' => '1.3.6.1.4.1.34380.1.1.2718', 'value' => 'e'}
357
+ )
358
+ end.to_return(status: 200)
359
+
360
+ state.next_state
361
+ end
362
+
363
+ it 'transitions to NeedCert if the server has a requested certificate' do
364
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).to_return(status: 400, body: "#{Puppet[:certname]} already has a requested certificate")
365
+
366
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::NeedCert)
367
+ end
368
+
369
+ it 'transitions to NeedCert if the server has a signed certificate' do
370
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).to_return(status: 400, body: "#{Puppet[:certname]} already has a signed certificate")
371
+
372
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::NeedCert)
373
+ end
374
+
375
+ it 'transitions to NeedCert if the server has a revoked certificate' do
376
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).to_return(status: 400, body: "#{Puppet[:certname]} already has a revoked certificate")
377
+
378
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::NeedCert)
379
+ end
380
+
381
+ it 'raises if the server errors' do
382
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).to_return(status: 500)
383
+
384
+ expect {
385
+ state.next_state
386
+ }.to raise_error(Puppet::SSL::SSLError, /Failed to submit the CSR, HTTP response was 500/)
387
+ end
388
+
389
+ it "verifies the server's certificate when submitting the CSR" do
390
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{Puppet[:certname]}}).to_return(status: 200)
391
+
392
+ Net::HTTP.any_instance.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
393
+
394
+ state.next_state
395
+ end
396
+ end
397
+
398
+ context 'in state NeedCert' do
399
+ let(:ca_chain) { [cert_fixture('ca.pem'), cert_fixture('intermediate.pem')] }
400
+ let(:crl_chain) { [crl_fixture('crl.pem'), crl_fixture('intermediate-crl.pem')] }
401
+ let(:ssl_context) { Puppet::SSL::SSLContext.new(cacerts: ca_chain, crls: crl_chain)}
402
+ let(:state) { Puppet::SSL::StateMachine::NeedCert.new(machine, ssl_context, private_key) }
403
+
404
+ it 'transitions to Done if the cert is signed and matches our private key' do
405
+ Puppet::X509::CertProvider.any_instance.stubs(:save_client_cert)
406
+ Puppet::X509::CertProvider.any_instance.stubs(:save_request)
407
+
408
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{Puppet[:certname]}}).to_return(status: 200, body: client_cert.to_pem)
409
+
410
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::Done)
411
+ end
412
+
413
+ it 'transitions to Wait if the cert does not match our private key' do
414
+ wrong_cert = cert_fixture('127.0.0.1.pem')
415
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{Puppet[:certname]}}).to_return(status: 200, body: wrong_cert.to_pem)
416
+
417
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::Wait)
418
+ end
419
+
420
+ it 'transitions to Wait if the server returns non-200' do
421
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{Puppet[:certname]}}).to_return(status: 404)
422
+
423
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::Wait)
424
+ end
425
+
426
+ it "verifies the server's certificate when getting the client cert" do
427
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{Puppet[:certname]}}).to_return(status: 200, body: client_cert.to_pem)
428
+ Puppet::X509::CertProvider.any_instance.stubs(:save_client_cert)
429
+ Puppet::X509::CertProvider.any_instance.stubs(:save_request)
430
+
431
+ Net::HTTP.any_instance.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
432
+
433
+ state.next_state
434
+ end
435
+
436
+ it 'does not save an invalid client cert' do
437
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{Puppet[:certname]}}).to_return(status: 200, body: <<~END)
438
+ -----BEGIN CERTIFICATE-----
439
+ MIIBpDCCAQ2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRUZXN0
440
+ END
441
+
442
+ state.next_state
443
+
444
+ expect(@logs).to include(an_object_having_attributes(message: /Failed to parse certificate: /))
445
+ expect(File).to_not exist(Puppet[:hostcert])
446
+ end
447
+
448
+ it 'does not save a mismatched client cert' do
449
+ wrong_cert = cert_fixture('127.0.0.1.pem').to_pem
450
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{Puppet[:certname]}}).to_return(status: 200, body: wrong_cert)
451
+
452
+ state.next_state
453
+
454
+ expect(@logs).to include(an_object_having_attributes(message: %r{The certificate for '/CN=127.0.0.1' does not match its private key}))
455
+ expect(File).to_not exist(Puppet[:hostcert])
456
+ end
457
+
458
+ it 'does not save a revoked client cert' do
459
+ revoked_cert = cert_fixture('revoked.pem').to_pem
460
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{Puppet[:certname]}}).to_return(status: 200, body: revoked_cert)
461
+
462
+ state.next_state
463
+
464
+ expect(@logs).to include(an_object_having_attributes(message: %r{Certificate '/CN=revoked' is revoked}))
465
+ expect(File).to_not exist(Puppet[:hostcert])
466
+ end
467
+ end
468
+
469
+ context 'in state Wait' do
470
+ let(:ssl_context) { Puppet::SSL::SSLContext.new(cacerts: cacerts, crls: crls)}
471
+
472
+ it 'exits with 1 if only running once' do
473
+ machine = described_class.new(onetime: true)
474
+
475
+ expect {
476
+ expect {
477
+ Puppet::SSL::StateMachine::Wait.new(machine, ssl_context).next_state
478
+ }.to output("Exiting; no certificate found and waitforcert is disabled").to_stdout
479
+ }.to exit_with(1)
480
+ end
481
+
482
+ it 'exits with 1 if waitforcert is 0' do
483
+ machine = described_class.new(waitforcert: 0)
484
+
485
+ expect {
486
+ expect {
487
+ Puppet::SSL::StateMachine::Wait.new(machine, ssl_context).next_state
488
+ }.to output("Exiting; no certificate found and waitforcert is disabled").to_stdout
489
+ }.to exit_with(1)
490
+ end
491
+
492
+ it 'sleeps and transitions to NeedCACerts' do
493
+ machine = described_class.new(waitforcert: 15)
494
+
495
+ state = Puppet::SSL::StateMachine::Wait.new(machine, ssl_context)
496
+ state.expects(:sleep).with(15)
497
+
498
+ expect(state.next_state).to be_an_instance_of(Puppet::SSL::StateMachine::NeedCACerts)
499
+ end
500
+ end
501
+ end
502
+ end