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,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe Puppet::SSL::Verifier do
4
+ let(:options) { {} }
5
+ let(:ssl_context) { Puppet::SSL::SSLContext.new(options) }
6
+ let(:host) { 'example.com' }
7
+ let(:http) { Net::HTTP.new(host) }
8
+ let(:verifier) { described_class.new(host, ssl_context) }
9
+ let(:adapter) { Puppet::SSL::VerifierAdapter.new(Puppet::SSL::Validator::DefaultValidator.new) }
10
+
11
+ context '#reusable?' do
12
+ it 'Verifiers with the same ssl_context are reusable' do
13
+ expect(verifier).to be_reusable(described_class.new(host, ssl_context))
14
+ end
15
+
16
+ it 'Verifiers with different ssl_contexts are not reusable' do
17
+ expect(verifier).to_not be_reusable(described_class.new(host, Puppet::SSL::SSLContext.new))
18
+ end
19
+
20
+ it 'Verifier is not reusable with VerifierAdapter' do
21
+ expect(verifier).to_not be_reusable(adapter)
22
+ end
23
+
24
+ it 'VerifierAdapter is not reusable with Verifier' do
25
+ expect(adapter).to_not be_reusable(verifier)
26
+ end
27
+
28
+ it 'VerifierAdapters with the same class of Validator are reusable' do
29
+ expect(
30
+ adapter
31
+ ).to be_reusable(Puppet::SSL::VerifierAdapter.new(Puppet::SSL::Validator::DefaultValidator.new))
32
+ end
33
+
34
+ it 'VerifierAdapters with different classes of Validators are not reusable' do
35
+ expect(
36
+ adapter
37
+ ).to_not be_reusable(Puppet::SSL::VerifierAdapter.new(Puppet::SSL::Validator::NoValidator.new))
38
+ end
39
+ end
40
+
41
+ context '#setup_connection' do
42
+ it 'copies parameters from the ssl_context to the connection' do
43
+ store = stub('store')
44
+ options.merge!(store: store)
45
+ verifier.setup_connection(http)
46
+
47
+ expect(http.cert_store).to eq(store)
48
+ end
49
+
50
+ it 'defaults to VERIFY_PEER' do
51
+ http.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
52
+
53
+ verifier.setup_connection(http)
54
+ end
55
+
56
+ it 'only uses VERIFY_NONE if explicitly disabled' do
57
+ options.merge!(verify_peer: false)
58
+
59
+ http.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
60
+
61
+ verifier.setup_connection(http)
62
+ end
63
+
64
+ it 'registers a verify callback' do
65
+ verifier.setup_connection(http)
66
+
67
+ expect(http.verify_callback).to eq(verifier)
68
+ end
69
+ end
70
+
71
+ context '#handle_connection_error' do
72
+ let(:peer_cert) { cert_fixture('127.0.0.1.pem') }
73
+ let(:chain) { [peer_cert] }
74
+ let(:ssl_error) { OpenSSL::SSL::SSLError.new("certificate verify failed") }
75
+
76
+ it "raises a verification error for a CA cert" do
77
+ store_context = stub('store_context', current_cert: peer_cert, chain: [peer_cert], error: OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, error_string: "unable to get local issuer certificate")
78
+ verifier.call(false, store_context)
79
+
80
+ expect {
81
+ verifier.handle_connection_error(http, ssl_error)
82
+ }.to raise_error(Puppet::SSL::CertVerifyError, "certificate verify failed [unable to get local issuer certificate for /CN=127.0.0.1]")
83
+ end
84
+
85
+ it "raises a verification error for the server cert" do
86
+ store_context = stub('store_context', current_cert: peer_cert, chain: chain, error: OpenSSL::X509::V_ERR_CERT_REJECTED, error_string: "certificate rejected")
87
+ verifier.call(false, store_context)
88
+
89
+ expect {
90
+ verifier.handle_connection_error(http, ssl_error)
91
+ }.to raise_error(Puppet::SSL::CertVerifyError, "certificate verify failed [certificate rejected for /CN=127.0.0.1]")
92
+ end
93
+
94
+ it "raises cert mismatch error on ruby < 2.4" do
95
+ http.expects(:peer_cert).returns(peer_cert)
96
+
97
+ store_context = stub('store_context')
98
+ verifier.call(true, store_context)
99
+
100
+ ssl_error = OpenSSL::SSL::SSLError.new("hostname 'example'com' does not match the server certificate")
101
+
102
+ expect {
103
+ verifier.handle_connection_error(http, ssl_error)
104
+ }.to raise_error(Puppet::Error, "Server hostname 'example.com' did not match server certificate; expected one of 127.0.0.1, DNS:127.0.0.1, DNS:127.0.0.2")
105
+ end
106
+
107
+ it "raises cert mismatch error on ruby >= 2.4" do
108
+ store_context = stub('store_context', current_cert: peer_cert, chain: chain, error: OpenSSL::X509::V_OK, error_string: "ok")
109
+ verifier.call(false, store_context)
110
+
111
+ expect {
112
+ verifier.handle_connection_error(http, ssl_error)
113
+ }.to raise_error(Puppet::Error, "Server hostname 'example.com' did not match server certificate; expected one of 127.0.0.1, DNS:127.0.0.1, DNS:127.0.0.2")
114
+ end
115
+
116
+ it 're-raises other ssl connection errors' do
117
+ err = OpenSSL::SSL::SSLError.new("This version of OpenSSL does not support FIPS mode")
118
+ expect {
119
+ verifier.handle_connection_error(http, err)
120
+ }.to raise_error(err)
121
+ end
122
+ end
123
+ end
@@ -31,6 +31,33 @@ describe Puppet::Type.type(:exec) do
31
31
  return exec
32
32
  end
33
33
 
34
+ def exec_stub(options = {})
35
+ command = options.delete(:command) || @command
36
+ #unless_val = options.delete(:unless) || :true
37
+ type_args = {
38
+ :name => command,
39
+ #:unless => unless_val,
40
+ }.merge(options)
41
+
42
+ # Chicken, meet egg:
43
+ # Provider methods have to be stubbed before resource init or checks fail
44
+ # We have to set 'unless' in resource init or it can not be marked sensitive correctly.
45
+ # So: we create a dummy ahead of time and use 'any_instance' to stub out provider methods.
46
+ dummy = Puppet::Type.type(:exec).new(:name => @command)
47
+ dummy.provider.class.any_instance.stubs(:validatecmd)
48
+ dummy.provider.class.any_instance.stubs(:checkexe).returns(true)
49
+ pass_status = stub('status', :exitstatus => 0, :split => ["pass output"])
50
+ fail_status = stub('status', :exitstatus => 1, :split => ["fail output"])
51
+ Puppet::Util::Execution.stubs(:execute).with(:true, anything).returns(pass_status)
52
+ Puppet::Util::Execution.stubs(:execute).with(:false, anything).returns(fail_status)
53
+
54
+ test = Puppet::Type.type(:exec).new(type_args)
55
+
56
+ Puppet::Util::Log.level = :debug
57
+
58
+ return test
59
+ end
60
+
34
61
  before do
35
62
  @command = make_absolute('/bin/true whatever')
36
63
  @executable = make_absolute('/bin/true')
@@ -176,6 +203,26 @@ describe Puppet::Type.type(:exec) do
176
203
  expect(@logs).to eq([])
177
204
  end
178
205
 
206
+ describe "when checks stop execution when debugging" do
207
+ [[:unless, :true], [:onlyif, :false]].each do |check, result|
208
+ it "should log a message with the command when #{check} is #{result}" do
209
+ output = "'#{@command}' won't be executed because of failed check '#{check}'"
210
+ test = exec_stub({:command => @command, check => result})
211
+ expect(test.check_all_attributes).to eq(false)
212
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: output))
213
+ end
214
+
215
+ it "should log a message with a redacted command and check if #{check} is sensitive" do
216
+ output1 = "Executing check '[redacted]'"
217
+ output2 = "'[command redacted]' won't be executed because of failed check '#{check}'"
218
+ test = exec_stub({:command => @command, check => result, :sensitive_parameters => [check]})
219
+ expect(test.check_all_attributes).to eq(false)
220
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: output1))
221
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: output2))
222
+ end
223
+ end
224
+ end
225
+
179
226
  describe " when multiple tries are set," do
180
227
  it "should repeat the command attempt 'tries' times on failure and produce an error" do
181
228
  tries = 5
@@ -635,6 +682,22 @@ describe Puppet::Type.type(:exec) do
635
682
  @test[:creates] = [@exist] * 3
636
683
  end
637
684
  end
685
+
686
+ context "when creates is being checked" do
687
+ it "should be logged to debug when the path does exist" do
688
+ Puppet::Util::Log.level = :debug
689
+ @test[:creates] = @exist
690
+ expect(@test.check_all_attributes).to eq(false)
691
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Checking that 'creates' path '#{@exist}' exists"))
692
+ end
693
+
694
+ it "should be logged to debug when the path does not exist" do
695
+ Puppet::Util::Log.level = :debug
696
+ @test[:creates] = @unexist
697
+ expect(@test.check_all_attributes).to eq(true)
698
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Checking that 'creates' path '#{@unexist}' exists"))
699
+ end
700
+ end
638
701
  end
639
702
 
640
703
  { :onlyif => { :pass => false, :fail => true },
@@ -594,7 +594,7 @@ describe Puppet::Type.type(:file).attrclass(:source), :uses_checksums => true do
594
594
  it 'should use an explicit fileserver if source starts with puppet://' do
595
595
  response.stubs(:code).returns('200')
596
596
  source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet://somehostname/test/foo', :ftype => 'file')
597
- Puppet::Network::HttpPool.expects(:http_instance).with('somehostname', anything).returns(conn)
597
+ Puppet::Network::HttpPool.expects(:connection).with('somehostname', 8140, anything).returns(conn)
598
598
 
599
599
  resource.write(source)
600
600
  end
@@ -602,14 +602,14 @@ describe Puppet::Type.type(:file).attrclass(:source), :uses_checksums => true do
602
602
  it 'should use the default fileserver if source starts with puppet:///' do
603
603
  response.stubs(:code).returns('200')
604
604
  source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo', :ftype => 'file')
605
- Puppet::Network::HttpPool.expects(:http_instance).with(Puppet.settings[:server], anything).returns(conn)
605
+ Puppet::Network::HttpPool.expects(:connection).with(Puppet[:server], 8140, anything).returns(conn)
606
606
 
607
607
  resource.write(source)
608
608
  end
609
609
 
610
610
  it 'should percent encode reserved characters' do
611
611
  response.stubs(:code).returns('200')
612
- Puppet::Network::HttpPool.stubs(:http_instance).returns(conn)
612
+ Puppet::Network::HttpPool.stubs(:connection).returns(conn)
613
613
  source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo bar', :ftype => 'file')
614
614
 
615
615
  conn.unstub(:request_get)
@@ -620,7 +620,7 @@ describe Puppet::Type.type(:file).attrclass(:source), :uses_checksums => true do
620
620
 
621
621
  it 'should request binary content' do
622
622
  response.stubs(:code).returns('200')
623
- Puppet::Network::HttpPool.stubs(:http_instance).returns(conn)
623
+ Puppet::Network::HttpPool.stubs(:connection).returns(conn)
624
624
  source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo bar', :ftype => 'file')
625
625
 
626
626
  conn.unstub(:request_get)
@@ -637,7 +637,7 @@ describe Puppet::Type.type(:file).attrclass(:source), :uses_checksums => true do
637
637
  end
638
638
 
639
639
  before(:each) do
640
- Puppet::Network::HttpPool.stubs(:http_instance).returns(conn)
640
+ Puppet::Network::HttpPool.stubs(:connection).returns(conn)
641
641
  source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo', :ftype => 'file')
642
642
  end
643
643
 
@@ -21,14 +21,14 @@ describe Puppet::Type.type(:filebucket) do
21
21
  expect(Puppet::Type.type(:filebucket).new(:name => "main")[:path]).to eq(Puppet[:clientbucketdir])
22
22
  end
23
23
 
24
- it "should use the masterport as the path by default port" do
24
+ it "should not have a default port" do
25
25
  Puppet.settings[:masterport] = 50
26
- expect(Puppet::Type.type(:filebucket).new(:name => "main")[:port]).to eq(Puppet[:masterport])
26
+ expect(Puppet::Type.type(:filebucket).new(:name => "main")[:port]).to eq(nil)
27
27
  end
28
28
 
29
- it "should use the server as the path by default server" do
29
+ it "should not have a default server" do
30
30
  Puppet.settings[:server] = "myserver"
31
- expect(Puppet::Type.type(:filebucket).new(:name => "main")[:server]).to eq(Puppet[:server])
31
+ expect(Puppet::Type.type(:filebucket).new(:name => "main")[:server]).to eq(nil)
32
32
  end
33
33
 
34
34
  it "be local by default" do
@@ -93,10 +93,12 @@ describe Puppet::Type.type(:filebucket) do
93
93
  bucket.bucket
94
94
  end
95
95
 
96
- it "should use the default server if the path is unset and no server is provided" do
96
+ it "should not try to guess server or port if the path is unset and no server is provided" do
97
97
  Puppet.settings[:server] = "myserv"
98
+ Puppet.settings[:server_list] = ['server_list_0', 'server_list_1']
99
+ Puppet::FileBucket::Dipper.expects(:new).with(:Server => nil, :Port => nil).returns @bucket
100
+
98
101
  bucket = Puppet::Type.type(:filebucket).new :name => "main", :path => false
99
- Puppet::FileBucket::Dipper.expects(:new).with { |args| args[:Server] == "myserv" }.returns @bucket
100
102
  bucket.bucket
101
103
  end
102
104
  end
@@ -88,7 +88,7 @@ describe Puppet::Util::Feature do
88
88
  @features.expects(:require).with("foo").raises(LoadError)
89
89
  @features.stubs(:require).with("bar")
90
90
 
91
- Puppet.expects(:debug)
91
+ @features.expects(:debug_once)
92
92
 
93
93
  expect(@features).not_to be_myfeature
94
94
  end
@@ -100,7 +100,7 @@ describe Puppet::Util::Feature do
100
100
  Puppet::Util::RubyGems::Source.stubs(:source).returns(Puppet::Util::RubyGems::Gems18Source)
101
101
  Puppet::Util::RubyGems::Gems18Source.any_instance.expects(:clear_paths).times(3)
102
102
 
103
- Puppet.expects(:debug)
103
+ @features.expects(:debug_once)
104
104
 
105
105
  expect(@features).not_to be_myfeature
106
106
  expect(@features).to be_myfeature
@@ -181,6 +181,8 @@ describe Puppet::Util::Storage do
181
181
  end
182
182
 
183
183
  describe "when storing to the state file" do
184
+ A_SMALL_AMOUNT_OF_TIME = 0.001 #Seconds
185
+
184
186
  before(:each) do
185
187
  @state_file = tmpfile('storage_test')
186
188
  @saved_statefile = Puppet[:statefile]
@@ -232,13 +234,13 @@ describe Puppet::Util::Storage do
232
234
  stale_checked = recent_checked - (Puppet[:statettl] + 10)
233
235
  Puppet::Util::Storage.cache(:yayness)[:checked] = recent_checked
234
236
  Puppet::Util::Storage.cache(:stale)[:checked] = stale_checked
235
- expect(Puppet::Util::Storage.state).to eq(
237
+ expect(Puppet::Util::Storage.state).to match(
236
238
  {
237
239
  :yayness => {
238
- :checked => recent_checked
240
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(recent_checked)
239
241
  },
240
242
  :stale => {
241
- :checked => stale_checked
243
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(stale_checked)
242
244
  }
243
245
  }
244
246
  )
@@ -250,29 +252,28 @@ describe Puppet::Util::Storage do
250
252
 
251
253
  Puppet::Util::Storage.load
252
254
 
253
- expect(Puppet::Util::Storage.state).to eq(
255
+ expect(Puppet::Util::Storage.state).to match(
254
256
  {
255
257
  :yayness => {
256
- :checked => recent_checked
258
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(recent_checked)
257
259
  }
258
260
  }
259
261
  )
260
262
  end
261
263
 
262
-
263
264
  it "does not expire entries when statettl is 0" do
264
265
  Puppet[:statettl] = '0'
265
266
  recent_checked = Time.now.round
266
267
  older_checked = recent_checked - 10_000_000
267
268
  Puppet::Util::Storage.cache(:yayness)[:checked] = recent_checked
268
269
  Puppet::Util::Storage.cache(:older)[:checked] = older_checked
269
- expect(Puppet::Util::Storage.state).to eq(
270
+ expect(Puppet::Util::Storage.state).to match(
270
271
  {
271
272
  :yayness => {
272
- :checked => recent_checked
273
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(recent_checked)
273
274
  },
274
275
  :older => {
275
- :checked => older_checked
276
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(older_checked)
276
277
  }
277
278
  }
278
279
  )
@@ -284,32 +285,31 @@ describe Puppet::Util::Storage do
284
285
 
285
286
  Puppet::Util::Storage.load
286
287
 
287
- expect(Puppet::Util::Storage.state).to eq(
288
+ expect(Puppet::Util::Storage.state).to match(
288
289
  {
289
290
  :yayness => {
290
- :checked => recent_checked
291
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(recent_checked)
291
292
  },
292
293
  :older => {
293
- :checked => older_checked
294
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(older_checked)
294
295
  }
295
296
  }
296
297
  )
297
298
  end
298
299
 
299
-
300
300
  it "does not expire entries when statettl is 'unlimited'" do
301
301
  Puppet[:statettl] = 'unlimited'
302
302
  recent_checked = Time.now
303
303
  older_checked = Time.now - 10_000_000
304
304
  Puppet::Util::Storage.cache(:yayness)[:checked] = recent_checked
305
305
  Puppet::Util::Storage.cache(:older)[:checked] = older_checked
306
- expect(Puppet::Util::Storage.state).to eq(
306
+ expect(Puppet::Util::Storage.state).to match(
307
307
  {
308
308
  :yayness => {
309
- :checked => recent_checked
309
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(recent_checked)
310
310
  },
311
311
  :older => {
312
- :checked => older_checked
312
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(older_checked)
313
313
  }
314
314
  }
315
315
  )
@@ -321,13 +321,13 @@ describe Puppet::Util::Storage do
321
321
 
322
322
  Puppet::Util::Storage.load
323
323
 
324
- expect(Puppet::Util::Storage.state).to eq(
324
+ expect(Puppet::Util::Storage.state).to match(
325
325
  {
326
326
  :yayness => {
327
- :checked => recent_checked
327
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(recent_checked)
328
328
  },
329
329
  :older => {
330
- :checked => older_checked
330
+ :checked => a_value_within(A_SMALL_AMOUNT_OF_TIME).of(older_checked)
331
331
  }
332
332
  }
333
333
  )
@@ -0,0 +1,527 @@
1
+ require 'spec_helper'
2
+ require 'puppet/x509'
3
+
4
+ describe Puppet::X509::CertProvider do
5
+ include PuppetSpec::Files
6
+
7
+ def create_provider(options)
8
+ described_class.new(options)
9
+ end
10
+
11
+ def as_pem_file(pem)
12
+ path = tmpfile('cert_provider_pem')
13
+ File.write(path, pem)
14
+ path
15
+ end
16
+
17
+ def expects_public_file(path)
18
+ if Puppet::Util::Platform.windows?
19
+ current_sid = Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name)
20
+ sd = Puppet::Util::Windows::Security.get_security_descriptor(path)
21
+ expect(sd.dacl).to contain_exactly(
22
+ an_object_having_attributes(sid: Puppet::Util::Windows::SID::LocalSystem, mask: 0x1f01ff),
23
+ an_object_having_attributes(sid: Puppet::Util::Windows::SID::BuiltinAdministrators, mask: 0x1f01ff),
24
+ an_object_having_attributes(sid: current_sid, mask: 0x1f01ff),
25
+ an_object_having_attributes(sid: Puppet::Util::Windows::SID::BuiltinUsers, mask: 0x120089)
26
+ )
27
+ else
28
+ expect(File.stat(path).mode & 07777).to eq(0644)
29
+ end
30
+ end
31
+
32
+ def expects_private_file(path)
33
+ if Puppet::Util::Platform.windows?
34
+ current_sid = Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name)
35
+ sd = Puppet::Util::Windows::Security.get_security_descriptor(path)
36
+ expect(sd.dacl).to contain_exactly(
37
+ an_object_having_attributes(sid: Puppet::Util::Windows::SID::LocalSystem, mask: 0x1f01ff),
38
+ an_object_having_attributes(sid: Puppet::Util::Windows::SID::BuiltinAdministrators, mask: 0x1f01ff),
39
+ an_object_having_attributes(sid: current_sid, mask: 0x1f01ff)
40
+ )
41
+ else
42
+ expect(File.stat(path).mode & 07777).to eq(0640)
43
+ end
44
+ end
45
+
46
+ let(:fixture_dir) { File.join(PuppetSpec::FIXTURE_DIR, 'ssl') }
47
+
48
+ context 'when loading' do
49
+ context 'cacerts' do
50
+ it 'returns nil if it does not exist' do
51
+ provider = create_provider(capath: '/does/not/exist')
52
+
53
+ expect(provider.load_cacerts).to be_nil
54
+ end
55
+
56
+ it 'raises if cacerts are required' do
57
+ provider = create_provider(capath: '/does/not/exist')
58
+
59
+ expect {
60
+ provider.load_cacerts(required: true)
61
+ }.to raise_error(Puppet::Error, %r{The CA certificates are missing from '/does/not/exist'})
62
+ end
63
+
64
+ it 'returns an array of certificates' do
65
+ subject = OpenSSL::X509::Name.new([['CN', 'Test CA']])
66
+ certs = create_provider(capath: File.join(fixture_dir, 'ca.pem')).load_cacerts
67
+ expect(certs).to contain_exactly(an_object_having_attributes(subject: subject))
68
+ end
69
+
70
+ context 'and input is invalid' do
71
+ it 'raises when invalid input is inside BEGIN-END block' do
72
+ ca_path = as_pem_file(<<~END)
73
+ -----BEGIN CERTIFICATE-----
74
+ whoops
75
+ -----END CERTIFICATE-----
76
+ END
77
+
78
+ expect {
79
+ create_provider(capath: ca_path).load_cacerts
80
+ }.to raise_error(OpenSSL::X509::CertificateError)
81
+ end
82
+
83
+ it 'raises if the input is empty' do
84
+ expect {
85
+ create_provider(capath: as_pem_file('')).load_cacerts
86
+ }.to raise_error(OpenSSL::X509::CertificateError)
87
+ end
88
+
89
+ it 'raises if the input is malformed' do
90
+ ca_path = as_pem_file(<<~END)
91
+ -----BEGIN CERTIFICATE-----
92
+ MIIBpDCCAQ2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRUZXN0
93
+ END
94
+
95
+ expect {
96
+ create_provider(capath: ca_path).load_cacerts
97
+ }.to raise_error(OpenSSL::X509::CertificateError)
98
+ end
99
+ end
100
+
101
+ it 'raises if the cacerts are unreadable' do
102
+ capath = File.join(fixture_dir, 'ca.pem')
103
+ provider = create_provider(capath: capath)
104
+ provider.stubs(:load_pem).raises(Errno::EACCES, 'Permission denied')
105
+
106
+ expect {
107
+ provider.load_cacerts
108
+ }.to raise_error(Puppet::Error, "Failed to load CA certificates from '#{capath}'")
109
+ end
110
+ end
111
+
112
+ context 'crls' do
113
+ it 'returns nil if it does not exist' do
114
+ provider = create_provider(crlpath: '/does/not/exist')
115
+ expect(provider.load_crls).to be_nil
116
+ end
117
+
118
+ it 'raises if CRLs are required' do
119
+ provider = create_provider(crlpath: '/does/not/exist')
120
+
121
+ expect {
122
+ provider.load_crls(required: true)
123
+ }.to raise_error(Puppet::Error, %r{The CRL is missing from '/does/not/exist'})
124
+ end
125
+
126
+ it 'returns an array of CRLs' do
127
+ issuer = OpenSSL::X509::Name.new([['CN', 'Test CA']])
128
+ crls = create_provider(crlpath: File.join(fixture_dir, 'crl.pem')).load_crls
129
+ expect(crls).to contain_exactly(an_object_having_attributes(issuer: issuer))
130
+ end
131
+
132
+ context 'and input is invalid' do
133
+ it 'raises when invalid input is inside BEGIN-END block' do
134
+ pending('jruby bug: https://github.com/jruby/jruby/issues/5619') if Puppet::Util::Platform.jruby?
135
+
136
+ crl_path = as_pem_file(<<~END)
137
+ -----BEGIN X509 CRL-----
138
+ whoops
139
+ -----END X509 CRL-----
140
+ END
141
+
142
+ expect {
143
+ create_provider(crlpath: crl_path).load_crls
144
+ }.to raise_error(OpenSSL::X509::CRLError, 'nested asn1 error')
145
+ end
146
+
147
+ it 'raises if the input is empty' do
148
+ expect {
149
+ create_provider(crlpath: as_pem_file('')).load_crls
150
+ }.to raise_error(OpenSSL::X509::CRLError, 'Failed to parse CRLs as PEM')
151
+ end
152
+
153
+ it 'raises if the input is malformed' do
154
+ crl_path = as_pem_file(<<~END)
155
+ -----BEGIN X509 CRL-----
156
+ MIIBCjB1AgEBMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNVBAMMB1Rlc3QgQ0EXDTcw
157
+ END
158
+
159
+ expect {
160
+ create_provider(crlpath: crl_path).load_crls
161
+ }.to raise_error(OpenSSL::X509::CRLError, 'Failed to parse CRLs as PEM')
162
+ end
163
+ end
164
+
165
+ it 'raises if the CRLs are unreadable' do
166
+ crlpath = File.join(fixture_dir, 'crl.pem')
167
+ provider = create_provider(crlpath: crlpath)
168
+ provider.stubs(:load_pem).raises(Errno::EACCES, 'Permission denied')
169
+
170
+ expect {
171
+ provider.load_crls
172
+ }.to raise_error(Puppet::Error, "Failed to load CRLs from '#{crlpath}'")
173
+ end
174
+ end
175
+ end
176
+
177
+ context 'when saving' do
178
+ context 'cacerts' do
179
+ let(:ca_path) { tmpfile('pem_cacerts') }
180
+ let(:ca_cert) { cert_fixture('ca.pem') }
181
+
182
+ it 'writes PEM encoded certs' do
183
+ create_provider(capath: ca_path).save_cacerts([ca_cert])
184
+
185
+ expect(File.read(ca_path)).to match(/\A-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----\Z/m)
186
+ end
187
+
188
+ it 'sets mode to 644' do
189
+ create_provider(capath: ca_path).save_cacerts([ca_cert])
190
+
191
+ expects_public_file(ca_path)
192
+ end
193
+
194
+ it 'raises if the CA certs are unwritable' do
195
+ provider = create_provider(capath: ca_path)
196
+ provider.stubs(:save_pem).raises(Errno::EACCES, 'Permission denied')
197
+
198
+ expect {
199
+ provider.save_cacerts([ca_cert])
200
+ }.to raise_error(Puppet::Error, "Failed to save CA certificates to '#{ca_path}'")
201
+ end
202
+ end
203
+
204
+ context 'crls' do
205
+ let(:crl_path) { tmpfile('pem_crls') }
206
+ let(:ca_crl) { crl_fixture('crl.pem') }
207
+
208
+ it 'writes PEM encoded CRLs' do
209
+ create_provider(crlpath: crl_path).save_crls([ca_crl])
210
+
211
+ expect(File.read(crl_path)).to match(/\A-----BEGIN X509 CRL-----.*?-----END X509 CRL-----\Z/m)
212
+ end
213
+
214
+ it 'sets mode to 644' do
215
+ create_provider(crlpath: crl_path).save_crls([ca_crl])
216
+
217
+ expects_public_file(crl_path)
218
+ end
219
+
220
+ it 'raises if the CRLs are unwritable' do
221
+ provider = create_provider(crlpath: crl_path)
222
+ provider.stubs(:save_pem).raises(Errno::EACCES, 'Permission denied')
223
+
224
+ expect {
225
+ provider.save_crls([ca_crl])
226
+ }.to raise_error(Puppet::Error, "Failed to save CRLs to '#{crl_path}'")
227
+ end
228
+ end
229
+ end
230
+
231
+ context 'when loading' do
232
+ context 'private keys' do
233
+ let(:provider) { create_provider(privatekeydir: fixture_dir) }
234
+
235
+ it 'returns nil if it does not exist' do
236
+ provider = create_provider(privatekeydir: '/does/not/exist')
237
+
238
+ expect(provider.load_private_key('whatever')).to be_nil
239
+ end
240
+
241
+ it 'raises if it is required' do
242
+ provider = create_provider(privatekeydir: '/does/not/exist')
243
+
244
+ expect {
245
+ provider.load_private_key('whatever', required: true)
246
+ }.to raise_error(Puppet::Error, %r{The private key is missing from '/does/not/exist/whatever.pem'})
247
+ end
248
+
249
+ it 'returns an RSA key' do
250
+ expect(provider.load_private_key('signed-key')).to be_a(OpenSSL::PKey::RSA)
251
+ end
252
+
253
+ it 'downcases name' do
254
+ expect(provider.load_private_key('SIGNED-KEY')).to be_a(OpenSSL::PKey::RSA)
255
+ end
256
+
257
+ it 'raises if name is invalid' do
258
+ expect {
259
+ provider.load_private_key('signed/../key')
260
+ }.to raise_error(RuntimeError, 'Certname "signed/../key" must not contain unprintable or non-ASCII characters')
261
+ end
262
+
263
+ it 'returns nil if `hostprivkey` is overridden' do
264
+ Puppet[:certname] = 'foo'
265
+ Puppet[:hostprivkey] = File.join(fixture_dir, "signed-key.pem")
266
+
267
+ expect(provider.load_private_key('foo')).to be_nil
268
+ end
269
+
270
+ it 'raises if the private key is unreadable' do
271
+ provider.stubs(:load_pem).raises(Errno::EACCES, 'Permission denied')
272
+
273
+ expect {
274
+ provider.load_private_key('signed')
275
+ }.to raise_error(Puppet::Error, "Failed to load private key for 'signed'")
276
+ end
277
+
278
+ context 'that are encrypted' do
279
+ it 'raises without a passphrase' do
280
+ # password is 74695716c8b6
281
+ expect {
282
+ provider.load_private_key('encrypted-key')
283
+ }.to raise_error(OpenSSL::PKey::RSAError, /Neither PUB key nor PRIV key/)
284
+ end
285
+ end
286
+ end
287
+
288
+ context 'certs' do
289
+ let(:provider) { create_provider(certdir: fixture_dir) }
290
+
291
+ it 'returns nil if it does not exist' do
292
+ provider = create_provider(certdir: '/does/not/exist')
293
+
294
+ expect(provider.load_client_cert('nonexistent')).to be_nil
295
+ end
296
+
297
+ it 'raises if it is required' do
298
+ provider = create_provider(certdir: '/does/not/exist')
299
+
300
+ expect {
301
+ provider.load_client_cert('nonexistent', required: true)
302
+ }.to raise_error(Puppet::Error, %r{The client certificate is missing from '/does/not/exist/nonexistent.pem'})
303
+ end
304
+
305
+ it 'returns a certificate' do
306
+ cert = provider.load_client_cert('signed')
307
+ expect(cert.subject.to_s).to eq('/CN=signed')
308
+ end
309
+
310
+ it 'downcases name' do
311
+ cert = provider.load_client_cert('SIGNED')
312
+ expect(cert.subject.to_s).to eq('/CN=signed')
313
+ end
314
+
315
+ it 'raises if name is invalid' do
316
+ expect {
317
+ provider.load_client_cert('tom/../key')
318
+ }.to raise_error(RuntimeError, 'Certname "tom/../key" must not contain unprintable or non-ASCII characters')
319
+ end
320
+
321
+ it 'returns nil if `hostcert` is overridden' do
322
+ Puppet[:certname] = 'foo'
323
+ Puppet[:hostcert] = File.join(fixture_dir, "signed.pem")
324
+
325
+ expect(provider.load_client_cert('foo')).to be_nil
326
+ end
327
+
328
+ it 'raises if the certificate is unreadable' do
329
+ provider.stubs(:load_pem).raises(Errno::EACCES, 'Permission denied')
330
+
331
+ expect {
332
+ provider.load_client_cert('signed')
333
+ }.to raise_error(Puppet::Error, "Failed to load client certificate for 'signed'")
334
+ end
335
+ end
336
+
337
+ context 'requests' do
338
+ let(:request) { request_fixture('request.pem') }
339
+ let(:provider) { create_provider(requestdir: fixture_dir) }
340
+
341
+ it 'returns nil if it does not exist' do
342
+ expect(provider.load_request('whatever')).to be_nil
343
+ end
344
+
345
+ it 'returns a request' do
346
+ expect(provider.load_request('request')).to be_a(OpenSSL::X509::Request)
347
+ end
348
+
349
+ it 'downcases name' do
350
+ csr = provider.load_request('REQUEST')
351
+ expect(csr.subject.to_s).to eq('/CN=pending')
352
+ end
353
+
354
+ it 'raises if name is invalid' do
355
+ expect {
356
+ provider.load_request('tom/../key')
357
+ }.to raise_error(RuntimeError, 'Certname "tom/../key" must not contain unprintable or non-ASCII characters')
358
+ end
359
+
360
+ it 'ignores `hostcsr`' do
361
+ Puppet[:hostcsr] = File.join(fixture_dir, "doesnotexist.pem")
362
+
363
+ expect(provider.load_request('request')).to be_a(OpenSSL::X509::Request)
364
+ end
365
+
366
+ it 'raises if the certificate is unreadable' do
367
+ provider.stubs(:load_pem).raises(Errno::EACCES, 'Permission denied')
368
+
369
+ expect {
370
+ provider.load_request('pending')
371
+ }.to raise_error(Puppet::Error, "Failed to load certificate request for 'pending'")
372
+ end
373
+ end
374
+ end
375
+
376
+ context 'when saving' do
377
+ let(:name) { 'tom' }
378
+
379
+ context 'private keys' do
380
+ let(:privatekeydir) { tmpdir('privatekeydir') }
381
+ let(:private_key) { key_fixture('signed-key.pem') }
382
+ let(:path) { File.join(privatekeydir, 'tom.pem') }
383
+ let(:provider) { create_provider(privatekeydir: privatekeydir) }
384
+
385
+ it 'writes PEM encoded private key' do
386
+ provider.save_private_key(name, private_key)
387
+
388
+ expect(File.read(path)).to match(/\A-----BEGIN RSA PRIVATE KEY-----.*?-----END RSA PRIVATE KEY-----\Z/m)
389
+ end
390
+
391
+ it 'sets mode to 640' do
392
+ provider.save_private_key(name, private_key)
393
+
394
+ expects_private_file(path)
395
+ end
396
+
397
+ it 'downcases name' do
398
+ provider.save_private_key('TOM', private_key)
399
+
400
+ expect(File).to be_exist(path)
401
+ end
402
+
403
+ it 'raises if name is invalid' do
404
+ expect {
405
+ provider.save_private_key('tom/../key', private_key)
406
+ }.to raise_error(RuntimeError, 'Certname "tom/../key" must not contain unprintable or non-ASCII characters')
407
+ end
408
+
409
+ it 'raises if the private key is unwritable' do
410
+ provider.stubs(:save_pem).raises(Errno::EACCES, 'Permission denied')
411
+
412
+ expect {
413
+ provider.save_private_key(name, private_key)
414
+ }.to raise_error(Puppet::Error, "Failed to save private key for '#{name}'")
415
+ end
416
+ end
417
+
418
+ context 'certs' do
419
+ let(:certdir) { tmpdir('certdir') }
420
+ let(:client_cert) { cert_fixture('signed.pem') }
421
+ let(:path) { File.join(certdir, 'tom.pem') }
422
+ let(:provider) { create_provider(certdir: certdir) }
423
+
424
+ it 'writes PEM encoded cert' do
425
+ provider.save_client_cert(name, client_cert)
426
+
427
+ expect(File.read(path)).to match(/\A-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----\Z/m)
428
+ end
429
+
430
+ it 'sets mode to 644' do
431
+ provider.save_client_cert(name, client_cert)
432
+
433
+ expects_public_file(path)
434
+ end
435
+
436
+ it 'downcases name' do
437
+ provider.save_client_cert('TOM', client_cert)
438
+
439
+ expect(File).to be_exist(path)
440
+ end
441
+
442
+ it 'raises if name is invalid' do
443
+ expect {
444
+ provider.save_client_cert('tom/../key', client_cert)
445
+ }.to raise_error(RuntimeError, 'Certname "tom/../key" must not contain unprintable or non-ASCII characters')
446
+ end
447
+
448
+ it 'raises if the cert is unwritable' do
449
+ provider.stubs(:save_pem).raises(Errno::EACCES, 'Permission denied')
450
+
451
+ expect {
452
+ provider.save_client_cert(name, client_cert)
453
+ }.to raise_error(Puppet::Error, "Failed to save client certificate for '#{name}'")
454
+ end
455
+ end
456
+
457
+ context 'requests' do
458
+ let(:requestdir) { tmpdir('requestdir') }
459
+ let(:csr) { request_fixture('request.pem') }
460
+ let(:path) { File.join(requestdir, 'tom.pem') }
461
+ let(:provider) { create_provider(requestdir: requestdir) }
462
+
463
+ it 'writes PEM encoded request' do
464
+ provider.save_request(name, csr)
465
+
466
+ expect(File.read(path)).to match(/\A-----BEGIN CERTIFICATE REQUEST-----.*?-----END CERTIFICATE REQUEST-----\Z/m)
467
+ end
468
+
469
+ it 'sets mode to 644' do
470
+ provider.save_request(name, csr)
471
+
472
+ expects_public_file(path)
473
+ end
474
+
475
+ it 'downcases name' do
476
+ provider.save_request('TOM', csr)
477
+
478
+ expect(File).to be_exist(path)
479
+ end
480
+
481
+ it 'raises if name is invalid' do
482
+ expect {
483
+ provider.save_request('tom/../key', csr)
484
+ }.to raise_error(RuntimeError, 'Certname "tom/../key" must not contain unprintable or non-ASCII characters')
485
+ end
486
+
487
+ it 'raises if the request is unwritable' do
488
+ provider.stubs(:save_pem).raises(Errno::EACCES, 'Permission denied')
489
+
490
+ expect {
491
+ provider.save_request(name, csr)
492
+ }.to raise_error(Puppet::Error, "Failed to save certificate request for '#{name}'")
493
+ end
494
+ end
495
+ end
496
+
497
+ context 'when deleting' do
498
+ context 'requests' do
499
+ let(:name) { 'jerry' }
500
+ let(:requestdir) { tmpdir('cert_provider') }
501
+ let(:provider) { create_provider(requestdir: requestdir) }
502
+
503
+ it 'returns true if request was deleted' do
504
+ path = File.join(requestdir, "#{name}.pem")
505
+ File.write(path, "PEM")
506
+
507
+ expect(provider.delete_request(name)).to eq(true)
508
+ expect(File).not_to be_exist(path)
509
+ end
510
+
511
+ it 'returns false if the request is non-existent' do
512
+ path = File.join(requestdir, "#{name}.pem")
513
+
514
+ expect(provider.delete_request(name)).to eq(false)
515
+ expect(File).to_not be_exist(path)
516
+ end
517
+
518
+ it 'raises if the file is undeletable' do
519
+ provider.stubs(:delete_pem).raises(Errno::EACCES, 'Permission denied')
520
+
521
+ expect {
522
+ provider.delete_request(name)
523
+ }.to raise_error(Puppet::Error, "Failed to delete certificate request for '#{name}'")
524
+ end
525
+ end
526
+ end
527
+ end