diff options
| author | Luke Kanies <luke@madstop.com> | 2008-04-17 14:47:27 -0500 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2008-04-17 14:47:27 -0500 |
| commit | daa8cd57b9f61c40c1b4e6954533f197ee5a2f1d (patch) | |
| tree | 995e10e5727ee9bccc54b209cac834a223f69338 /lib/puppet | |
| parent | 7d2c05e86eb14bc7600dcf1d61ba447cd9b4cab8 (diff) | |
| download | puppet-daa8cd57b9f61c40c1b4e6954533f197ee5a2f1d.tar.gz puppet-daa8cd57b9f61c40c1b4e6954533f197ee5a2f1d.tar.xz puppet-daa8cd57b9f61c40c1b4e6954533f197ee5a2f1d.zip | |
Changing all of the SSL terminus classes to treat CA files specially.
This is a kind of weird design situation. For instance, we've got a
collection of certificates in the :certdir, but then there's a special
CA certificate off by itself. Rather than build a whole separate
infrastructure for managing those separate files (cert and key, at least),
I decided to add special support for specifying where to find the CA-specific
bits, and then code for handling them when necessary.
This requires that we have a standard way of knowing whether we should be
managing the CA bits or normal host files. The Puppet::SSL::Host class now has
a 'ca_name' method that returns the string we're using for the CA name; this
name is currently 'ca'. We have to use a name, because the name is the only
thing that all methods have access to (e.g., when trying to 'find' the right
cert, we only have the name available).
What this means is that if you want access to the CA key or cert, then create
a Puppet::SSL::Host instance with the name 'ca'.
You'll still get the CA cert created with the host's :certname; it will just
be stored in a different location.
Diffstat (limited to 'lib/puppet')
| -rw-r--r-- | lib/puppet/defaults.rb | 7 | ||||
| -rw-r--r-- | lib/puppet/indirector/certificate/file.rb | 1 | ||||
| -rw-r--r-- | lib/puppet/indirector/certificate_revocation_list/ca_file.rb | 8 | ||||
| -rw-r--r-- | lib/puppet/indirector/certificate_revocation_list/file.rb | 8 | ||||
| -rw-r--r-- | lib/puppet/indirector/key/file.rb | 8 | ||||
| -rw-r--r-- | lib/puppet/indirector/ssl_file.rb | 82 | ||||
| -rw-r--r-- | lib/puppet/ssl/certificate_revocation_list.rb | 50 | ||||
| -rw-r--r-- | lib/puppet/ssl/host.rb | 28 |
8 files changed, 139 insertions, 53 deletions
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 300f9bad4..7b206901c 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -183,7 +183,7 @@ module Puppet }, :hostcsr => { :default => "$ssldir/csr_$certname.pem", :mode => 0644, - :desc => "Where individual hosts store and look for their certificates." + :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :mode => 0644, @@ -200,6 +200,11 @@ module Puppet :localcacert => { :default => "$certdir/ca.pem", :mode => 0644, :desc => "Where each client stores the CA certificate." + }, + :hostcrl => { :default => "$ssldir/crl.pem", + :mode => 0644, + :desc => "Where the host's certificate revocation list can be found. + This is distinct from the certificate authority's CRL." } ) diff --git a/lib/puppet/indirector/certificate/file.rb b/lib/puppet/indirector/certificate/file.rb index 9e2e8ed99..5f4ade051 100644 --- a/lib/puppet/indirector/certificate/file.rb +++ b/lib/puppet/indirector/certificate/file.rb @@ -5,4 +5,5 @@ class Puppet::SSL::Certificate::File < Puppet::Indirector::SslFile desc "Manage SSL certificates on disk." store_in :certdir + store_ca_at :cacert end diff --git a/lib/puppet/indirector/certificate_revocation_list/ca_file.rb b/lib/puppet/indirector/certificate_revocation_list/ca_file.rb new file mode 100644 index 000000000..d29a5ce3b --- /dev/null +++ b/lib/puppet/indirector/certificate_revocation_list/ca_file.rb @@ -0,0 +1,8 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/certificate_revocation_list' + +class Puppet::SSL::CertificateRevocationList::CaFile < Puppet::Indirector::SslFile + desc "Manage the CA collection of certificate requests on disk." + + store_at :cacrl +end diff --git a/lib/puppet/indirector/certificate_revocation_list/file.rb b/lib/puppet/indirector/certificate_revocation_list/file.rb new file mode 100644 index 000000000..037aa6b8c --- /dev/null +++ b/lib/puppet/indirector/certificate_revocation_list/file.rb @@ -0,0 +1,8 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/certificate_revocation_list' + +class Puppet::SSL::CertificateRevocationList::File < Puppet::Indirector::SslFile + desc "Manage the global certificate revocation list." + + store_at :hostcrl +end diff --git a/lib/puppet/indirector/key/file.rb b/lib/puppet/indirector/key/file.rb index 41d30a2d4..4536f8aa7 100644 --- a/lib/puppet/indirector/key/file.rb +++ b/lib/puppet/indirector/key/file.rb @@ -5,9 +5,15 @@ class Puppet::SSL::Key::File < Puppet::Indirector::SslFile desc "Manage SSL private and public keys on disk." store_in :privatekeydir + store_ca_at :cakey + # Where should we store the public key? def public_key_path(name) - File.join(Puppet[:publickeydir], name.to_s + ".pem") + if ca?(name) + Puppet[:capub] + else + File.join(Puppet[:publickeydir], name.to_s + ".pem") + end end # Remove the public key, in addition to the private key diff --git a/lib/puppet/indirector/ssl_file.rb b/lib/puppet/indirector/ssl_file.rb index eddf82ac5..582d282ff 100644 --- a/lib/puppet/indirector/ssl_file.rb +++ b/lib/puppet/indirector/ssl_file.rb @@ -1,27 +1,67 @@ require 'puppet/indirector/file' +require 'puppet/ssl/host' class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus + # Specify the directory in which multiple files are stored. def self.store_in(setting) @directory_setting = setting end + # Specify a single file location for storing just one file. + # This is used for things like the CRL. + def self.store_at(setting) + @file_setting = setting + end + + # Specify where a specific ca file should be stored. + def self.store_ca_at(setting) + @ca_setting = setting + end + class << self - attr_reader :directory_setting + attr_reader :directory_setting, :file_setting, :ca_setting end # The full path to where we should store our files. def self.collection_directory - raise(Puppet::DevError, "No setting defined for %s" % self) unless @directory_setting - Puppet.settings[@directory_setting] + return nil unless directory_setting + Puppet.settings[directory_setting] + end + + # The full path to an individual file we would be managing. + def self.file_location + return nil unless file_setting + Puppet.settings[file_setting] + end + + # The full path to a ca file we would be managing. + def self.ca_location + return nil unless ca_setting + Puppet.settings[ca_setting] + end + + # We assume that all files named 'ca' are pointing to individual ca files, + # rather than normal host files. It's a bit hackish, but all the other + # solutions seemed even more hackish. + def ca?(name) + name == Puppet::SSL::Host.ca_name end def initialize Puppet.settings.use(:ssl) + + (collection_directory || file_location) or raise Puppet::DevError, "No file or directory setting provided; terminus %s cannot function" % self.class.name end # Use a setting to determine our path. def path(name) - File.join(collection_directory, name.to_s + ".pem") + if ca?(name) and ca_location + ca_location + elsif collection_directory + File.join(collection_directory, name.to_s + ".pem") + else + file_location + end end # Remove our file. @@ -53,13 +93,9 @@ class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus dir = File.dirname(path) raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [request.key, dir]) unless FileTest.directory?(dir) - raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [request.key, dir]) unless FileTest.writable?(dir) + raise Puppet::Error.new("Cannot save %s; parent directory %s is not writable" % [request.key, dir]) unless FileTest.writable?(dir) - begin - File.open(path, "w") { |f| f.print request.instance.to_s } - rescue => detail - raise Puppet::Error, "Could not write %s: %s" % [request.key, detail] - end + write(request.key, path) { |f| f.print request.instance.to_s } end # Search for more than one file. At this point, it just returns @@ -76,8 +112,32 @@ class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus private - # A demeterish pointer to the collection directory. + # Demeterish pointers to class info. def collection_directory self.class.collection_directory end + + def file_location + self.class.file_location + end + + def ca_location + self.class.ca_location + end + + # Yield a filehandle set up appropriately, either with our settings doing + # the work or opening a filehandle manually. + def write(name, path) + if ca?(name) and ca_location + Puppet.settings.write(self.class.ca_setting) { |f| yield f } + elsif file_location + Puppet.settings.write(self.class.file_setting) { |f| yield f } + else + begin + File.open(path, "w") { |f| yield f } + rescue => detail + raise Puppet::Error, "Could not write %s: %s" % [path, detail] + end + end + end end diff --git a/lib/puppet/ssl/certificate_revocation_list.rb b/lib/puppet/ssl/certificate_revocation_list.rb index e892e276a..939b48443 100644 --- a/lib/puppet/ssl/certificate_revocation_list.rb +++ b/lib/puppet/ssl/certificate_revocation_list.rb @@ -1,12 +1,16 @@ require 'puppet/ssl/base' +require 'puppet/indirector' # Manage the CRL. class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base wraps OpenSSL::X509::CRL + extend Puppet::Indirector + indirects :certificate_revocation_list + # Knows how to create a CRL with our system defaults. - def generate(cert, key) - Puppet.info "Creating a new SSL key for %s" % name + def generate(cert) + Puppet.info "Creating a new certificate revocation list" @content = wrapped_class.new @content.issuer = cert.subject @content.version = 1 @@ -14,29 +18,19 @@ class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base @content end - def initialize(name, cert, key) + def initialize raise Puppet::Error, "Cannot manage the CRL when :cacrl is set to false" if [false, "false"].include?(Puppet[:cacrl]) - @name = name - - read_or_generate(cert, key) - end - - # A stupid indirection method to make this easier to test. Yay. - def read_or_generate(cert, key) - unless read(Puppet[:cacrl]) - generate(cert, key) - save(key) - end + @name = "crl" end # Revoke the certificate with serial number SERIAL issued by this - # CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons - def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) - if @config[:cacrl] == 'false' - raise Puppet::Error, "Revocation requires a CRL, but ca_crl is set to 'false'" - end + # CA, then write the CRL back to disk. The REASON must be one of the + # OpenSSL::OCSP::REVOKED_* reasons + def revoke(serial, cakey, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) time = Time.now + + # Add our revocation to the CRL. revoked = OpenSSL::X509::Revoked.new revoked.serial = serial revoked.time = time @@ -44,13 +38,7 @@ class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base ext = OpenSSL::X509::Extension.new("CRLReason", enum) revoked.add_extension(ext) @content.add_revoked(revoked) - store_crl - end - # Save the CRL to disk. Note that none of the other Base subclasses - # have this method, because they all use the indirector to find and save - # the CRL. - def save(key) # Increment the crlNumber e = @content.extensions.find { |e| e.oid == 'crlNumber' } ext = @content.extensions.reject { |e| e.oid == 'crlNumber' } @@ -59,14 +47,12 @@ class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base @content.extensions = ext # Set last/next update - now = Time.now - @content.last_update = now + @content.last_update = time # Keep CRL valid for 5 years - @content.next_update = now + 5 * 365*24*60*60 + @content.next_update = time + 5 * 365*24*60*60 + + @content.sign(cakey, OpenSSL::Digest::SHA1.new) - sign_with_key(@content) - Puppet.settings.write(:cacrl) do |f| - f.puts @content.to_pem - end + save end end diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb index 6f49175aa..dbd885316 100644 --- a/lib/puppet/ssl/host.rb +++ b/lib/puppet/ssl/host.rb @@ -15,7 +15,15 @@ class Puppet::SSL::Host extend Puppet::Util::ConstantInflector attr_reader :name - attr_accessor :ca + attr_accessor :ca, :password_file + + CA_NAME = "ca" + + # This is the constant that people will use to mark that a given host is + # a certificate authority. + def self.ca_name + CA_NAME + end # Search for more than one host, optionally only specifying # an interest in hosts with a given file type. @@ -36,6 +44,11 @@ class Puppet::SSL::Host end end + # Is this a ca host, meaning that all of its files go in the CA location? + def ca? + ca + end + def key return nil unless (defined?(@key) and @key) or @key = Key.find(name) @key.content @@ -45,8 +58,12 @@ class Puppet::SSL::Host # with no inputs. def generate_key @key = Key.new(name) + + # If a password file is set, then the key will be stored + # encrypted by the password. + @key.password_file = password_file if password_file @key.generate - @key.save :in => :file + @key.save true end @@ -60,7 +77,7 @@ class Puppet::SSL::Host generate_key unless key @certificate_request = CertificateRequest.new(name) @certificate_request.generate(key) - @certificate_request.save :in => :file + @certificate_request.save return true end @@ -71,11 +88,6 @@ class Puppet::SSL::Host @certificate.content end - # Is this a ca host, meaning that all of its files go in the CA collections? - def ca? - ca - end - # Remove all traces of this ssl host def destroy [key, certificate, certificate_request].each do |instance| |
