puppet 6.3.0-x86-mingw32 → 6.4.0-x86-mingw32

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
@@ -5,11 +5,12 @@ require 'puppet/network/http'
5
5
 
6
6
  describe Puppet::Network::HTTP::Session do
7
7
  let(:connection) { stub('connection') }
8
+ let(:verifier) { stub('verifier') }
8
9
 
9
10
  def create_session(connection, expiration_time = nil)
10
11
  expiration_time ||= Time.now + 60 * 60
11
12
 
12
- Puppet::Network::HTTP::Session.new(connection, expiration_time)
13
+ Puppet::Network::HTTP::Session.new(connection, verifier, expiration_time)
13
14
  end
14
15
 
15
16
  it 'provides access to its connection' do
@@ -18,6 +19,12 @@ describe Puppet::Network::HTTP::Session do
18
19
  expect(session.connection).to eq(connection)
19
20
  end
20
21
 
22
+ it 'provides access to its verifier' do
23
+ session = create_session(connection)
24
+
25
+ expect(session.verifier).to eq(verifier)
26
+ end
27
+
21
28
  it 'expires a connection whose expiration time is in the past' do
22
29
  now = Time.now
23
30
  past = now - 1
@@ -46,6 +46,42 @@ describe Puppet::Network::HttpPool do
46
46
  expect(Puppet::Network::HttpPool.http_instance("me", 54321, true)).to be_use_ssl
47
47
  end
48
48
 
49
+ it 'has an http_ssl_instance method' do
50
+ expect(Puppet::Network::HttpPool.http_ssl_instance("me", 54321)).to be_use_ssl
51
+ end
52
+
53
+ context "when calling 'connection'" do
54
+ it 'requires an ssl_context' do
55
+ expect {
56
+ Puppet::Network::HttpPool.connection('me', 8140)
57
+ }.to raise_error(ArgumentError, "An ssl_context is required when connecting to 'https://me:8140'")
58
+ end
59
+
60
+ it 'creates a verifier from the context' do
61
+ ssl_context = Puppet::SSL::SSLContext.new
62
+ expect(
63
+ Puppet::Network::HttpPool.connection('me', 8140, ssl_context: ssl_context).verifier
64
+ ).to be_a_kind_of(Puppet::SSL::Verifier)
65
+ end
66
+
67
+ it 'does not use SSL when specified' do
68
+ expect(Puppet::Network::HttpPool.connection('me', 8140, use_ssl: false)).to_not be_use_ssl
69
+ end
70
+
71
+ it 'defaults to SSL' do
72
+ ssl_context = Puppet::SSL::SSLContext.new
73
+ conn = Puppet::Network::HttpPool.connection('me', 8140, ssl_context: ssl_context)
74
+ expect(conn).to be_use_ssl
75
+ end
76
+
77
+ it 'warns if an ssl_context is used for an http connection' do
78
+ Puppet.expects(:warning).with("An ssl_context is unnecessary when connecting to 'http://me:8140' and will be ignored")
79
+
80
+ ssl_context = Puppet::SSL::SSLContext.new
81
+ Puppet::Network::HttpPool.connection('me', 8140, use_ssl: false, ssl_context: ssl_context)
82
+ end
83
+ end
84
+
49
85
  describe 'peer verification' do
50
86
  def setup_standard_ssl_configuration
51
87
  ca_cert_file = File.expand_path('/path/to/ssl/certs/ca_cert.pem')
@@ -250,6 +250,11 @@ describe 'The Loader' do
250
250
  'aplan.pp' => <<-PUPPET.unindent,
251
251
  plan b::aplan() {}
252
252
  PUPPET
253
+ 'yamlplan.yaml' => '{}',
254
+ 'conflict.yaml' => '{}',
255
+ 'conflict.pp' => <<-PUPPET.unindent,
256
+ plan b::conflict() {}
257
+ PUPPET
253
258
  }
254
259
  }
255
260
 
@@ -368,7 +373,7 @@ describe 'The Loader' do
368
373
 
369
374
  it 'private loader finds plans in all modules' do
370
375
  expect(loader.private_loader.discover(:plan) { |t| t.name =~ /^.(?:::.*)?\z/ }).to(
371
- contain_exactly(tn(:plan, 'b'), tn(:plan, 'a::aplan'), tn(:plan, 'b::aplan')))
376
+ contain_exactly(tn(:plan, 'b'), tn(:plan, 'a::aplan'), tn(:plan, 'b::aplan'), tn(:plan, 'b::conflict')))
372
377
  end
373
378
 
374
379
  it 'module loader finds plans only in itself' do
@@ -376,6 +381,26 @@ describe 'The Loader' do
376
381
  contain_exactly(tn(:plan, 'a::aplan')))
377
382
  end
378
383
 
384
+ context 'with a yaml plan instantiator defined' do
385
+ before :each do
386
+ Puppet.push_context(:yaml_plan_instantiator => mock(:create => mock('plan')))
387
+ end
388
+
389
+ after :each do
390
+ Puppet.pop_context
391
+ end
392
+
393
+ it 'module loader finds yaml plans' do
394
+ expect(Loaders.find_loader('b').discover(:plan)).to(
395
+ include(tn(:plan, 'b::yamlplan')))
396
+ end
397
+
398
+ it 'module loader excludes plans with both .pp and .yaml versions' do
399
+ expect(Loaders.find_loader('b').discover(:plan)).not_to(
400
+ include(tn(:plan, 'b::conflict')))
401
+ end
402
+ end
403
+
379
404
  it 'private loader finds types in all modules' do
380
405
  expect(loader.private_loader.discover(:type) { |t| t.name =~ /^.::.*\z/ }).to(
381
406
  contain_exactly(tn(:type, 'a::atype'), tn(:type, 'b::atype'), tn(:type, 'c::atype')))
@@ -86,7 +86,7 @@ describe Puppet::Type.type(:package).provider(:windows), :if => Puppet.features.
86
86
  context '#install' do
87
87
  let(:command) { 'blarg.exe /S' }
88
88
  let(:klass) { mock('installer', :install_command => ['blarg.exe', '/S'] ) }
89
- let(:execute_options) do {:failonfail => false, :combine => true, :cwd => 'E:\Rando\Directory', :suppress_window => true} end
89
+ let(:execute_options) do {:failonfail => false, :combine => true, :cwd => nil, :suppress_window => true} end
90
90
  before :each do
91
91
  Puppet::Provider::Package::Windows::Package.expects(:installer_class).returns(klass)
92
92
  end
@@ -136,6 +136,17 @@ describe Puppet::Type.type(:package).provider(:windows), :if => Puppet.features.
136
136
  expect(error.code).to eq(5) # ERROR_ACCESS_DENIED
137
137
  end
138
138
  end
139
+
140
+ context 'With a real working dir' do
141
+ let(:execute_options) do {:failonfail => false, :combine => true, :cwd => 'E:\Rando\Directory', :suppress_window => true} end
142
+
143
+ it 'should not try to set the working directory' do
144
+ Puppet::FileSystem.expects(:exist?).with('E:\Rando\Directory').returns(true)
145
+ expect_execute(command, 0)
146
+
147
+ provider.install
148
+ end
149
+ end
139
150
  end
140
151
 
141
152
  context '#uninstall' do
@@ -18,18 +18,18 @@ describe processor do
18
18
  it "configures the connection for ssl when using https" do
19
19
  Puppet[:reporturl] = 'https://testing:8080/the/path'
20
20
 
21
- Puppet::Network::HttpPool.expects(:http_instance).with(
22
- 'testing', 8080, true
21
+ Puppet::Network::HttpPool.expects(:connection).with(
22
+ 'testing', 8080, has_entry(ssl_context: instance_of(Puppet::SSL::SSLContext))
23
23
  ).returns http
24
24
 
25
25
  subject.process
26
26
  end
27
27
 
28
- it "does not configure the connectino for ssl when using http" do
29
- Puppet[:reporturl] = "http://testing:8080/the/path"
28
+ it "does not configure the connection for ssl when using http" do
29
+ Puppet[:reporturl] = 'http://testing:8080/the/path'
30
30
 
31
- Puppet::Network::HttpPool.expects(:http_instance).with(
32
- 'testing', 8080, false
31
+ Puppet::Network::HttpPool.expects(:connection).with(
32
+ 'testing', 8080, use_ssl: false, ssl_context: nil
33
33
  ).returns http
34
34
 
35
35
  subject.process
@@ -42,7 +42,7 @@ describe processor do
42
42
  let(:options) { {:metric_id => [:puppet, :report, :http]} }
43
43
 
44
44
  before :each do
45
- Puppet::Network::HttpPool.expects(:http_instance).returns(connection)
45
+ Puppet::Network::HttpPool.expects(:connection).returns(connection)
46
46
  end
47
47
 
48
48
  it "should use the path specified by the 'reporturl' setting" do
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  require 'puppet/rest/client'
4
- require 'puppet/rest/ssl_context'
5
4
  require 'puppet_spec/validators'
6
5
  require 'puppet_spec/ssl'
7
6
 
@@ -57,7 +56,6 @@ describe Puppet::Rest::Client do
57
56
 
58
57
  it 'initializes itself with basic defaults' do
59
58
  HTTPClient.expects(:new).returns(http)
60
- OpenSSL::X509::Store.stubs(:new).returns(ssl_store)
61
59
  # Configure connection with HTTP settings
62
60
  Puppet[:http_read_timeout] = 120
63
61
  Puppet[:http_connect_timeout] = 10
@@ -73,18 +71,18 @@ describe Puppet::Rest::Client do
73
71
  Puppet[:hostcert] = '/fake/cert/path'
74
72
  ssl_config.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
75
73
 
76
- Puppet::Rest::Client.new(ssl_context: Puppet::Rest::SSLContext.new(OpenSSL::SSL::VERIFY_NONE))
74
+ Puppet::Rest::Client.new(ssl_context: Puppet::SSL::SSLContext.new(verify_peer: false, store: ssl_store))
77
75
  end
78
76
 
79
77
  it 'uses a given client and SSL store when provided' do
80
78
  ssl_config.expects(:cert_store=).with(ssl_store)
81
79
  Puppet::Rest::Client.new(client: http,
82
- ssl_context: Puppet::Rest::SSLContext.new(OpenSSL::SSL::VERIFY_PEER, ssl_store))
80
+ ssl_context: Puppet::SSL::SSLContext.new(verify_peer: true, store: ssl_store))
83
81
  end
84
82
 
85
83
  it 'configures a receive timeout when provided' do
86
84
  http.expects(:receive_timeout=).with(10)
87
- Puppet::Rest::Client.new(ssl_context: Puppet::Rest::SSLContext.new(OpenSSL::SSL::VERIFY_NONE),
85
+ Puppet::Rest::Client.new(ssl_context: Puppet::SSL::SSLContext.new(verify_peer: false),
88
86
  client: http, receive_timeout: 10)
89
87
  end
90
88
  end
@@ -92,7 +90,7 @@ describe Puppet::Rest::Client do
92
90
  context 'when making requests' do
93
91
  let(:ssl_config) { stub_everything('ssl config') }
94
92
  let(:http) { stub_everything('http', :ssl_config => ssl_config) }
95
- let(:client) { Puppet::Rest::Client.new(ssl_context: Puppet::Rest::SSLContext.new(OpenSSL::SSL::VERIFY_NONE), client: http) }
93
+ let(:client) { Puppet::Rest::Client.new(ssl_context: Puppet::SSL::SSLContext.new(verify_peer: false), client: http) }
96
94
  let(:url) { 'https://myserver.com:555/data' }
97
95
 
98
96
  describe "#get" do
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'spec_helper'
3
+ require 'webmock/rspec'
3
4
  require 'puppet/test_ca'
4
5
 
5
6
  require 'puppet/ssl/host'
@@ -308,40 +309,49 @@ describe Puppet::SSL::Host, if: !Puppet::Util::Platform.jruby? do
308
309
  @host.stubs(:validate_certificate_with_key)
309
310
  @host.stubs(:http_client).returns(@http)
310
311
  @host.stubs(:ssl_store).returns(mock("ssl store"))
312
+
313
+ WebMock.disable_net_connect!
314
+ Net::HTTP.any_instance.stubs(:start)
315
+ Net::HTTP.any_instance.stubs(:finish)
311
316
  end
312
317
 
313
318
  let(:ca_cert_response) { @pki[:ca_bundle] }
319
+ let(:crl_response) { @pki[:crl_chain] }
314
320
  let(:host_cert_response) { @pki[:unrevoked_leaf_node_cert] }
315
321
 
316
322
  it "should find the CA certificate and save it to disk" do
317
- Puppet::Rest::Routes.expects(:get_certificate)
318
- .with(Puppet::SSL::CA_NAME, anything)
319
- .returns(ca_cert_response)
320
- Puppet::Rest::Routes.expects(:get_certificate)
321
- .with(@host.name, anything)
322
- .raises(Puppet::Rest::ResponseError.new('no client cert',
323
- mock('response', code: '404')))
323
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: ca_cert_response)
324
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: crl_response)
325
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{@host.name}}).to_return(status: 404)
326
+
324
327
  @host.certificate
325
328
  actual_ca_bundle = Puppet::FileSystem.read(Puppet[:localcacert])
326
329
  expect(actual_ca_bundle).to match(/BEGIN CERTIFICATE.*END CERTIFICATE.*BEGIN CERTIFICATE/m)
327
330
  end
328
331
 
329
- it "should return nil if it cannot find a CA certificate" do
330
- @host.expects(:ensure_ca_certificate).returns(false)
332
+ it "should raise if it cannot find a CA certificate" do
333
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 404)
334
+
331
335
  @host.expects(:get_host_certificate).never
332
336
 
333
- expect(@host.certificate).to be_nil
337
+ expect {
338
+ @host.certificate
339
+ }.to raise_error(Puppet::Error, /CA certificate is missing from the server/)
334
340
  end
335
341
 
336
342
  it "should find the key if it does not have one" do
337
- @host.expects(:ensure_ca_certificate).returns(true)
343
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: ca_cert_response)
344
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: crl_response)
345
+
338
346
  @host.expects(:get_host_certificate).returns(nil)
339
347
  @host.expects(:key).returns mock("key")
340
348
  @host.certificate
341
349
  end
342
350
 
343
351
  it "should generate the key if one cannot be found" do
344
- @host.expects(:ensure_ca_certificate).returns(true)
352
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: ca_cert_response)
353
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: crl_response)
354
+
345
355
  @host.expects(:get_host_certificate).returns(nil)
346
356
  @host.expects(:key).returns nil
347
357
  @host.expects(:generate_key)
@@ -349,12 +359,10 @@ describe Puppet::SSL::Host, if: !Puppet::Util::Platform.jruby? do
349
359
  end
350
360
 
351
361
  it "should find the host certificate, write it to file, and return the Puppet certificate instance" do
352
- Puppet::Rest::Routes.expects(:get_certificate)
353
- .with(Puppet::SSL::CA_NAME, anything)
354
- .returns(ca_cert_response)
355
- Puppet::Rest::Routes.expects(:get_certificate)
356
- .with(@host.name, anything)
357
- .returns(host_cert_response)
362
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: ca_cert_response)
363
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: crl_response)
364
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{@host.name}}).to_return(status: 200, body: host_cert_response.to_pem)
365
+
358
366
  expected_cert = Puppet::SSL::Certificate.from_s(@pki[:unrevoked_leaf_node_cert])
359
367
  actual_cert = @host.certificate
360
368
  expect(actual_cert).to be_a(Puppet::SSL::Certificate)
@@ -365,7 +373,8 @@ describe Puppet::SSL::Host, if: !Puppet::Util::Platform.jruby? do
365
373
 
366
374
  it "should return any previously found certificate" do
367
375
  cert = mock 'cert'
368
- @host.expects(:ensure_ca_certificate).returns(true).once
376
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: ca_cert_response)
377
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: crl_response)
369
378
  @host.expects(:get_host_certificate).returns(cert).once
370
379
 
371
380
  expect(@host.certificate).to equal(cert)
@@ -374,19 +383,16 @@ describe Puppet::SSL::Host, if: !Puppet::Util::Platform.jruby? do
374
383
 
375
384
  context 'invalid certificates' do
376
385
  it "should raise if the CA certificate downloaded from CA is invalid" do
377
- Puppet::Rest::Routes.expects(:get_certificate)
378
- .with(Puppet::SSL::CA_NAME, anything)
379
- .returns('garbage')
380
- expect { @host.certificate }.to raise_error(Puppet::Error, /did not contain a valid CA certificate/)
386
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: 'garbage')
387
+
388
+ expect { @host.certificate }.to raise_error(OpenSSL::X509::CertificateError, /Failed to parse CA certificates as PEM/)
381
389
  end
382
390
 
383
391
  it "should warn if the host certificate downloaded from CA is invalid" do
384
- Puppet::Rest::Routes.expects(:get_certificate)
385
- .with(Puppet::SSL::CA_NAME, anything)
386
- .returns(ca_cert_response)
387
- Puppet::Rest::Routes.expects(:get_certificate)
388
- .with(@host.name, anything)
389
- .returns('garbage')
392
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: ca_cert_response)
393
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: crl_response)
394
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{@host.name}}).to_return(status: 200, body: 'garbage')
395
+
390
396
  expect { @host.certificate }.to raise_error(Puppet::Error, /did not contain a valid certificate for #{@host.name}/)
391
397
  end
392
398
 
@@ -394,13 +400,13 @@ describe Puppet::SSL::Host, if: !Puppet::Util::Platform.jruby? do
394
400
  Puppet::FileSystem.open(Puppet[:localcacert], nil, "w:ASCII") do |f|
395
401
  f.puts 'garbage'
396
402
  end
397
- expect { @host.certificate }.to raise_error(Puppet::Error, /The CA certificate.*invalid/)
403
+ expect { @host.certificate }.to raise_error(OpenSSL::X509::CertificateError, /Failed to parse CA certificates as PEM/)
398
404
  end
399
405
 
400
406
  it 'should warn if the host certificate loaded from disk in invalid' do
401
- Puppet::Rest::Routes.expects(:get_certificate)
402
- .with(Puppet::SSL::CA_NAME, anything)
403
- .returns(ca_cert_response)
407
+ stub_request(:get, %r{puppet-ca/v1/certificate/ca}).to_return(status: 200, body: ca_cert_response)
408
+ stub_request(:get, %r{puppet-ca/v1/certificate_revocation_list/ca}).to_return(status: 200, body: crl_response)
409
+
404
410
  Puppet::FileSystem.open(File.join(Puppet[:certdir], "#{@host.name}.pem"), nil, "w:ASCII") do |f|
405
411
  f.puts 'garbage'
406
412
  end
@@ -0,0 +1,428 @@
1
+ require 'spec_helper'
2
+
3
+ describe Puppet::SSL::SSLProvider do
4
+ include PuppetSpec::Files
5
+
6
+ let(:global_cacerts) { [ cert_fixture('ca.pem'), cert_fixture('intermediate.pem') ] }
7
+ let(:global_crls) { [ crl_fixture('crl.pem'), crl_fixture('intermediate-crl.pem') ] }
8
+ let(:wrong_key) { OpenSSL::PKey::RSA.new(512) }
9
+
10
+ def as_pem_file(x509)
11
+ path = tmpfile('ssl_provider_pem')
12
+ File.write(path, x509.to_pem)
13
+ path
14
+ end
15
+
16
+ context 'when creating an insecure context' do
17
+ let(:sslctx) { subject.create_insecure_context }
18
+
19
+ it 'has an empty list of trusted certs' do
20
+ expect(sslctx.cacerts).to eq([])
21
+ end
22
+
23
+ it 'has an empty list of crls' do
24
+ expect(sslctx.crls).to eq([])
25
+ end
26
+
27
+ it 'has an empty chain' do
28
+ expect(sslctx.client_chain).to eq([])
29
+ end
30
+
31
+ it 'has a nil private key and cert' do
32
+ expect(sslctx.private_key).to be_nil
33
+ expect(sslctx.client_cert).to be_nil
34
+ end
35
+
36
+ it 'does not authenticate the server' do
37
+ expect(sslctx.verify_peer).to eq(false)
38
+ end
39
+
40
+ it 'raises if the frozen context is modified' do
41
+ expect {
42
+ sslctx.cacerts = []
43
+ }.to raise_error(/can't modify frozen/)
44
+ end
45
+ end
46
+
47
+ context 'when creating an root ssl context with CA certs' do
48
+ let(:config) { { cacerts: [], crls: [], revocation: false } }
49
+
50
+ it 'accepts empty list of certs and crls' do
51
+ sslctx = subject.create_root_context(config)
52
+ expect(sslctx.cacerts).to eq([])
53
+ expect(sslctx.crls).to eq([])
54
+ end
55
+
56
+ it 'accepts valid root certs' do
57
+ certs = [cert_fixture('ca.pem')]
58
+ sslctx = subject.create_root_context(config.merge(cacerts: certs))
59
+ expect(sslctx.cacerts).to eq(certs)
60
+ end
61
+
62
+ it 'accepts valid intermediate certs' do
63
+ certs = [cert_fixture('ca.pem'), cert_fixture('intermediate.pem')]
64
+ sslctx = subject.create_root_context(config.merge(cacerts: certs))
65
+ expect(sslctx.cacerts).to eq(certs)
66
+ end
67
+
68
+ it 'accepts expired CA certs' do
69
+ expired = [cert_fixture('ca.pem'), cert_fixture('intermediate.pem')]
70
+ expired.each { |x509| x509.not_after = Time.at(0) }
71
+
72
+ sslctx = subject.create_root_context(config.merge(cacerts: expired))
73
+ expect(sslctx.cacerts).to eq(expired)
74
+ end
75
+
76
+ it 'raises if the frozen context is modified' do
77
+ sslctx = subject.create_root_context(config)
78
+ expect {
79
+ sslctx.verify_peer = false
80
+ }.to raise_error(/can't modify frozen/)
81
+ end
82
+ end
83
+
84
+ context 'when creating an ssl context with crls' do
85
+ let(:config) { { cacerts: global_cacerts, crls: global_crls} }
86
+
87
+ it 'accepts valid CRLs' do
88
+ certs = [cert_fixture('ca.pem')]
89
+ crls = [crl_fixture('crl.pem')]
90
+ sslctx = subject.create_root_context(config.merge(cacerts: certs, crls: crls))
91
+ expect(sslctx.crls).to eq(crls)
92
+ end
93
+
94
+ it 'accepts valid CRLs for intermediate certs' do
95
+ certs = [cert_fixture('ca.pem'), cert_fixture('intermediate.pem')]
96
+ crls = [crl_fixture('crl.pem'), crl_fixture('intermediate-crl.pem')]
97
+ sslctx = subject.create_root_context(config.merge(cacerts: certs, crls: crls))
98
+ expect(sslctx.crls).to eq(crls)
99
+ end
100
+
101
+ it 'accepts expired CRLs' do
102
+ expired = [crl_fixture('crl.pem'), crl_fixture('intermediate-crl.pem')]
103
+ expired.each { |x509| x509.last_update = Time.at(0) }
104
+
105
+ sslctx = subject.create_root_context(config.merge(crls: expired))
106
+ expect(sslctx.crls).to eq(expired)
107
+ end
108
+ end
109
+
110
+ context 'when creating an ssl context with client certs' do
111
+ let(:client_cert) { cert_fixture('signed.pem') }
112
+ let(:private_key) { key_fixture('signed-key.pem') }
113
+ let(:config) { { cacerts: global_cacerts, crls: global_crls, client_cert: client_cert, private_key: private_key } }
114
+
115
+ it 'raises if CA certs are missing' do
116
+ expect {
117
+ subject.create_context(config.merge(cacerts: nil))
118
+ }.to raise_error(ArgumentError, /CA certs are missing/)
119
+ end
120
+
121
+ it 'raises if CRLs are are missing' do
122
+ expect {
123
+ subject.create_context(config.merge(crls: nil))
124
+ }.to raise_error(ArgumentError, /CRLs are missing/)
125
+ end
126
+
127
+ it 'raises if private key is missing' do
128
+ expect {
129
+ subject.create_context(config.merge(private_key: nil))
130
+ }.to raise_error(ArgumentError, /Private key is missing/)
131
+ end
132
+
133
+ it 'raises if client cert is missing' do
134
+ expect {
135
+ subject.create_context(config.merge(client_cert: nil))
136
+ }.to raise_error(ArgumentError, /Client cert is missing/)
137
+ end
138
+
139
+ it 'accepts RSA keys' do
140
+ sslctx = subject.create_context(config)
141
+ expect(sslctx.private_key).to eq(private_key)
142
+ end
143
+
144
+ it 'raises if private key is unsupported' do
145
+ ec_key = OpenSSL::PKey::EC.new
146
+ expect {
147
+ subject.create_context(config.merge(private_key: ec_key))
148
+ }.to raise_error(Puppet::SSL::SSLError, /Unsupported key 'OpenSSL::PKey::EC'/)
149
+ end
150
+
151
+ it 'resolves the client chain from leaf to root' do
152
+ sslctx = subject.create_context(config)
153
+ expect(
154
+ sslctx.client_chain.map(&:subject).map(&:to_s)
155
+ ).to eq(['/CN=signed', '/CN=Test CA Subauthority', '/CN=Test CA'])
156
+ end
157
+
158
+ it 'raises if client cert signature is invalid' do
159
+ client_cert.sign(wrong_key, OpenSSL::Digest::SHA256.new)
160
+ expect {
161
+ subject.create_context(config.merge(client_cert: client_cert))
162
+ }.to raise_error(Puppet::SSL::CertVerifyError,
163
+ "Invalid signature for certificate '/CN=signed'")
164
+ end
165
+
166
+ it 'raises if client cert and private key are mismatched' do
167
+ expect {
168
+ subject.create_context(config.merge(private_key: wrong_key))
169
+ }.to raise_error(Puppet::SSL::SSLError,
170
+ "The certificate for '/CN=signed' does not match its private key")
171
+ end
172
+
173
+ it "raises if client cert's public key has been replaced" do
174
+ expect {
175
+ subject.create_context(config.merge(client_cert: cert_fixture('tampered-cert.pem')))
176
+ }.to raise_error(Puppet::SSL::CertVerifyError,
177
+ "Invalid signature for certificate '/CN=signed'")
178
+ end
179
+
180
+ # This option is only available in openssl 1.1
181
+ it 'raises if root cert signature is invalid', if: defined?(OpenSSL::X509::V_FLAG_CHECK_SS_SIGNATURE) do
182
+ ca = global_cacerts.first
183
+ ca.sign(wrong_key, OpenSSL::Digest::SHA256.new)
184
+
185
+ expect {
186
+ subject.create_context(config.merge(cacerts: global_cacerts))
187
+ }.to raise_error(Puppet::SSL::CertVerifyError,
188
+ "Invalid signature for certificate '/CN=Test CA'")
189
+ end
190
+
191
+ it 'raises if intermediate CA signature is invalid' do
192
+ int = global_cacerts.last
193
+ int.sign(wrong_key, OpenSSL::Digest::SHA256.new)
194
+
195
+ expect {
196
+ subject.create_context(config.merge(cacerts: global_cacerts))
197
+ }.to raise_error(Puppet::SSL::CertVerifyError,
198
+ "Invalid signature for certificate '/CN=Test CA Subauthority'")
199
+ end
200
+
201
+ it 'raises if CRL signature for root CA is invalid', unless: Puppet::Util::Platform.jruby? do
202
+ crl = global_crls.first
203
+ crl.sign(wrong_key, OpenSSL::Digest::SHA256.new)
204
+
205
+ expect {
206
+ subject.create_context(config.merge(crls: global_crls))
207
+ }.to raise_error(Puppet::SSL::CertVerifyError,
208
+ "Invalid signature for CRL issued by '/CN=Test CA'")
209
+ end
210
+
211
+ it 'raises if CRL signature for intermediate CA is invalid', unless: Puppet::Util::Platform.jruby? do
212
+ crl = global_crls.last
213
+ crl.sign(wrong_key, OpenSSL::Digest::SHA256.new)
214
+
215
+ expect {
216
+ subject.create_context(config.merge(crls: global_crls))
217
+ }.to raise_error(Puppet::SSL::CertVerifyError,
218
+ "Invalid signature for CRL issued by '/CN=Test CA Subauthority'")
219
+ end
220
+
221
+ it 'raises if client cert is revoked' do
222
+ expect {
223
+ subject.create_context(config.merge(private_key: key_fixture('revoked-key.pem'), client_cert: cert_fixture('revoked.pem')))
224
+ }.to raise_error(Puppet::SSL::CertVerifyError,
225
+ "Certificate '/CN=revoked' is revoked")
226
+ end
227
+
228
+ it 'warns if intermediate issuer is missing' do
229
+ Puppet.expects(:warning).with("The issuer '/CN=Test CA Subauthority' of certificate '/CN=signed' cannot be found locally")
230
+
231
+ subject.create_context(config.merge(cacerts: [cert_fixture('ca.pem')]))
232
+ end
233
+
234
+ it 'raises if root issuer is missing' do
235
+ expect {
236
+ subject.create_context(config.merge(cacerts: [cert_fixture('intermediate.pem')]))
237
+ }.to raise_error(Puppet::SSL::CertVerifyError,
238
+ "The issuer '/CN=Test CA' of certificate '/CN=Test CA Subauthority' is missing")
239
+ end
240
+
241
+ it 'raises if cert is not valid yet', unless: Puppet::Util::Platform.jruby? do
242
+ client_cert.not_before = Time.now + (5 * 60 * 60)
243
+ expect {
244
+ subject.create_context(config.merge(client_cert: client_cert))
245
+ }.to raise_error(Puppet::SSL::CertVerifyError,
246
+ "The certificate '/CN=signed' is not yet valid, verify time is synchronized")
247
+ end
248
+
249
+ it 'raises if cert is expired', unless: Puppet::Util::Platform.jruby? do
250
+ client_cert.not_after = Time.at(0)
251
+ expect {
252
+ subject.create_context(config.merge(client_cert: client_cert))
253
+ }.to raise_error(Puppet::SSL::CertVerifyError,
254
+ "The certificate '/CN=signed' has expired, verify time is synchronized")
255
+ end
256
+
257
+ it 'raises if crl is not valid yet', unless: Puppet::Util::Platform.jruby? do
258
+ future_crls = global_crls
259
+ # invalidate the CRL issued by the root
260
+ future_crls.first.last_update = Time.now + (5 * 60 * 60)
261
+
262
+ expect {
263
+ subject.create_context(config.merge(crls: future_crls))
264
+ }.to raise_error(Puppet::SSL::CertVerifyError,
265
+ "The CRL issued by '/CN=Test CA' is not yet valid, verify time is synchronized")
266
+ end
267
+
268
+ it 'raises if crl is expired', unless: Puppet::Util::Platform.jruby? do
269
+ past_crls = global_crls
270
+ # invalidate the CRL issued by the root
271
+ past_crls.first.next_update = Time.at(0)
272
+
273
+ expect {
274
+ subject.create_context(config.merge(crls: past_crls))
275
+ }.to raise_error(Puppet::SSL::CertVerifyError,
276
+ "The CRL issued by '/CN=Test CA' has expired, verify time is synchronized")
277
+ end
278
+
279
+ it 'raises if the root CRL is missing' do
280
+ crls = [crl_fixture('intermediate-crl.pem')]
281
+ expect {
282
+ subject.create_context(config.merge(crls: crls, revocation: :chain))
283
+ }.to raise_error(Puppet::SSL::CertVerifyError,
284
+ "The CRL issued by '/CN=Test CA' is missing")
285
+ end
286
+
287
+ it 'raises if the intermediate CRL is missing' do
288
+ crls = [crl_fixture('crl.pem')]
289
+ expect {
290
+ subject.create_context(config.merge(crls: crls))
291
+ }.to raise_error(Puppet::SSL::CertVerifyError,
292
+ "The CRL issued by '/CN=Test CA Subauthority' is missing")
293
+ end
294
+
295
+ it "doesn't raise if the root CRL is missing and we're just checking the leaf" do
296
+ crls = [crl_fixture('intermediate-crl.pem')]
297
+ subject.create_context(config.merge(crls: crls, revocation: :leaf))
298
+ end
299
+
300
+ it "doesn't raise if the intermediate CRL is missing and revocation checking is disabled" do
301
+ crls = [crl_fixture('crl.pem')]
302
+ subject.create_context(config.merge(crls: crls, revocation: false))
303
+ end
304
+
305
+ it "doesn't raise if both CRLs are missing and revocation checking is disabled" do
306
+ subject.create_context(config.merge(crls: [], revocation: false))
307
+ end
308
+
309
+ # OpenSSL < 1.1 does not verify basicConstraints
310
+ it "raises if root CA's isCA basic constraint is false", unless: Puppet::Util::Platform.jruby? || OpenSSL::OPENSSL_VERSION_NUMBER < 0x10100000 do
311
+ certs = [cert_fixture('bad-basic-constraints.pem'), cert_fixture('intermediate.pem')]
312
+
313
+ expect {
314
+ subject.create_context(config.merge(cacerts: certs, crls: [], revocation: false))
315
+ }.to raise_error(Puppet::SSL::CertVerifyError,
316
+ "Certificate '/CN=Test CA' failed verification (24): invalid CA certificate")
317
+ end
318
+
319
+ # OpenSSL < 1.1 does not verify basicConstraints
320
+ it "raises if intermediate CA's isCA basic constraint is false", unless: Puppet::Util::Platform.jruby? || OpenSSL::OPENSSL_VERSION_NUMBER < 0x10100000 do
321
+ certs = [cert_fixture('ca.pem'), cert_fixture('bad-int-basic-constraints.pem')]
322
+
323
+ expect {
324
+ subject.create_context(config.merge(cacerts: certs, crls: [], revocation: false))
325
+ }.to raise_error(Puppet::SSL::CertVerifyError,
326
+ "Certificate '/CN=Test CA Subauthority' failed verification (24): invalid CA certificate")
327
+ end
328
+
329
+ it 'accepts CA certs in any order' do
330
+ sslctx = subject.create_context(config.merge(cacerts: global_cacerts.reverse))
331
+ # certs in ruby+openssl 1.0.x are not comparable, so compare subjects
332
+ expect(sslctx.client_chain.map(&:subject).map(&:to_s)).to contain_exactly('/CN=Test CA', '/CN=Test CA Subauthority', '/CN=signed')
333
+ end
334
+
335
+ it 'accepts CRLs in any order' do
336
+ sslctx = subject.create_context(config.merge(crls: global_crls.reverse))
337
+ # certs in ruby+openssl 1.0.x are not comparable, so compare subjects
338
+ expect(sslctx.client_chain.map(&:subject).map(&:to_s)).to contain_exactly('/CN=Test CA', '/CN=Test CA Subauthority', '/CN=signed')
339
+ end
340
+
341
+ it 'raises if the frozen context is modified' do
342
+ sslctx = subject.create_context(config)
343
+ expect {
344
+ sslctx.verify_peer = false
345
+ }.to raise_error(/can't modify frozen/)
346
+ end
347
+ end
348
+
349
+ context 'when loading an ssl context' do
350
+ let(:client_cert) { cert_fixture('signed.pem') }
351
+ let(:private_key) { key_fixture('signed-key.pem') }
352
+ let(:doesnt_exist) { '/does/not/exist' }
353
+
354
+ before :each do
355
+ Puppet[:localcacert] = as_pem_file(global_cacerts.first)
356
+ Puppet[:hostcrl] = as_pem_file(global_crls.first)
357
+
358
+ Puppet[:certname] = 'signed'
359
+ Puppet[:privatekeydir] = tmpdir('privatekeydir')
360
+ File.write(File.join(Puppet[:privatekeydir], 'signed.pem'), private_key.to_pem)
361
+
362
+ Puppet[:certdir] = tmpdir('privatekeydir')
363
+ File.write(File.join(Puppet[:certdir], 'signed.pem'), client_cert.to_pem)
364
+ end
365
+
366
+ it 'raises if CA certs are missing' do
367
+ Puppet[:localcacert] = doesnt_exist
368
+
369
+ expect {
370
+ subject.load_context
371
+ }.to raise_error(Puppet::Error, /The CA certificates are missing from/)
372
+ end
373
+
374
+ it 'raises if the CRL is missing' do
375
+ Puppet[:hostcrl] = doesnt_exist
376
+
377
+ expect {
378
+ subject.load_context
379
+ }.to raise_error(Puppet::Error, /The CRL is missing from/)
380
+ end
381
+
382
+ it 'does not raise if the CRL is missing and revocation is disabled' do
383
+ Puppet[:hostcrl] = doesnt_exist
384
+
385
+ subject.load_context(revocation: false)
386
+ end
387
+
388
+ it 'raises if the private key is missing' do
389
+ Puppet[:privatekeydir] = doesnt_exist
390
+
391
+ expect {
392
+ subject.load_context
393
+ }.to raise_error(Puppet::Error, /The private key is missing from/)
394
+ end
395
+
396
+ it 'raises if the client cert is missing' do
397
+ Puppet[:certdir] = doesnt_exist
398
+
399
+ expect {
400
+ subject.load_context
401
+ }.to raise_error(Puppet::Error, /The client certificate is missing from/)
402
+ end
403
+ end
404
+
405
+ context 'when verifying requests' do
406
+ let(:csr) { request_fixture('request.pem') }
407
+
408
+ it 'accepts valid requests' do
409
+ private_key = key_fixture('request-key.pem')
410
+ expect(subject.verify_request(csr, private_key.public_key)).to eq(csr)
411
+ end
412
+
413
+ it "raises if the CSR was signed by a private key that doesn't match public key" do
414
+ expect {
415
+ subject.verify_request(csr, wrong_key.public_key)
416
+ }.to raise_error(Puppet::SSL::SSLError,
417
+ "The CSR for host '/CN=pending' does not match the public key")
418
+ end
419
+
420
+ it "raises if the CSR was tampered with" do
421
+ csr = request_fixture('tampered-csr.pem')
422
+ expect {
423
+ subject.verify_request(csr, csr.public_key)
424
+ }.to raise_error(Puppet::SSL::SSLError,
425
+ "The CSR for host '/CN=signed' does not match the public key")
426
+ end
427
+ end
428
+ end