4
require 'puppet/ssl/certificate_authority'
6
describe Puppet::SSL::CertificateAuthority do
8
Puppet::Util::Cacher.expire
9
Puppet.settings.clearused
14
@key.stubs(:content).returns "cakey"
15
@cacert = mock 'certificate'
16
@cacert.stubs(:content).returns "cacertificate"
18
@host = stub 'ssl_host', :key => @key, :certificate => @cacert, :name => Puppet::SSL::Host.ca_name
21
it "should have a class method for returning a singleton instance" do
22
Puppet::SSL::CertificateAuthority.should respond_to(:instance)
25
describe "when finding an existing instance" do
26
describe "and the host is a CA host and the run_mode is master" do
28
Puppet.settings.stubs(:value).with(:ca).returns true
29
Puppet.run_mode.stubs(:master?).returns true
32
Puppet::SSL::CertificateAuthority.stubs(:new).returns @ca
35
it "should return an instance" do
36
Puppet::SSL::CertificateAuthority.instance.should equal(@ca)
39
it "should always return the same instance" do
40
Puppet::SSL::CertificateAuthority.instance.should equal(Puppet::SSL::CertificateAuthority.instance)
44
describe "and the host is not a CA host" do
45
it "should return nil" do
46
Puppet.settings.stubs(:value).with(:ca).returns false
47
Puppet.run_mode.stubs(:master?).returns true
50
Puppet::SSL::CertificateAuthority.expects(:new).never
51
Puppet::SSL::CertificateAuthority.instance.should be_nil
55
describe "and the run_mode is not master" do
56
it "should return nil" do
57
Puppet.settings.stubs(:value).with(:ca).returns true
58
Puppet.run_mode.stubs(:master?).returns false
61
Puppet::SSL::CertificateAuthority.expects(:new).never
62
Puppet::SSL::CertificateAuthority.instance.should be_nil
67
describe "when initializing" do
69
Puppet.settings.stubs(:use)
70
Puppet.settings.stubs(:value).returns "ca_testing"
72
Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup)
75
it "should always set its name to the value of :certname" do
76
Puppet.settings.expects(:value).with(:certname).returns "ca_testing"
78
Puppet::SSL::CertificateAuthority.new.name.should == "ca_testing"
81
it "should create an SSL::Host instance whose name is the 'ca_name'" do
82
Puppet::SSL::Host.expects(:ca_name).returns "caname"
85
Puppet::SSL::Host.expects(:new).with("caname").returns host
87
Puppet::SSL::CertificateAuthority.new
90
it "should use the :main, :ca, and :ssl settings sections" do
91
Puppet.settings.expects(:use).with(:main, :ssl, :ca)
92
Puppet::SSL::CertificateAuthority.new
95
it "should create an inventory instance" do
96
Puppet::SSL::Inventory.expects(:new).returns "inventory"
98
Puppet::SSL::CertificateAuthority.new.inventory.should == "inventory"
101
it "should make sure the CA is set up" do
102
Puppet::SSL::CertificateAuthority.any_instance.expects(:setup)
104
Puppet::SSL::CertificateAuthority.new
108
describe "when setting itself up" do
109
it "should generate the CA certificate if it does not have one" do
110
Puppet.settings.stubs :use
113
Puppet::SSL::Host.stubs(:new).returns host
115
host.expects(:certificate).returns nil
117
Puppet::SSL::CertificateAuthority.any_instance.expects(:generate_ca_certificate)
118
Puppet::SSL::CertificateAuthority.new
122
describe "when retrieving the certificate revocation list" do
124
Puppet.settings.stubs(:use)
125
Puppet.settings.stubs(:value).returns "ca_testing"
126
Puppet.settings.stubs(:value).with(:cacrl).returns "/my/crl"
128
cert = stub("certificate", :content => "real_cert")
129
key = stub("key", :content => "real_key")
130
@host = stub 'host', :certificate => cert, :name => "hostname", :key => key
132
Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup)
133
@ca = Puppet::SSL::CertificateAuthority.new
135
@ca.stubs(:host).returns @host
138
it "should return any found CRL instance" do
140
Puppet::SSL::CertificateRevocationList.indirection.expects(:find).returns crl
141
@ca.crl.should equal(crl)
144
it "should create, generate, and save a new CRL instance of no CRL can be found" do
145
crl = Puppet::SSL::CertificateRevocationList.new("fakename")
146
Puppet::SSL::CertificateRevocationList.indirection.expects(:find).returns nil
148
Puppet::SSL::CertificateRevocationList.expects(:new).returns crl
150
crl.expects(:generate).with(@ca.host.certificate.content, @ca.host.key.content)
151
Puppet::SSL::CertificateRevocationList.indirection.expects(:save).with(crl)
153
@ca.crl.should equal(crl)
157
describe "when generating a self-signed CA certificate" do
159
Puppet.settings.stubs(:use)
160
Puppet.settings.stubs(:value).returns "ca_testing"
162
Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup)
163
Puppet::SSL::CertificateAuthority.any_instance.stubs(:crl)
164
@ca = Puppet::SSL::CertificateAuthority.new
166
@host = stub 'host', :key => mock("key"), :name => "hostname", :certificate => mock('certificate')
168
Puppet::SSL::CertificateRequest.any_instance.stubs(:generate)
170
@ca.stubs(:host).returns @host
173
it "should create and store a password at :capass" do
174
Puppet.settings.expects(:value).with(:capass).returns "/path/to/pass"
176
FileTest.expects(:exist?).with("/path/to/pass").returns false
178
fh = mock 'filehandle'
179
Puppet.settings.expects(:write).with(:capass).yields fh
181
fh.expects(:print).with { |s| s.length > 18 }
185
@ca.generate_ca_certificate
188
it "should generate a key if one does not exist" do
189
@ca.stubs :generate_password
192
@ca.host.expects(:key).returns nil
193
@ca.host.expects(:generate_key)
195
@ca.generate_ca_certificate
198
it "should create and sign a self-signed cert using the CA name" do
199
request = mock 'request'
200
Puppet::SSL::CertificateRequest.expects(:new).with(@ca.host.name).returns request
201
request.expects(:generate).with(@ca.host.key)
203
@ca.expects(:sign).with(@host.name, :ca, request)
205
@ca.stubs :generate_password
207
@ca.generate_ca_certificate
210
it "should generate its CRL" do
211
@ca.stubs :generate_password
214
@ca.host.expects(:key).returns nil
215
@ca.host.expects(:generate_key)
219
@ca.generate_ca_certificate
223
describe "when signing" do
225
Puppet.settings.stubs(:use)
227
Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true
231
Puppet::SSL::Host.expects(:new).with(Puppet::SSL::Host.ca_name).returns @host
233
@ca = Puppet::SSL::CertificateAuthority.new
236
@real_cert = stub 'realcert', :sign => nil
237
@cert = Puppet::SSL::Certificate.new(@name)
238
@cert.content = @real_cert
240
Puppet::SSL::Certificate.stubs(:new).returns @cert
242
@cert.stubs(:content=)
243
Puppet::SSL::Certificate.indirection.stubs(:save)
245
# Stub out the factory
246
@factory = stub 'factory', :result => "my real cert"
247
Puppet::SSL::CertificateFactory.stubs(:new).returns @factory
249
@request = stub 'request', :content => "myrequest", :name => @name
252
@inventory = stub 'inventory', :add => nil
253
@ca.stubs(:inventory).returns @inventory
255
Puppet::SSL::CertificateRequest.indirection.stubs(:destroy)
258
describe "and calculating the next certificate serial number" do
260
@path = "/path/to/serial"
261
Puppet.settings.stubs(:value).with(:serial).returns @path
263
@filehandle = stub 'filehandle', :<< => @filehandle
264
Puppet.settings.stubs(:readwritelock).with(:serial).yields @filehandle
267
it "should default to 0x1 for the first serial number" do
268
@ca.next_serial.should == 0x1
271
it "should return the current content of the serial file" do
272
FileTest.stubs(:exist?).with(@path).returns true
273
File.expects(:read).with(@path).returns "0002"
275
@ca.next_serial.should == 2
278
it "should write the next serial number to the serial file as hex" do
279
@filehandle.expects(:<<).with("0002")
284
it "should lock the serial file while writing" do
285
Puppet.settings.expects(:readwritelock).with(:serial)
291
describe "its own certificate" do
294
@ca.stubs(:next_serial).returns @serial
297
it "should not look up a certificate request for the host" do
298
Puppet::SSL::CertificateRequest.indirection.expects(:find).never
300
@ca.sign(@name, :ca, @request)
303
it "should use a certificate type of :ca" do
304
Puppet::SSL::CertificateFactory.expects(:new).with do |*args|
307
@ca.sign(@name, :ca, @request)
310
it "should pass the provided CSR as the CSR" do
311
Puppet::SSL::CertificateFactory.expects(:new).with do |*args|
312
args[1] == "myrequest"
314
@ca.sign(@name, :ca, @request)
317
it "should use the provided CSR's content as the issuer" do
318
Puppet::SSL::CertificateFactory.expects(:new).with do |*args|
319
args[2] == "myrequest"
321
@ca.sign(@name, :ca, @request)
324
it "should pass the next serial as the serial number" do
325
Puppet::SSL::CertificateFactory.expects(:new).with do |*args|
328
@ca.sign(@name, :ca, @request)
331
it "should save the resulting certificate" do
332
Puppet::SSL::Certificate.indirection.expects(:save).with(@cert)
334
@ca.sign(@name, :ca, @request)
338
describe "another host's certificate" do
341
@ca.stubs(:next_serial).returns @serial
343
Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request
344
Puppet::SSL::CertificateRequest.indirection.stubs :save
347
it "should use a certificate type of :server" do
348
Puppet::SSL::CertificateFactory.expects(:new).with do |*args|
355
it "should use look up a CSR for the host in the :ca_file terminus" do
356
Puppet::SSL::CertificateRequest.indirection.expects(:find).with(@name).returns @request
361
it "should fail if no CSR can be found for the host" do
362
Puppet::SSL::CertificateRequest.indirection.expects(:find).with(@name).returns nil
364
lambda { @ca.sign(@name) }.should raise_error(ArgumentError)
367
it "should use the CA certificate as the issuer" do
368
Puppet::SSL::CertificateFactory.expects(:new).with do |*args|
369
args[2] == @cacert.content
374
it "should pass the next serial as the serial number" do
375
Puppet::SSL::CertificateFactory.expects(:new).with do |*args|
381
it "should sign the resulting certificate using its real key and a digest" do
382
digest = mock 'digest'
383
OpenSSL::Digest::SHA1.expects(:new).returns digest
385
key = stub 'key', :content => "real_key"
386
@ca.host.stubs(:key).returns key
388
@cert.content.expects(:sign).with("real_key", digest)
392
it "should save the resulting certificate" do
393
Puppet::SSL::Certificate.indirection.stubs(:save).with(@cert)
397
it "should remove the host's certificate request" do
398
Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with(@name)
404
it "should create a certificate instance with the content set to the newly signed x509 certificate" do
406
@ca.stubs(:next_serial).returns @serial
408
Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request
409
Puppet::SSL::Certificate.indirection.stubs :save
410
Puppet::SSL::Certificate.expects(:new).with(@name).returns @cert
415
it "should return the certificate instance" do
416
@ca.stubs(:next_serial).returns @serial
417
Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request
418
Puppet::SSL::Certificate.indirection.stubs :save
419
@ca.sign(@name).should equal(@cert)
422
it "should add the certificate to its inventory" do
423
@ca.stubs(:next_serial).returns @serial
424
@inventory.expects(:add).with(@cert)
426
Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request
427
Puppet::SSL::Certificate.indirection.stubs :save
431
it "should have a method for triggering autosigning of available CSRs" do
432
@ca.should respond_to(:autosign)
435
describe "when autosigning certificates" do
436
it "should do nothing if autosign is disabled" do
437
Puppet.settings.expects(:value).with(:autosign).returns 'false'
439
Puppet::SSL::CertificateRequest.indirection.expects(:search).never
443
it "should do nothing if no autosign.conf exists" do
444
Puppet.settings.expects(:value).with(:autosign).returns '/auto/sign'
445
FileTest.expects(:exist?).with("/auto/sign").returns false
447
Puppet::SSL::CertificateRequest.indirection.expects(:search).never
451
describe "and autosign is enabled and the autosign.conf file exists" do
453
Puppet.settings.stubs(:value).with(:autosign).returns '/auto/sign'
454
FileTest.stubs(:exist?).with("/auto/sign").returns true
455
File.stubs(:readlines).with("/auto/sign").returns ["one\n", "two\n"]
457
Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns []
459
@store = stub 'store', :allow => nil
460
Puppet::Network::AuthStore.stubs(:new).returns @store
463
describe "when creating the AuthStore instance to verify autosigning" do
464
it "should create an AuthStore with each line in the configuration file allowed to be autosigned" do
465
Puppet::Network::AuthStore.expects(:new).returns @store
467
@store.expects(:allow).with("one")
468
@store.expects(:allow).with("two")
473
it "should reparse the autosign configuration on each call" do
474
Puppet::Network::AuthStore.expects(:new).times(2).returns @store
480
it "should ignore comments" do
481
File.stubs(:readlines).with("/auto/sign").returns ["one\n", "#two\n"]
483
@store.expects(:allow).with("one")
487
it "should ignore blank lines" do
488
File.stubs(:readlines).with("/auto/sign").returns ["one\n", "\n"]
490
@store.expects(:allow).with("one")
495
it "should sign all CSRs whose hostname matches the autosign configuration" do
498
Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [csr1, csr2]
501
it "should not sign CSRs whose hostname does not match the autosign configuration" do
504
Puppet::SSL::CertificateRequest.indirection.stubs(:search).returns [csr1, csr2]
510
describe "when managing certificate clients" do
512
Puppet.settings.stubs(:use)
514
Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true
518
Puppet::SSL::Host.expects(:new).returns @host
519
Puppet::SSL::CertificateAuthority.any_instance.stubs(:host).returns @host
521
@cacert = mock 'certificate'
522
@cacert.stubs(:content).returns "cacertificate"
523
@ca = Puppet::SSL::CertificateAuthority.new
526
it "should have a method for acting on the SSL files" do
527
@ca.should respond_to(:apply)
530
describe "when applying a method to a set of hosts" do
531
it "should fail if no subjects have been specified" do
532
lambda { @ca.apply(:generate) }.should raise_error(ArgumentError)
535
it "should create an Interface instance with the specified method and the options" do
536
Puppet::SSL::CertificateAuthority::Interface.expects(:new).with(:generate, :to => :host).returns(stub('applier', :apply => nil))
537
@ca.apply(:generate, :to => :host)
540
it "should apply the Interface with itself as the argument" do
541
applier = stub('applier')
542
applier.expects(:apply).with(@ca)
543
Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns applier
544
@ca.apply(:generate, :to => :ca_testing)
548
it "should be able to list waiting certificate requests" do
549
req1 = stub 'req1', :name => "one"
550
req2 = stub 'req2', :name => "two"
551
Puppet::SSL::CertificateRequest.indirection.expects(:search).with("*").returns [req1, req2]
553
@ca.waiting?.should == %w{one two}
556
it "should delegate removing hosts to the Host class" do
557
Puppet::SSL::Host.expects(:destroy).with("myhost")
559
@ca.destroy("myhost")
562
it "should be able to verify certificates" do
563
@ca.should respond_to(:verify)
566
it "should list certificates as the sorted list of all existing signed certificates" do
567
cert1 = stub 'cert1', :name => "cert1"
568
cert2 = stub 'cert2', :name => "cert2"
569
Puppet::SSL::Certificate.indirection.expects(:search).with("*").returns [cert1, cert2]
570
@ca.list.should == %w{cert1 cert2}
573
describe "and printing certificates" do
574
it "should return nil if the certificate cannot be found" do
575
Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil
576
@ca.print("myhost").should be_nil
579
it "should print certificates by calling :to_text on the host's certificate" do
580
cert1 = stub 'cert1', :name => "cert1", :to_text => "mytext"
581
Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns cert1
582
@ca.print("myhost").should == "mytext"
586
describe "and fingerprinting certificates" do
588
@cert = stub 'cert', :name => "cert", :fingerprint => "DIGEST"
589
Puppet::SSL::Certificate.indirection.stubs(:find).with("myhost").returns @cert
590
Puppet::SSL::CertificateRequest.indirection.stubs(:find).with("myhost")
593
it "should raise an error if the certificate or CSR cannot be found" do
594
Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil
595
Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myhost").returns nil
596
lambda { @ca.fingerprint("myhost") }.should raise_error
599
it "should try to find a CSR if no certificate can be found" do
600
Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil
601
Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myhost").returns @cert
602
@cert.expects(:fingerprint)
603
@ca.fingerprint("myhost")
606
it "should delegate to the certificate fingerprinting" do
607
@cert.expects(:fingerprint)
608
@ca.fingerprint("myhost")
611
it "should propagate the digest algorithm to the certificate fingerprinting system" do
612
@cert.expects(:fingerprint).with(:digest)
613
@ca.fingerprint("myhost", :digest)
617
describe "and verifying certificates" do
619
@store = stub 'store', :verify => true, :add_file => nil, :purpose= => nil, :add_crl => true, :flags= => nil
621
OpenSSL::X509::Store.stubs(:new).returns @store
623
Puppet.settings.stubs(:value).returns "crtstuff"
625
@cert = stub 'cert', :content => "mycert"
626
Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert
628
@crl = stub('crl', :content => "mycrl")
630
@ca.stubs(:crl).returns @crl
633
it "should fail if the host's certificate cannot be found" do
634
Puppet::SSL::Certificate.indirection.expects(:find).with("me").returns(nil)
636
lambda { @ca.verify("me") }.should raise_error(ArgumentError)
639
it "should create an SSL Store to verify" do
640
OpenSSL::X509::Store.expects(:new).returns @store
645
it "should add the CA Certificate to the store" do
646
Puppet.settings.stubs(:value).with(:cacert).returns "/ca/cert"
647
@store.expects(:add_file).with "/ca/cert"
652
it "should add the CRL to the store if the crl is enabled" do
653
@store.expects(:add_crl).with "mycrl"
658
it "should set the store purpose to OpenSSL::X509::PURPOSE_SSL_CLIENT" do
659
Puppet.settings.stubs(:value).with(:cacert).returns "/ca/cert"
660
@store.expects(:add_file).with "/ca/cert"
665
it "should set the store flags to check the crl" do
666
@store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK
671
it "should use the store to verify the certificate" do
672
@cert.expects(:content).returns "mycert"
674
@store.expects(:verify).with("mycert").returns true
679
it "should fail if the verification returns false" do
680
@cert.expects(:content).returns "mycert"
682
@store.expects(:verify).with("mycert").returns false
684
lambda { @ca.verify("me") }.should raise_error
688
describe "and revoking certificates" do
691
@ca.stubs(:crl).returns @crl
693
@ca.stubs(:next_serial).returns 10
695
@real_cert = stub 'real_cert', :serial => 15
696
@cert = stub 'cert', :content => @real_cert
697
Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert
701
it "should fail if the certificate revocation list is disabled" do
702
@ca.stubs(:crl).returns false
704
lambda { @ca.revoke('ca_testing') }.should raise_error(ArgumentError)
708
it "should delegate the revocation to its CRL" do
709
@ca.crl.expects(:revoke)
714
it "should get the serial number from the local certificate if it exists" do
715
@ca.crl.expects(:revoke).with { |serial, key| serial == 15 }
717
Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns @cert
722
it "should get the serial number from inventory if no local certificate exists" do
723
real_cert = stub 'real_cert', :serial => 15
724
cert = stub 'cert', :content => real_cert
725
Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil
727
@ca.inventory.expects(:serial).with("host").returns 16
729
@ca.crl.expects(:revoke).with { |serial, key| serial == 16 }
734
it "should be able to generate a complete new SSL host" do
735
@ca.should respond_to(:generate)
738
describe "and generating certificates" do
740
@host = stub 'host', :generate_certificate_request => nil
741
Puppet::SSL::Host.stubs(:new).returns @host
742
Puppet::SSL::Certificate.indirection.stubs(:find).returns nil
747
it "should fail if a certificate already exists for the host" do
748
Puppet::SSL::Certificate.indirection.expects(:find).with("him").returns "something"
750
lambda { @ca.generate("him") }.should raise_error(ArgumentError)
753
it "should create a new Host instance with the correct name" do
754
Puppet::SSL::Host.expects(:new).with("him").returns @host
759
it "should use the Host to generate the certificate request" do
760
@host.expects :generate_certificate_request
765
it "should sign the generated request" do
766
@ca.expects(:sign).with("him")