summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2008-03-19 23:46:43 -0500
committerLuke Kanies <luke@madstop.com>2008-04-15 21:34:06 -0500
commitb9d647974915da05af8036933e71bc1e6dc00374 (patch)
tree61f4968c292a364978f8681bfccbd1730e6ab8cd
parent1efed0304ebdc13a55eb2d865cdc4965c5253d3a (diff)
downloadpuppet-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.rb6
-rw-r--r--lib/puppet/indirector/key/file.rb4
-rw-r--r--lib/puppet/ssl/certificate_authority.rb12
-rw-r--r--lib/puppet/ssl/certificate_factory.rb10
-rw-r--r--lib/puppet/ssl/host.rb25
-rw-r--r--lib/puppet/ssl/key.rb14
-rw-r--r--lib/puppet/util/settings.rb2
-rwxr-xr-xspec/unit/ssl/certificate_authority.rb14
-rwxr-xr-xspec/unit/ssl/host.rb87
-rwxr-xr-xspec/unit/ssl/key.rb64
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