diff options
| author | Luke Kanies <luke@madstop.com> | 2008-03-19 23:46:43 -0500 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2008-04-15 21:34:06 -0500 |
| commit | b9d647974915da05af8036933e71bc1e6dc00374 (patch) | |
| tree | 61f4968c292a364978f8681bfccbd1730e6ab8cd | |
| parent | 1efed0304ebdc13a55eb2d865cdc4965c5253d3a (diff) | |
| download | puppet-b9d647974915da05af8036933e71bc1e6dc00374.tar.gz puppet-b9d647974915da05af8036933e71bc1e6dc00374.tar.xz puppet-b9d647974915da05af8036933e71bc1e6dc00374.zip | |
We have a basically functional CA -- it can sign
requests and return certificates. There's still plenty
more work to do, but I'm probably not much more than a
day away from redoing puppetca to use this code.
| -rw-r--r-- | lib/puppet/indirector/certificate_request/ca_file.rb | 6 | ||||
| -rw-r--r-- | lib/puppet/indirector/key/file.rb | 4 | ||||
| -rw-r--r-- | lib/puppet/ssl/certificate_authority.rb | 12 | ||||
| -rw-r--r-- | lib/puppet/ssl/certificate_factory.rb | 10 | ||||
| -rw-r--r-- | lib/puppet/ssl/host.rb | 25 | ||||
| -rw-r--r-- | lib/puppet/ssl/key.rb | 14 | ||||
| -rw-r--r-- | lib/puppet/util/settings.rb | 2 | ||||
| -rwxr-xr-x | spec/unit/ssl/certificate_authority.rb | 14 | ||||
| -rwxr-xr-x | spec/unit/ssl/host.rb | 87 | ||||
| -rwxr-xr-x | spec/unit/ssl/key.rb | 64 |
10 files changed, 186 insertions, 52 deletions
diff --git a/lib/puppet/indirector/certificate_request/ca_file.rb b/lib/puppet/indirector/certificate_request/ca_file.rb index 08aa73eaf..24c262ef3 100644 --- a/lib/puppet/indirector/certificate_request/ca_file.rb +++ b/lib/puppet/indirector/certificate_request/ca_file.rb @@ -5,4 +5,10 @@ class Puppet::SSL::CertificateRequest::CaFile < Puppet::Indirector::SslFile desc "Manage the CA collection of certificate requests on disk." store_in :csrdir + + def save(instance, *args) + result = super + Puppet.notice "%s has a waiting certificate request" % instance.name + result + end end diff --git a/lib/puppet/indirector/key/file.rb b/lib/puppet/indirector/key/file.rb index 9efcd1a31..03e94ed2d 100644 --- a/lib/puppet/indirector/key/file.rb +++ b/lib/puppet/indirector/key/file.rb @@ -11,7 +11,7 @@ class Puppet::SSL::Key::File < Puppet::Indirector::SslFile end # Remove the public key, in addition to the private key - def destroy(key) + def destroy(key, options = {}) super return unless FileTest.exist?(public_key_path(key.name)) @@ -24,7 +24,7 @@ class Puppet::SSL::Key::File < Puppet::Indirector::SslFile end # Save the public key, in addition to the private key. - def save(key) + def save(key, options = {}) super begin diff --git a/lib/puppet/ssl/certificate_authority.rb b/lib/puppet/ssl/certificate_authority.rb index 19887c70b..aa997aaf6 100644 --- a/lib/puppet/ssl/certificate_authority.rb +++ b/lib/puppet/ssl/certificate_authority.rb @@ -44,7 +44,7 @@ class Puppet::SSL::CertificateAuthority < Puppet::SSL::Host request.generate(key) # Create a self-signed certificate. - @certificate = sign(request, :ca, true) + @certificate = sign(name, :ca, request) Puppet.settings.write(:cacert) do |f| f.print @certificate.to_s @@ -54,6 +54,8 @@ class Puppet::SSL::CertificateAuthority < Puppet::SSL::Host end def initialize + Puppet.settings.use :main, :ssl, :ca + # Always name the ca after the host we're running on. super(Puppet[:certname]) @@ -72,12 +74,14 @@ class Puppet::SSL::CertificateAuthority < Puppet::SSL::Host unless csr = Puppet::SSL::CertificateRequest.find(host, :in => :ca_file) raise ArgumentError, "Could not find certificate request for %s" % host end - issuer = certificate.content + issuer = certificate end cert = Puppet::SSL::Certificate.new(host) cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result + Puppet.notice "Signed certificate request for %s" % host + # Save the now-signed cert, unless it's a self-signed cert, since we # assume it goes somewhere else. cert.save(:in => :ca_file) unless self_signing_csr @@ -88,11 +92,11 @@ class Puppet::SSL::CertificateAuthority < Puppet::SSL::Host # Do all of the initialization necessary to set up our # ca. def setup_ca - generate_key unless key - # Make sure we've got a password protecting our private key. generate_password unless password? + generate_key unless key + # And then make sure we've got the whole kaboodle. This will # create a self-signed CA certificate if we don't already have one, # and it will just read it in if we do. diff --git a/lib/puppet/ssl/certificate_factory.rb b/lib/puppet/ssl/certificate_factory.rb index 47b9f74d7..4b1669804 100644 --- a/lib/puppet/ssl/certificate_factory.rb +++ b/lib/puppet/ssl/certificate_factory.rb @@ -58,9 +58,11 @@ class Puppet::SSL::CertificateFactory method = "add_#{@cert_type.to_s}_extensions" - raise ArgumentError, "%s is an invalid certificate type" % @cert_type unless respond_to?(method) - - send(method) + begin + send(method) + rescue NoMethodError + raise ArgumentError, "%s is an invalid certificate type" % @cert_type + end @extensions << @ef.create_extension("nsComment", "Puppet Ruby/OpenSSL Generated Certificate") @extensions << @ef.create_extension("basicConstraints", @basic_constraint, true) @@ -72,7 +74,7 @@ class Puppet::SSL::CertificateFactory @cert.extensions = @extensions # for some reason this _must_ be the last extension added - @extensions << ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if @cert_type == :ca + @extensions << @ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if @cert_type == :ca end # TTL for new certificates in seconds. If config param :ca_ttl is set, diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb index a50355509..0e65d30b1 100644 --- a/lib/puppet/ssl/host.rb +++ b/lib/puppet/ssl/host.rb @@ -57,7 +57,7 @@ class Puppet::SSL::Host manage_file :key do @key = Key.new(name) @key.generate - @key.save + @key.save :in => :file true end @@ -66,7 +66,7 @@ class Puppet::SSL::Host generate_key unless key @certificate_request = CertificateRequest.new(name) @certificate_request.generate(key) - @certificate_request.save + @certificate_request.save :in => :file return true end @@ -78,7 +78,7 @@ class Puppet::SSL::Host @certificate = Certificate.new(name) if @certificate.generate(certificate_request) - @certificate.save + @certificate.save :in => :file return true else return false @@ -107,4 +107,23 @@ class Puppet::SSL::Host def public_key key.public_key end + + # Try to get our signed certificate. + def retrieve_signed_certificate(source = :ca_file) + if cert = Puppet::SSL::Certificate.find(name, :in => source) + @certificate = cert + @certificate.save :in => :file + return true + else + return false + end + end + + # Send our CSR to the server, defaulting to the + # local CA. + def send_certificate_request(dest = :ca_file) + raise ArgumentError, "Must generate CSR before sending to server" unless certificate_request + + @certificate_request.save :in => dest + end end diff --git a/lib/puppet/ssl/key.rb b/lib/puppet/ssl/key.rb index 35370ac69..65294ac00 100644 --- a/lib/puppet/ssl/key.rb +++ b/lib/puppet/ssl/key.rb @@ -13,11 +13,7 @@ class Puppet::SSL::Key < Puppet::SSL::Base # Knows how to create keys with our system defaults. def generate Puppet.info "Creating a new SSL key for %s" % name - if pass = password - @content = OpenSSL::PKey::RSA.new(Puppet[:keylength], pass) - else - @content = OpenSSL::PKey::RSA.new(Puppet[:keylength]) - end + @content = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i) end def password @@ -39,4 +35,12 @@ class Puppet::SSL::Key < Puppet::SSL::Base @content = wrapped_class.new(::File.read(path), password) end + + def to_s + if pass = password + @content.export(OpenSSL::Cipher::DES.new(:EDE3, :CBC), pass) + else + return super + end + end end diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb index 24a71516a..e595e2eea 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -685,7 +685,7 @@ Generated on #{Time.now}. end sync.synchronize(Sync::EX) do - File.open(file, "r+", 0600) do |rf| + File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf| rf.lock_exclusive do if File.exist?(tmpfile) raise Puppet::Error, ".tmp file already exists for %s; Aborting locked write. Check the .tmp file and delete if appropriate" % diff --git a/spec/unit/ssl/certificate_authority.rb b/spec/unit/ssl/certificate_authority.rb index 8fb23d883..3271acb91 100755 --- a/spec/unit/ssl/certificate_authority.rb +++ b/spec/unit/ssl/certificate_authority.rb @@ -7,6 +7,7 @@ require 'puppet/ssl/certificate_authority' describe Puppet::SSL::CertificateAuthority do describe "when initializing" do it "should always set its name to the value of :certname" do + Puppet.settings.stubs(:use) Puppet.settings.expects(:value).with(:certname).returns "whatever" Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup_ca) @@ -14,8 +15,15 @@ describe Puppet::SSL::CertificateAuthority do Puppet::SSL::CertificateAuthority.new.name.should == "whatever" end + it "should use the :main, :ca, and :ssl settings sections" do + Puppet.settings.expects(:use).with(:main, :ssl, :ca) + Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup_ca) + Puppet::SSL::CertificateAuthority.new + end + describe "a new certificate authority" do before do + Puppet.settings.stubs(:use) Puppet.settings.stubs(:value).with(:certname).returns "whatever" end @@ -73,7 +81,7 @@ describe Puppet::SSL::CertificateAuthority do cert = mock 'cert' cert.expects(:to_s).returns "my cert" - Puppet::SSL::CertificateAuthority.any_instance.expects(:sign).with(request, :ca, true).returns cert + Puppet::SSL::CertificateAuthority.any_instance.expects(:sign).with("whatever", :ca, request).returns cert fh = mock 'filehandle' Puppet.settings.expects(:write).with(:cacert).yields fh @@ -90,6 +98,7 @@ describe Puppet::SSL::CertificateAuthority do describe "an existing certificate authority" do it "should read and decrypt the key at :cakey using the password at :capass and it should read the cert at :cacert" do Puppet.settings.stubs(:value).with(:certname).returns "whatever" + Puppet.settings.stubs(:use) paths = {} [:capass, :cakey, :cacert].each do |value| @@ -117,6 +126,7 @@ describe Puppet::SSL::CertificateAuthority do describe "when signing" do before do Puppet.settings.stubs(:value).with(:certname).returns "whatever" + Puppet.settings.stubs(:use) Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true @@ -259,7 +269,7 @@ describe Puppet::SSL::CertificateAuthority do it "should use the CA certificate as the issuer" do Puppet::SSL::CertificateFactory.expects(:new).with do |*args| - args[2] == @cacert.content + args[2] == @cacert end.returns @factory @ca.sign(@name) end diff --git a/spec/unit/ssl/host.rb b/spec/unit/ssl/host.rb index f3ead362a..a4972de9d 100755 --- a/spec/unit/ssl/host.rb +++ b/spec/unit/ssl/host.rb @@ -53,7 +53,7 @@ describe Puppet::SSL::Host do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.expects(:generate) - @key.expects(:save) + @key.expects(:save).with(:in => :file) @host.generate_key.should be_true @host.key.should equal(@realkey) @@ -90,7 +90,7 @@ describe Puppet::SSL::Host do @host.expects(:generate_key).returns(key) @request.stubs(:generate) - @request.stubs(:save) + @request.stubs(:save).with(:in => :file) @host.generate_certificate_request end @@ -101,7 +101,7 @@ describe Puppet::SSL::Host do key = stub 'key', :public_key => mock("public_key") @host.stubs(:key).returns(key) @request.expects(:generate).with(key) - @request.expects(:save) + @request.expects(:save).with(:in => :file) @host.generate_certificate_request.should be_true @host.certificate_request.should equal(@realrequest) @@ -133,7 +133,7 @@ describe Puppet::SSL::Host do @host.expects(:generate_certificate_request) @cert.stubs(:generate) - @cert.stubs(:save) + @cert.stubs(:save).with(:in => :file) @host.generate_certificate end @@ -144,7 +144,7 @@ describe Puppet::SSL::Host do request = stub 'request' @host.stubs(:certificate_request).returns(request) @cert.expects(:generate).with(request).returns(true) - @cert.expects(:save) + @cert.expects(:save).with(:in => :file) @host.generate_certificate.should be_true @host.certificate.should equal(@realcert) @@ -183,4 +183,81 @@ describe Puppet::SSL::Host do @host.destroy end end + + describe "when sending its CSR to the CA" do + before do + @realrequest = "real request" + @request = stub 'request', :content => @realrequest + + @host.instance_variable_set("@certificate_request", @request) + end + + it "should be able to send its CSR" do + @request.expects(:save) + + @host.send_certificate_request + end + + it "should default to sending its CSR to the :ca_file" do + @request.expects(:save).with(:in => :ca_file) + + @host.send_certificate_request + end + + it "should allow specification of another CA terminus" do + @request.expects(:save).with(:in => :rest) + + @host.send_certificate_request :rest + end + end + + describe "when retrieving its signed certificate from the CA" do + before do + @realcert = "real cert" + @cert = stub 'cert', :content => @realcert + end + + it "should be able to send its CSR" do + Puppet::SSL::Certificate.expects(:find).with { |*args| args[0] == @host.name } + + @host.retrieve_signed_certificate + end + + it "should default to searching for its certificate in the :ca_file" do + Puppet::SSL::Certificate.expects(:find).with { |*args| args[1] == {:in => :ca_file} } + + @host.retrieve_signed_certificate + end + + it "should allow specification of another CA terminus" do + Puppet::SSL::Certificate.expects(:find).with { |*args| args[1] == {:in => :rest} } + + @host.retrieve_signed_certificate :rest + end + + it "should return true and set its certificate if retrieval was successful" do + cert = stub 'cert', :content => "mycert" + Puppet::SSL::Certificate.stubs(:find).returns cert + + @host.retrieve_signed_certificate.should be_true + @host.certificate.should == "mycert" + end + + it "should save the retrieved certificate to the local disk" do + cert = stub 'cert', :content => "mycert" + Puppet::SSL::Certificate.stubs(:find).returns cert + + cert.expects(:save).with :in => :file + + @host.retrieve_signed_certificate + @host.certificate + end + + it "should return false and not set its certificate if retrieval was unsuccessful" do + Puppet::SSL::Certificate.stubs(:find).returns nil + + @host.retrieve_signed_certificate.should be_false + @host.certificate.should be_nil + end + end end diff --git a/spec/unit/ssl/key.rb b/spec/unit/ssl/key.rb index 4978a591b..57ad943c2 100755 --- a/spec/unit/ssl/key.rb +++ b/spec/unit/ssl/key.rb @@ -83,49 +83,61 @@ describe Puppet::SSL::Key do end it "should create the private key with the keylength specified in the settings" do - Puppet.settings.expects(:value).with(:keylength).returns(50) + Puppet.settings.expects(:value).with(:keylength).returns("50") OpenSSL::PKey::RSA.expects(:new).with(50).returns(@key) @instance.generate end - it "should fail if a provided password file does not exist" do - FileTest.expects(:exist?).with("/path/to/pass").returns false + it "should set the content to the generated key" do + OpenSSL::PKey::RSA.stubs(:new).returns(@key) + @instance.generate + @instance.content.should equal(@key) + end - lambda { @instance.password_file = "/path/to/pass" }.should raise_error(ArgumentError) + it "should return the generated key" do + OpenSSL::PKey::RSA.stubs(:new).returns(@key) + @instance.generate.should equal(@key) end - it "should return the contents of the password file as its password" do - FileTest.expects(:exist?).with("/path/to/pass").returns true - File.expects(:read).with("/path/to/pass").returns "my password" + it "should return the key in pem format" do + @instance.generate + @instance.content.expects(:to_pem).returns "my normal key" + @instance.to_s.should == "my normal key" + end - @instance.password_file = "/path/to/pass" + describe "with a password file set" do + it "should fail if the password file does not exist" do + FileTest.expects(:exist?).with("/path/to/pass").returns false - @instance.password.should == "my password" - end + lambda { @instance.password_file = "/path/to/pass" }.should raise_error(ArgumentError) + end - it "should create the private key with any provided password" do - Puppet.settings.stubs(:value).with(:keylength).returns(50) + it "should return the contents of the password file as its password" do + FileTest.expects(:exist?).with("/path/to/pass").returns true + File.expects(:read).with("/path/to/pass").returns "my password" - FileTest.expects(:exist?).with("/path/to/pass").returns true - File.expects(:read).with("/path/to/pass").returns "my password" + @instance.password_file = "/path/to/pass" - @instance.password_file = "/path/to/pass" + @instance.password.should == "my password" + end - OpenSSL::PKey::RSA.expects(:new).with(50, "my password").returns(@key) + it "should export the private key to text using the password" do + Puppet.settings.stubs(:value).with(:keylength).returns("50") - @instance.generate - end + FileTest.expects(:exist?).with("/path/to/pass").returns true + @instance.password_file = "/path/to/pass" + @instance.stubs(:password).returns "my password" - it "should set the content to the generated key" do - OpenSSL::PKey::RSA.stubs(:new).returns(@key) - @instance.generate - @instance.content.should equal(@key) - end + OpenSSL::PKey::RSA.expects(:new).returns(@key) + @instance.generate - it "should return the generated key" do - OpenSSL::PKey::RSA.stubs(:new).returns(@key) - @instance.generate.should equal(@key) + cipher = mock 'cipher' + OpenSSL::Cipher::DES.expects(:new).with(:EDE3, :CBC).returns cipher + @key.expects(:export).with(cipher, "my password").returns "my encrypted key" + + @instance.to_s.should == "my encrypted key" + end end end end |
