diff options
author | Markus Roberts <Markus@reality.com> | 2010-07-09 18:12:17 -0700 |
---|---|---|
committer | Markus Roberts <Markus@reality.com> | 2010-07-09 18:12:17 -0700 |
commit | 3180b9d9b2c844dade1d361326600f7001ec66dd (patch) | |
tree | 98fe7c5ac7eb942aac9c39f019a17b0b3f5a57f4 /lib/puppet/ssl | |
parent | 543225970225de5697734bfaf0a6eee996802c04 (diff) | |
download | puppet-3180b9d9b2c844dade1d361326600f7001ec66dd.tar.gz puppet-3180b9d9b2c844dade1d361326600f7001ec66dd.tar.xz puppet-3180b9d9b2c844dade1d361326600f7001ec66dd.zip |
Code smell: Two space indentation
Replaced 106806 occurances of ^( +)(.*$) with
The ruby community almost universally (i.e. everyone but Luke, Markus, and the other eleven people
who learned ruby in the 1900s) uses two-space indentation.
3 Examples:
The code:
end
# Tell getopt which arguments are valid
def test_get_getopt_args
element = Setting.new :name => "foo", :desc => "anything", :settings => Puppet::Util::Settings.new
assert_equal([["--foo", GetoptLong::REQUIRED_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args")
becomes:
end
# Tell getopt which arguments are valid
def test_get_getopt_args
element = Setting.new :name => "foo", :desc => "anything", :settings => Puppet::Util::Settings.new
assert_equal([["--foo", GetoptLong::REQUIRED_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args")
The code:
assert_equal(str, val)
assert_instance_of(Float, result)
end
# Now test it with a passed object
becomes:
assert_equal(str, val)
assert_instance_of(Float, result)
end
# Now test it with a passed object
The code:
end
assert_nothing_raised do
klass[:Yay] = "boo"
klass["Cool"] = :yayness
end
becomes:
end
assert_nothing_raised do
klass[:Yay] = "boo"
klass["Cool"] = :yayness
end
Diffstat (limited to 'lib/puppet/ssl')
-rw-r--r-- | lib/puppet/ssl/base.rb | 140 | ||||
-rw-r--r-- | lib/puppet/ssl/certificate.rb | 42 | ||||
-rw-r--r-- | lib/puppet/ssl/certificate_authority.rb | 426 | ||||
-rw-r--r-- | lib/puppet/ssl/certificate_authority/interface.rb | 250 | ||||
-rw-r--r-- | lib/puppet/ssl/certificate_factory.rb | 238 | ||||
-rw-r--r-- | lib/puppet/ssl/certificate_request.rb | 104 | ||||
-rw-r--r-- | lib/puppet/ssl/certificate_revocation_list.rb | 156 | ||||
-rw-r--r-- | lib/puppet/ssl/host.rb | 460 | ||||
-rw-r--r-- | lib/puppet/ssl/inventory.rb | 66 | ||||
-rw-r--r-- | lib/puppet/ssl/key.rb | 74 |
10 files changed, 978 insertions, 978 deletions
diff --git a/lib/puppet/ssl/base.rb b/lib/puppet/ssl/base.rb index 745d733dc..f9bbcac64 100644 --- a/lib/puppet/ssl/base.rb +++ b/lib/puppet/ssl/base.rb @@ -2,78 +2,78 @@ require 'puppet/ssl' # The base class for wrapping SSL instances. class Puppet::SSL::Base - # For now, use the YAML separator. - SEPARATOR = "\n---\n" - - def self.from_multiple_s(text) - text.split(SEPARATOR).collect { |inst| from_s(inst) } - end - - def self.to_multiple_s(instances) - instances.collect { |inst| inst.to_s }.join(SEPARATOR) - end - - def self.wraps(klass) - @wrapped_class = klass - end - - def self.wrapped_class - raise(Puppet::DevError, "#{self} has not declared what class it wraps") unless defined?(@wrapped_class) - @wrapped_class - end - - attr_accessor :name, :content - - # Is this file for the CA? - def ca? - name == Puppet::SSL::Host.ca_name - end - - def generate - raise Puppet::DevError, "#{self.class} did not override 'generate'" - end - - def initialize(name) - @name = name.to_s.downcase + # For now, use the YAML separator. + SEPARATOR = "\n---\n" + + def self.from_multiple_s(text) + text.split(SEPARATOR).collect { |inst| from_s(inst) } + end + + def self.to_multiple_s(instances) + instances.collect { |inst| inst.to_s }.join(SEPARATOR) + end + + def self.wraps(klass) + @wrapped_class = klass + end + + def self.wrapped_class + raise(Puppet::DevError, "#{self} has not declared what class it wraps") unless defined?(@wrapped_class) + @wrapped_class + end + + attr_accessor :name, :content + + # Is this file for the CA? + def ca? + name == Puppet::SSL::Host.ca_name + end + + def generate + raise Puppet::DevError, "#{self.class} did not override 'generate'" + end + + def initialize(name) + @name = name.to_s.downcase + end + + # Read content from disk appropriately. + def read(path) + @content = wrapped_class.new(File.read(path)) + end + + # Convert our thing to pem. + def to_s + return "" unless content + content.to_pem + end + + # Provide the full text of the thing we're dealing with. + def to_text + return "" unless content + content.to_text + end + + def fingerprint(md = :MD5) + require 'openssl/digest' + + # ruby 1.8.x openssl digest constants are string + # but in 1.9.x they are symbols + mds = md.to_s.upcase + if OpenSSL::Digest.constants.include?(mds) + md = mds + elsif OpenSSL::Digest.constants.include?(mds.to_sym) + md = mds.to_sym + else + raise ArgumentError, "#{md} is not a valid digest algorithm for fingerprinting certificate #{name}" end - # Read content from disk appropriately. - def read(path) - @content = wrapped_class.new(File.read(path)) - end + OpenSSL::Digest.const_get(md).hexdigest(content.to_der).scan(/../).join(':').upcase + end - # Convert our thing to pem. - def to_s - return "" unless content - content.to_pem - end + private - # Provide the full text of the thing we're dealing with. - def to_text - return "" unless content - content.to_text - end - - def fingerprint(md = :MD5) - require 'openssl/digest' - - # ruby 1.8.x openssl digest constants are string - # but in 1.9.x they are symbols - mds = md.to_s.upcase - if OpenSSL::Digest.constants.include?(mds) - md = mds - elsif OpenSSL::Digest.constants.include?(mds.to_sym) - md = mds.to_sym - else - raise ArgumentError, "#{md} is not a valid digest algorithm for fingerprinting certificate #{name}" - end - - OpenSSL::Digest.const_get(md).hexdigest(content.to_der).scan(/../).join(':').upcase - end - - private - - def wrapped_class - self.class.wrapped_class - end + def wrapped_class + self.class.wrapped_class + end end diff --git a/lib/puppet/ssl/certificate.rb b/lib/puppet/ssl/certificate.rb index 07dd0c8e6..a0e600291 100644 --- a/lib/puppet/ssl/certificate.rb +++ b/lib/puppet/ssl/certificate.rb @@ -6,29 +6,29 @@ require 'puppet/ssl/base' # retrieve them from the CA (or not, as is often # the case). class Puppet::SSL::Certificate < Puppet::SSL::Base - # This is defined from the base class - wraps OpenSSL::X509::Certificate + # This is defined from the base class + wraps OpenSSL::X509::Certificate - extend Puppet::Indirector - indirects :certificate, :terminus_class => :file + extend Puppet::Indirector + indirects :certificate, :terminus_class => :file - # Convert a string into an instance. - def self.from_s(string) - instance = wrapped_class.new(string) - name = instance.subject.to_s.sub(/\/CN=/i, '').downcase - result = new(name) - result.content = instance - result - end + # Convert a string into an instance. + def self.from_s(string) + instance = wrapped_class.new(string) + name = instance.subject.to_s.sub(/\/CN=/i, '').downcase + result = new(name) + result.content = instance + result + end - # Because of how the format handler class is included, this - # can't be in the base class. - def self.supported_formats - [:s] - end + # Because of how the format handler class is included, this + # can't be in the base class. + def self.supported_formats + [:s] + end - def expiration - return nil unless content - content.not_after - end + def expiration + return nil unless content + content.not_after + end end diff --git a/lib/puppet/ssl/certificate_authority.rb b/lib/puppet/ssl/certificate_authority.rb index 26febb3d3..efe562d4c 100644 --- a/lib/puppet/ssl/certificate_authority.rb +++ b/lib/puppet/ssl/certificate_authority.rb @@ -11,274 +11,274 @@ require 'puppet/util/cacher' # it can also be seen as a general interface into all of the # SSL stuff. class Puppet::SSL::CertificateAuthority - require 'puppet/ssl/certificate_factory' - require 'puppet/ssl/inventory' - require 'puppet/ssl/certificate_revocation_list' + require 'puppet/ssl/certificate_factory' + require 'puppet/ssl/inventory' + require 'puppet/ssl/certificate_revocation_list' - require 'puppet/ssl/certificate_authority/interface' + require 'puppet/ssl/certificate_authority/interface' - class CertificateVerificationError < RuntimeError - attr_accessor :error_code + class CertificateVerificationError < RuntimeError + attr_accessor :error_code - def initialize(code) - @error_code = code - end + def initialize(code) + @error_code = code end + end - class << self - include Puppet::Util::Cacher + class << self + include Puppet::Util::Cacher - cached_attr(:singleton_instance) { new } - end + cached_attr(:singleton_instance) { new } + end - def self.ca? - return false unless Puppet[:ca] - return false unless Puppet.run_mode.master? - true - end + def self.ca? + return false unless Puppet[:ca] + return false unless Puppet.run_mode.master? + true + end - # If this process can function as a CA, then return a singleton - # instance. - def self.instance - return nil unless ca? + # If this process can function as a CA, then return a singleton + # instance. + def self.instance + return nil unless ca? - singleton_instance - end + singleton_instance + end - attr_reader :name, :host + attr_reader :name, :host - # Create and run an applicator. I wanted to build an interface where you could do - # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible. - def apply(method, options) - raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" unless options[:to] - applier = Interface.new(method, options) + # Create and run an applicator. I wanted to build an interface where you could do + # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible. + def apply(method, options) + raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" unless options[:to] + applier = Interface.new(method, options) - applier.apply(self) - end + applier.apply(self) + end - # If autosign is configured, then autosign all CSRs that match our configuration. - def autosign - return unless auto = autosign? + # If autosign is configured, then autosign all CSRs that match our configuration. + def autosign + return unless auto = autosign? - store = nil - store = autosign_store(auto) if auto != true + store = nil + store = autosign_store(auto) if auto != true - Puppet::SSL::CertificateRequest.search("*").each do |csr| - sign(csr.name) if auto == true or store.allowed?(csr.name, "127.1.1.1") - end + Puppet::SSL::CertificateRequest.search("*").each do |csr| + sign(csr.name) if auto == true or store.allowed?(csr.name, "127.1.1.1") + end + end + + # Do we autosign? This returns true, false, or a filename. + def autosign? + auto = Puppet[:autosign] + return false if ['false', false].include?(auto) + return true if ['true', true].include?(auto) + + raise ArgumentError, "The autosign configuration '#{auto}' must be a fully qualified file" unless auto =~ /^\// + FileTest.exist?(auto) && auto + end + + # Create an AuthStore for autosigning. + def autosign_store(file) + auth = Puppet::Network::AuthStore.new + File.readlines(file).each do |line| + next if line =~ /^\s*#/ + next if line =~ /^\s*$/ + auth.allow(line.chomp) end - # Do we autosign? This returns true, false, or a filename. - def autosign? - auto = Puppet[:autosign] - return false if ['false', false].include?(auto) - return true if ['true', true].include?(auto) - - raise ArgumentError, "The autosign configuration '#{auto}' must be a fully qualified file" unless auto =~ /^\// - FileTest.exist?(auto) && auto + auth + end + + # Retrieve (or create, if necessary) the certificate revocation list. + def crl + unless defined?(@crl) + unless @crl = Puppet::SSL::CertificateRevocationList.find(Puppet::SSL::CA_NAME) + @crl = Puppet::SSL::CertificateRevocationList.new(Puppet::SSL::CA_NAME) + @crl.generate(host.certificate.content, host.key.content) + @crl.save + end end + @crl + end - # Create an AuthStore for autosigning. - def autosign_store(file) - auth = Puppet::Network::AuthStore.new - File.readlines(file).each do |line| - next if line =~ /^\s*#/ - next if line =~ /^\s*$/ - auth.allow(line.chomp) - end + # Delegate this to our Host class. + def destroy(name) + Puppet::SSL::Host.destroy(name) + end - auth - end + # Generate a new certificate. + def generate(name) + raise ArgumentError, "A Certificate already exists for #{name}" if Puppet::SSL::Certificate.find(name) + host = Puppet::SSL::Host.new(name) - # Retrieve (or create, if necessary) the certificate revocation list. - def crl - unless defined?(@crl) - unless @crl = Puppet::SSL::CertificateRevocationList.find(Puppet::SSL::CA_NAME) - @crl = Puppet::SSL::CertificateRevocationList.new(Puppet::SSL::CA_NAME) - @crl.generate(host.certificate.content, host.key.content) - @crl.save - end - end - @crl - end + host.generate_certificate_request - # Delegate this to our Host class. - def destroy(name) - Puppet::SSL::Host.destroy(name) - end + sign(name) + end - # Generate a new certificate. - def generate(name) - raise ArgumentError, "A Certificate already exists for #{name}" if Puppet::SSL::Certificate.find(name) - host = Puppet::SSL::Host.new(name) + # Generate our CA certificate. + def generate_ca_certificate + generate_password unless password? - host.generate_certificate_request + host.generate_key unless host.key - sign(name) - end + # Create a new cert request. We do this + # specially, because we don't want to actually + # save the request anywhere. + request = Puppet::SSL::CertificateRequest.new(host.name) + request.generate(host.key) - # Generate our CA certificate. - def generate_ca_certificate - generate_password unless password? + # Create a self-signed certificate. + @certificate = sign(host.name, :ca, request) - host.generate_key unless host.key + # And make sure we initialize our CRL. + crl + end - # Create a new cert request. We do this - # specially, because we don't want to actually - # save the request anywhere. - request = Puppet::SSL::CertificateRequest.new(host.name) - request.generate(host.key) + def initialize + Puppet.settings.use :main, :ssl, :ca - # Create a self-signed certificate. - @certificate = sign(host.name, :ca, request) + @name = Puppet[:certname] - # And make sure we initialize our CRL. - crl - end + @host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name) - def initialize - Puppet.settings.use :main, :ssl, :ca + setup + end - @name = Puppet[:certname] + # Retrieve (or create, if necessary) our inventory manager. + def inventory + @inventory ||= Puppet::SSL::Inventory.new + end - @host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name) + # Generate a new password for the CA. + def generate_password + pass = "" + 20.times { pass += (rand(74) + 48).chr } - setup + begin + Puppet.settings.write(:capass) { |f| f.print pass } + rescue Errno::EACCES => detail + raise Puppet::Error, "Could not write CA password: #{detail}" end - # Retrieve (or create, if necessary) our inventory manager. - def inventory - @inventory ||= Puppet::SSL::Inventory.new - end + @password = pass - # Generate a new password for the CA. - def generate_password - pass = "" - 20.times { pass += (rand(74) + 48).chr } + pass + end - begin - Puppet.settings.write(:capass) { |f| f.print pass } - rescue Errno::EACCES => detail - raise Puppet::Error, "Could not write CA password: #{detail}" - end + # List all signed certificates. + def list + Puppet::SSL::Certificate.search("*").collect { |c| c.name } + end - @password = pass + # Read the next serial from the serial file, and increment the + # file so this one is considered used. + def next_serial + serial = nil - pass - end + # This is slightly odd. If the file doesn't exist, our readwritelock creates + # it, but with a mode we can't actually read in some cases. So, use + # a default before the lock. + serial = 0x1 unless FileTest.exist?(Puppet[:serial]) - # List all signed certificates. - def list - Puppet::SSL::Certificate.search("*").collect { |c| c.name } - end + Puppet.settings.readwritelock(:serial) { |f| + serial ||= File.read(Puppet.settings[:serial]).chomp.hex if FileTest.exist?(Puppet[:serial]) - # Read the next serial from the serial file, and increment the - # file so this one is considered used. - def next_serial - serial = nil + # We store the next valid serial, not the one we just used. + f << "%04X" % (serial + 1) + } - # This is slightly odd. If the file doesn't exist, our readwritelock creates - # it, but with a mode we can't actually read in some cases. So, use - # a default before the lock. - serial = 0x1 unless FileTest.exist?(Puppet[:serial]) + serial + end - Puppet.settings.readwritelock(:serial) { |f| - serial ||= File.read(Puppet.settings[:serial]).chomp.hex if FileTest.exist?(Puppet[:serial]) + # Does the password file exist? + def password? + FileTest.exist? Puppet[:capass] + end - # We store the next valid serial, not the one we just used. - f << "%04X" % (serial + 1) - } + # Print a given host's certificate as text. + def print(name) + (cert = Puppet::SSL::Certificate.find(name)) ? cert.to_text : nil + end - serial - end + # Revoke a given certificate. + def revoke(name) + raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl - # Does the password file exist? - def password? - FileTest.exist? Puppet[:capass] + if cert = Puppet::SSL::Certificate.find(name) + serial = cert.content.serial + elsif ! serial = inventory.serial(name) + raise ArgumentError, "Could not find a serial number for #{name}" end - - # Print a given host's certificate as text. - def print(name) - (cert = Puppet::SSL::Certificate.find(name)) ? cert.to_text : nil + crl.revoke(serial, host.key.content) + end + + # This initializes our CA so it actually works. This should be a private + # method, except that you can't any-instance stub private methods, which is + # *awesome*. This method only really exists to provide a stub-point during + # testing. + def setup + generate_ca_certificate unless @host.certificate + end + + # Sign a given certificate request. + def sign(hostname, cert_type = :server, self_signing_csr = nil) + # This is a self-signed certificate + if self_signing_csr + csr = self_signing_csr + issuer = csr.content + else + unless csr = Puppet::SSL::CertificateRequest.find(hostname) + raise ArgumentError, "Could not find certificate request for #{hostname}" + end + issuer = host.certificate.content end - # Revoke a given certificate. - def revoke(name) - raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl + cert = Puppet::SSL::Certificate.new(hostname) + cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result + cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new) - if cert = Puppet::SSL::Certificate.find(name) - serial = cert.content.serial - elsif ! serial = inventory.serial(name) - raise ArgumentError, "Could not find a serial number for #{name}" - end - crl.revoke(serial, host.key.content) - end + Puppet.notice "Signed certificate request for #{hostname}" - # This initializes our CA so it actually works. This should be a private - # method, except that you can't any-instance stub private methods, which is - # *awesome*. This method only really exists to provide a stub-point during - # testing. - def setup - generate_ca_certificate unless @host.certificate - end + # Add the cert to the inventory before we save it, since + # otherwise we could end up with it being duplicated, if + # this is the first time we build the inventory file. + inventory.add(cert) - # Sign a given certificate request. - def sign(hostname, cert_type = :server, self_signing_csr = nil) - # This is a self-signed certificate - if self_signing_csr - csr = self_signing_csr - issuer = csr.content - else - unless csr = Puppet::SSL::CertificateRequest.find(hostname) - raise ArgumentError, "Could not find certificate request for #{hostname}" - end - issuer = host.certificate.content - end - - cert = Puppet::SSL::Certificate.new(hostname) - cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result - cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new) - - Puppet.notice "Signed certificate request for #{hostname}" - - # Add the cert to the inventory before we save it, since - # otherwise we could end up with it being duplicated, if - # this is the first time we build the inventory file. - inventory.add(cert) - - # Save the now-signed cert. This should get routed correctly depending - # on the certificate type. - cert.save - - # And remove the CSR if this wasn't self signed. - Puppet::SSL::CertificateRequest.destroy(csr.name) unless self_signing_csr - - cert - end + # Save the now-signed cert. This should get routed correctly depending + # on the certificate type. + cert.save - # Verify a given host's certificate. - def verify(name) - unless cert = Puppet::SSL::Certificate.find(name) - raise ArgumentError, "Could not find a certificate for #{name}" - end - store = OpenSSL::X509::Store.new - store.add_file Puppet[:cacert] - store.add_crl crl.content if self.crl - store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT - store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] - - raise CertificateVerificationError.new(store.error), store.error_string unless store.verify(cert.content) - end + # And remove the CSR if this wasn't self signed. + Puppet::SSL::CertificateRequest.destroy(csr.name) unless self_signing_csr - def fingerprint(name, md = :MD5) - unless cert = Puppet::SSL::Certificate.find(name) || Puppet::SSL::CertificateRequest.find(name) - raise ArgumentError, "Could not find a certificate or csr for #{name}" - end - cert.fingerprint(md) - end + cert + end - # List the waiting certificate requests. - def waiting? - Puppet::SSL::CertificateRequest.search("*").collect { |r| r.name } + # Verify a given host's certificate. + def verify(name) + unless cert = Puppet::SSL::Certificate.find(name) + raise ArgumentError, "Could not find a certificate for #{name}" end + store = OpenSSL::X509::Store.new + store.add_file Puppet[:cacert] + store.add_crl crl.content if self.crl + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] + + raise CertificateVerificationError.new(store.error), store.error_string unless store.verify(cert.content) + end + + def fingerprint(name, md = :MD5) + unless cert = Puppet::SSL::Certificate.find(name) || Puppet::SSL::CertificateRequest.find(name) + raise ArgumentError, "Could not find a certificate or csr for #{name}" + end + cert.fingerprint(md) + end + + # List the waiting certificate requests. + def waiting? + Puppet::SSL::CertificateRequest.search("*").collect { |r| r.name } + end end diff --git a/lib/puppet/ssl/certificate_authority/interface.rb b/lib/puppet/ssl/certificate_authority/interface.rb index e5ede3c6c..f73eff5f3 100644 --- a/lib/puppet/ssl/certificate_authority/interface.rb +++ b/lib/puppet/ssl/certificate_authority/interface.rb @@ -2,133 +2,133 @@ # on the CA. It's only used by the 'puppetca' executable, and its # job is to provide a CLI-like interface to the CA class. module Puppet - module SSL - class CertificateAuthority - class Interface - INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint] - - class InterfaceError < ArgumentError; end - - attr_reader :method, :subjects, :digest - - # Actually perform the work. - def apply(ca) - unless subjects or method == :list - raise ArgumentError, "You must provide hosts or :all when using #{method}" - end - - begin - return send(method, ca) if respond_to?(method) - - (subjects == :all ? ca.list : subjects).each do |host| - ca.send(method, host) - end - rescue InterfaceError - raise - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not call #{method}: #{detail}" - end - end - - def generate(ca) - raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all - - subjects.each do |host| - ca.generate(host) - end - end - - def initialize(method, options) - self.method = method - self.subjects = options[:to] - @digest = options[:digest] || :MD5 - end - - # List the hosts. - def list(ca) - unless subjects - puts ca.waiting?.join("\n") - return nil - end - - signed = ca.list - requests = ca.waiting? - - if subjects == :all - hosts = [signed, requests].flatten - elsif subjects == :signed - hosts = signed.flatten - else - hosts = subjects - end - - hosts.uniq.sort.each do |host| - invalid = false - begin - ca.verify(host) unless requests.include?(host) - rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details - invalid = details.to_s - end - if not invalid and signed.include?(host) - puts "+ #{host} (#{ca.fingerprint(host, @digest)})" - elsif invalid - puts "- #{host} (#{ca.fingerprint(host, @digest)}) (#{invalid})" - else - puts "#{host} (#{ca.fingerprint(host, @digest)})" - end - end - end - - # Set the method to apply. - def method=(method) - raise ArgumentError, "Invalid method #{method} to apply" unless INTERFACE_METHODS.include?(method) - @method = method - end - - # Print certificate information. - def print(ca) - (subjects == :all ? ca.list : subjects).each do |host| - if value = ca.print(host) - puts value - else - Puppet.err "Could not find certificate for #{host}" - end - end - end - - # Print certificate information. - def fingerprint(ca) - (subjects == :all ? ca.list + ca.waiting?: subjects).each do |host| - if value = ca.fingerprint(host, @digest) - puts "#{host} #{value}" - else - Puppet.err "Could not find certificate for #{host}" - end - end - end - - # Sign a given certificate. - def sign(ca) - list = subjects == :all ? ca.waiting? : subjects - raise InterfaceError, "No waiting certificate requests to sign" if list.empty? - list.each do |host| - ca.sign(host) - end - end - - # Set the list of hosts we're operating on. Also supports keywords. - def subjects=(value) - unless value == :all or value == :signed or value.is_a?(Array) - raise ArgumentError, "Subjects must be an array or :all; not #{value}" - end - - value = nil if value.is_a?(Array) and value.empty? - - @subjects = value - end + module SSL + class CertificateAuthority + class Interface + INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint] + + class InterfaceError < ArgumentError; end + + attr_reader :method, :subjects, :digest + + # Actually perform the work. + def apply(ca) + unless subjects or method == :list + raise ArgumentError, "You must provide hosts or :all when using #{method}" + end + + begin + return send(method, ca) if respond_to?(method) + + (subjects == :all ? ca.list : subjects).each do |host| + ca.send(method, host) + end + rescue InterfaceError + raise + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not call #{method}: #{detail}" + end + end + + def generate(ca) + raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all + + subjects.each do |host| + ca.generate(host) + end + end + + def initialize(method, options) + self.method = method + self.subjects = options[:to] + @digest = options[:digest] || :MD5 + end + + # List the hosts. + def list(ca) + unless subjects + puts ca.waiting?.join("\n") + return nil + end + + signed = ca.list + requests = ca.waiting? + + if subjects == :all + hosts = [signed, requests].flatten + elsif subjects == :signed + hosts = signed.flatten + else + hosts = subjects + end + + hosts.uniq.sort.each do |host| + invalid = false + begin + ca.verify(host) unless requests.include?(host) + rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details + invalid = details.to_s + end + if not invalid and signed.include?(host) + puts "+ #{host} (#{ca.fingerprint(host, @digest)})" + elsif invalid + puts "- #{host} (#{ca.fingerprint(host, @digest)}) (#{invalid})" + else + puts "#{host} (#{ca.fingerprint(host, @digest)})" end + end + end + + # Set the method to apply. + def method=(method) + raise ArgumentError, "Invalid method #{method} to apply" unless INTERFACE_METHODS.include?(method) + @method = method + end + + # Print certificate information. + def print(ca) + (subjects == :all ? ca.list : subjects).each do |host| + if value = ca.print(host) + puts value + else + Puppet.err "Could not find certificate for #{host}" + end + end + end + + # Print certificate information. + def fingerprint(ca) + (subjects == :all ? ca.list + ca.waiting?: subjects).each do |host| + if value = ca.fingerprint(host, @digest) + puts "#{host} #{value}" + else + Puppet.err "Could not find certificate for #{host}" + end + end + end + + # Sign a given certificate. + def sign(ca) + list = subjects == :all ? ca.waiting? : subjects + raise InterfaceError, "No waiting certificate requests to sign" if list.empty? + list.each do |host| + ca.sign(host) + end + end + + # Set the list of hosts we're operating on. Also supports keywords. + def subjects=(value) + unless value == :all or value == :signed or value.is_a?(Array) + raise ArgumentError, "Subjects must be an array or :all; not #{value}" + end + + value = nil if value.is_a?(Array) and value.empty? + + @subjects = value end + end end + end end diff --git a/lib/puppet/ssl/certificate_factory.rb b/lib/puppet/ssl/certificate_factory.rb index 9a5507dea..73290e9cf 100644 --- a/lib/puppet/ssl/certificate_factory.rb +++ b/lib/puppet/ssl/certificate_factory.rb @@ -3,143 +3,143 @@ require 'puppet/ssl' # The tedious class that does all the manipulations to the # certificate to correctly sign it. Yay. class Puppet::SSL::CertificateFactory - # How we convert from various units to the required seconds. - UNITMAP = { - "y" => 365 * 24 * 60 * 60, - "d" => 24 * 60 * 60, - "h" => 60 * 60, - "s" => 1 - } + # How we convert from various units to the required seconds. + UNITMAP = { + "y" => 365 * 24 * 60 * 60, + "d" => 24 * 60 * 60, + "h" => 60 * 60, + "s" => 1 + } - attr_reader :name, :cert_type, :csr, :issuer, :serial + attr_reader :name, :cert_type, :csr, :issuer, :serial - def initialize(cert_type, csr, issuer, serial) - @cert_type, @csr, @issuer, @serial = cert_type, csr, issuer, serial + def initialize(cert_type, csr, issuer, serial) + @cert_type, @csr, @issuer, @serial = cert_type, csr, issuer, serial - @name = @csr.subject - end - - # Actually generate our certificate. - def result - @cert = OpenSSL::X509::Certificate.new - - @cert.version = 2 # X509v3 - @cert.subject = @csr.subject - @cert.issuer = @issuer.subject - @cert.public_key = @csr.public_key - @cert.serial = @serial - - build_extensions - - set_ttl - - @cert - end - - private - - # This is pretty ugly, but I'm not really sure it's even possible to do - # it any other way. - def build_extensions - @ef = OpenSSL::X509::ExtensionFactory.new - - @ef.subject_certificate = @cert + @name = @csr.subject + end - if @issuer.is_a?(OpenSSL::X509::Request) # It's a self-signed cert - @ef.issuer_certificate = @cert - else - @ef.issuer_certificate = @issuer - end + # Actually generate our certificate. + def result + @cert = OpenSSL::X509::Certificate.new - @subject_alt_name = [] - @key_usage = nil - @ext_key_usage = nil - @extensions = [] + @cert.version = 2 # X509v3 + @cert.subject = @csr.subject + @cert.issuer = @issuer.subject + @cert.public_key = @csr.public_key + @cert.serial = @serial - method = "add_#{@cert_type.to_s}_extensions" + build_extensions - begin - send(method) - rescue NoMethodError - raise ArgumentError, "#{@cert_type} is an invalid certificate type" - end + set_ttl - @extensions << @ef.create_extension("nsComment", "Puppet Ruby/OpenSSL Generated Certificate") - @extensions << @ef.create_extension("basicConstraints", @basic_constraint, true) - @extensions << @ef.create_extension("subjectKeyIdentifier", "hash") - @extensions << @ef.create_extension("keyUsage", @key_usage.join(",")) if @key_usage - @extensions << @ef.create_extension("extendedKeyUsage", @ext_key_usage.join(",")) if @ext_key_usage - @extensions << @ef.create_extension("subjectAltName", @subject_alt_name.join(",")) if ! @subject_alt_name.empty? + @cert + end - @cert.extensions = @extensions + private - # for some reason this _must_ be the last extension added - @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, - # use that, otherwise use :ca_days for backwards compatibility - def ttl - ttl = Puppet.settings[:ca_ttl] - - return ttl unless ttl.is_a?(String) - - raise ArgumentError, "Invalid ca_ttl #{ttl}" unless ttl =~ /^(\d+)(y|d|h|s)$/ - - $1.to_i * UNITMAP[$2] - end + # This is pretty ugly, but I'm not really sure it's even possible to do + # it any other way. + def build_extensions + @ef = OpenSSL::X509::ExtensionFactory.new - def set_ttl - # Make the certificate valid as of yesterday, because - # so many people's clocks are out of sync. - from = Time.now - (60*60*24) - @cert.not_before = from - @cert.not_after = from + ttl - end + @ef.subject_certificate = @cert - # Woot! We're a CA. - def add_ca_extensions - @basic_constraint = "CA:TRUE" - @key_usage = %w{cRLSign keyCertSign} + if @issuer.is_a?(OpenSSL::X509::Request) # It's a self-signed cert + @ef.issuer_certificate = @cert + else + @ef.issuer_certificate = @issuer end - # We're a terminal CA, probably not self-signed. - def add_terminalsubca_extensions - @basic_constraint = "CA:TRUE,pathlen:0" - @key_usage = %w{cRLSign keyCertSign} - end + @subject_alt_name = [] + @key_usage = nil + @ext_key_usage = nil + @extensions = [] - # We're a normal server. - def add_server_extensions - @basic_constraint = "CA:FALSE" - dnsnames = Puppet[:certdnsnames] - name = @name.to_s.sub(%r{/CN=},'') - if dnsnames != "" - dnsnames.split(':').each { |d| @subject_alt_name << 'DNS:' + d } - @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias - elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server - @subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias - @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias - @subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias - end - @key_usage = %w{digitalSignature keyEncipherment} - @ext_key_usage = %w{serverAuth clientAuth emailProtection} - end + method = "add_#{@cert_type.to_s}_extensions" - # Um, no idea. - def add_ocsp_extensions - @basic_constraint = "CA:FALSE" - @key_usage = %w{nonRepudiation digitalSignature} - @ext_key_usage = %w{serverAuth OCSPSigning} + begin + send(method) + rescue NoMethodError + raise ArgumentError, "#{@cert_type} is an invalid certificate type" end - # Normal client. - def add_client_extensions - @basic_constraint = "CA:FALSE" - @key_usage = %w{nonRepudiation digitalSignature keyEncipherment} - @ext_key_usage = %w{clientAuth emailProtection} - - @extensions << @ef.create_extension("nsCertType", "client,email") + @extensions << @ef.create_extension("nsComment", "Puppet Ruby/OpenSSL Generated Certificate") + @extensions << @ef.create_extension("basicConstraints", @basic_constraint, true) + @extensions << @ef.create_extension("subjectKeyIdentifier", "hash") + @extensions << @ef.create_extension("keyUsage", @key_usage.join(",")) if @key_usage + @extensions << @ef.create_extension("extendedKeyUsage", @ext_key_usage.join(",")) if @ext_key_usage + @extensions << @ef.create_extension("subjectAltName", @subject_alt_name.join(",")) if ! @subject_alt_name.empty? + + @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 + end + + # TTL for new certificates in seconds. If config param :ca_ttl is set, + # use that, otherwise use :ca_days for backwards compatibility + def ttl + ttl = Puppet.settings[:ca_ttl] + + return ttl unless ttl.is_a?(String) + + raise ArgumentError, "Invalid ca_ttl #{ttl}" unless ttl =~ /^(\d+)(y|d|h|s)$/ + + $1.to_i * UNITMAP[$2] + end + + def set_ttl + # Make the certificate valid as of yesterday, because + # so many people's clocks are out of sync. + from = Time.now - (60*60*24) + @cert.not_before = from + @cert.not_after = from + ttl + end + + # Woot! We're a CA. + def add_ca_extensions + @basic_constraint = "CA:TRUE" + @key_usage = %w{cRLSign keyCertSign} + end + + # We're a terminal CA, probably not self-signed. + def add_terminalsubca_extensions + @basic_constraint = "CA:TRUE,pathlen:0" + @key_usage = %w{cRLSign keyCertSign} + end + + # We're a normal server. + def add_server_extensions + @basic_constraint = "CA:FALSE" + dnsnames = Puppet[:certdnsnames] + name = @name.to_s.sub(%r{/CN=},'') + if dnsnames != "" + dnsnames.split(':').each { |d| @subject_alt_name << 'DNS:' + d } + @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias + elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server + @subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias + @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias + @subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias end + @key_usage = %w{digitalSignature keyEncipherment} + @ext_key_usage = %w{serverAuth clientAuth emailProtection} + end + + # Um, no idea. + def add_ocsp_extensions + @basic_constraint = "CA:FALSE" + @key_usage = %w{nonRepudiation digitalSignature} + @ext_key_usage = %w{serverAuth OCSPSigning} + end + + # Normal client. + def add_client_extensions + @basic_constraint = "CA:FALSE" + @key_usage = %w{nonRepudiation digitalSignature keyEncipherment} + @ext_key_usage = %w{clientAuth emailProtection} + + @extensions << @ef.create_extension("nsCertType", "client,email") + end end diff --git a/lib/puppet/ssl/certificate_request.rb b/lib/puppet/ssl/certificate_request.rb index 3cd3ce0be..e4d06a039 100644 --- a/lib/puppet/ssl/certificate_request.rb +++ b/lib/puppet/ssl/certificate_request.rb @@ -2,57 +2,57 @@ require 'puppet/ssl/base' # Manage certificate requests. class Puppet::SSL::CertificateRequest < Puppet::SSL::Base - wraps OpenSSL::X509::Request - - extend Puppet::Indirector - indirects :certificate_request, :terminus_class => :file - - # Convert a string into an instance. - def self.from_s(string) - instance = wrapped_class.new(string) - name = instance.subject.to_s.sub(/\/CN=/i, '').downcase - result = new(name) - result.content = instance - result - end - - # Because of how the format handler class is included, this - # can't be in the base class. - def self.supported_formats - [:s] - end - - # How to create a certificate request with our system defaults. - def generate(key) - Puppet.info "Creating a new SSL certificate request for #{name}" - - # Support either an actual SSL key, or a Puppet key. - key = key.content if key.is_a?(Puppet::SSL::Key) - - # If we're a CSR for the CA, then use the real certname, rather than the - # fake 'ca' name. This is mostly for backward compatibility with 0.24.x, - # but it's also just a good idea. - common_name = name == Puppet::SSL::CA_NAME ? Puppet.settings[:ca_name] : name - - csr = OpenSSL::X509::Request.new - csr.version = 0 - csr.subject = OpenSSL::X509::Name.new([["CN", common_name]]) - csr.public_key = key.public_key - csr.sign(key, OpenSSL::Digest::MD5.new) - - raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for #{name} on the server" unless csr.verify(key.public_key) - - @content = csr - Puppet.info "Certificate Request fingerprint (md5): #{fingerprint}" - @content - end - - def save(args = {}) - super() - - # Try to autosign the CSR. - if ca = Puppet::SSL::CertificateAuthority.instance - ca.autosign - end + wraps OpenSSL::X509::Request + + extend Puppet::Indirector + indirects :certificate_request, :terminus_class => :file + + # Convert a string into an instance. + def self.from_s(string) + instance = wrapped_class.new(string) + name = instance.subject.to_s.sub(/\/CN=/i, '').downcase + result = new(name) + result.content = instance + result + end + + # Because of how the format handler class is included, this + # can't be in the base class. + def self.supported_formats + [:s] + end + + # How to create a certificate request with our system defaults. + def generate(key) + Puppet.info "Creating a new SSL certificate request for #{name}" + + # Support either an actual SSL key, or a Puppet key. + key = key.content if key.is_a?(Puppet::SSL::Key) + + # If we're a CSR for the CA, then use the real certname, rather than the + # fake 'ca' name. This is mostly for backward compatibility with 0.24.x, + # but it's also just a good idea. + common_name = name == Puppet::SSL::CA_NAME ? Puppet.settings[:ca_name] : name + + csr = OpenSSL::X509::Request.new + csr.version = 0 + csr.subject = OpenSSL::X509::Name.new([["CN", common_name]]) + csr.public_key = key.public_key + csr.sign(key, OpenSSL::Digest::MD5.new) + + raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for #{name} on the server" unless csr.verify(key.public_key) + + @content = csr + Puppet.info "Certificate Request fingerprint (md5): #{fingerprint}" + @content + end + + def save(args = {}) + super() + + # Try to autosign the CSR. + if ca = Puppet::SSL::CertificateAuthority.instance + ca.autosign end + end end diff --git a/lib/puppet/ssl/certificate_revocation_list.rb b/lib/puppet/ssl/certificate_revocation_list.rb index b2bff4830..44e0a9e22 100644 --- a/lib/puppet/ssl/certificate_revocation_list.rb +++ b/lib/puppet/ssl/certificate_revocation_list.rb @@ -3,82 +3,82 @@ require 'puppet/indirector' # Manage the CRL. class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base - wraps OpenSSL::X509::CRL - - extend Puppet::Indirector - indirects :certificate_revocation_list, :terminus_class => :file - - # Convert a string into an instance. - def self.from_s(string) - instance = wrapped_class.new(string) - result = new('foo') # The name doesn't matter - result.content = instance - result - end - - # Because of how the format handler class is included, this - # can't be in the base class. - def self.supported_formats - [:s] - end - - # Knows how to create a CRL with our system defaults. - def generate(cert, cakey) - Puppet.info "Creating a new certificate revocation list" - @content = wrapped_class.new - @content.issuer = cert.subject - @content.version = 1 - - # Init the CRL number. - crlNum = OpenSSL::ASN1::Integer(0) - @content.extensions = [OpenSSL::X509::Extension.new("crlNumber", crlNum)] - - # Set last/next update - @content.last_update = Time.now - # Keep CRL valid for 5 years - @content.next_update = Time.now + 5 * 365*24*60*60 - - @content.sign(cakey, OpenSSL::Digest::SHA1.new) - - @content - end - - # The name doesn't actually matter; there's only one CRL. - # We just need the name so our Indirector stuff all works more easily. - def initialize(fakename) - @name = "crl" - end - - # Revoke the certificate with serial number SERIAL issued by this - # 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) - Puppet.notice "Revoked certificate with serial #{serial}" - time = Time.now - - # Add our revocation to the CRL. - revoked = OpenSSL::X509::Revoked.new - revoked.serial = serial - revoked.time = time - enum = OpenSSL::ASN1::Enumerated(reason) - ext = OpenSSL::X509::Extension.new("CRLReason", enum) - revoked.add_extension(ext) - @content.add_revoked(revoked) - - # Increment the crlNumber - e = @content.extensions.find { |e| e.oid == 'crlNumber' } - ext = @content.extensions.reject { |e| e.oid == 'crlNumber' } - crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0) - ext << OpenSSL::X509::Extension.new("crlNumber", crlNum) - @content.extensions = ext - - # Set last/next update - @content.last_update = time - # Keep CRL valid for 5 years - @content.next_update = time + 5 * 365*24*60*60 - - @content.sign(cakey, OpenSSL::Digest::SHA1.new) - - save - end + wraps OpenSSL::X509::CRL + + extend Puppet::Indirector + indirects :certificate_revocation_list, :terminus_class => :file + + # Convert a string into an instance. + def self.from_s(string) + instance = wrapped_class.new(string) + result = new('foo') # The name doesn't matter + result.content = instance + result + end + + # Because of how the format handler class is included, this + # can't be in the base class. + def self.supported_formats + [:s] + end + + # Knows how to create a CRL with our system defaults. + def generate(cert, cakey) + Puppet.info "Creating a new certificate revocation list" + @content = wrapped_class.new + @content.issuer = cert.subject + @content.version = 1 + + # Init the CRL number. + crlNum = OpenSSL::ASN1::Integer(0) + @content.extensions = [OpenSSL::X509::Extension.new("crlNumber", crlNum)] + + # Set last/next update + @content.last_update = Time.now + # Keep CRL valid for 5 years + @content.next_update = Time.now + 5 * 365*24*60*60 + + @content.sign(cakey, OpenSSL::Digest::SHA1.new) + + @content + end + + # The name doesn't actually matter; there's only one CRL. + # We just need the name so our Indirector stuff all works more easily. + def initialize(fakename) + @name = "crl" + end + + # Revoke the certificate with serial number SERIAL issued by this + # 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) + Puppet.notice "Revoked certificate with serial #{serial}" + time = Time.now + + # Add our revocation to the CRL. + revoked = OpenSSL::X509::Revoked.new + revoked.serial = serial + revoked.time = time + enum = OpenSSL::ASN1::Enumerated(reason) + ext = OpenSSL::X509::Extension.new("CRLReason", enum) + revoked.add_extension(ext) + @content.add_revoked(revoked) + + # Increment the crlNumber + e = @content.extensions.find { |e| e.oid == 'crlNumber' } + ext = @content.extensions.reject { |e| e.oid == 'crlNumber' } + crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0) + ext << OpenSSL::X509::Extension.new("crlNumber", crlNum) + @content.extensions = ext + + # Set last/next update + @content.last_update = time + # Keep CRL valid for 5 years + @content.next_update = time + 5 * 365*24*60*60 + + @content.sign(cakey, OpenSSL::Digest::SHA1.new) + + save + end end diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb index 9aaff8ad2..8a6f0aa6d 100644 --- a/lib/puppet/ssl/host.rb +++ b/lib/puppet/ssl/host.rb @@ -8,255 +8,255 @@ require 'puppet/util/cacher' # The class that manages all aspects of our SSL certificates -- # private keys, public keys, requests, etc. class Puppet::SSL::Host - # Yay, ruby's strange constant lookups. - Key = Puppet::SSL::Key - CA_NAME = Puppet::SSL::CA_NAME - Certificate = Puppet::SSL::Certificate - CertificateRequest = Puppet::SSL::CertificateRequest - CertificateRevocationList = Puppet::SSL::CertificateRevocationList - - attr_reader :name - attr_accessor :ca - - attr_writer :key, :certificate, :certificate_request - - class << self - include Puppet::Util::Cacher - - cached_attr(:localhost) do - result = new - result.generate unless result.certificate - result.key # Make sure it's read in - result - end + # Yay, ruby's strange constant lookups. + Key = Puppet::SSL::Key + CA_NAME = Puppet::SSL::CA_NAME + Certificate = Puppet::SSL::Certificate + CertificateRequest = Puppet::SSL::CertificateRequest + CertificateRevocationList = Puppet::SSL::CertificateRevocationList + + attr_reader :name + attr_accessor :ca + + attr_writer :key, :certificate, :certificate_request + + class << self + include Puppet::Util::Cacher + + cached_attr(:localhost) do + result = new + result.generate unless result.certificate + result.key # Make sure it's read in + result end - - # 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 + + # 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 + + class << self + attr_reader :ca_location + end + + # Configure how our various classes interact with their various terminuses. + def self.configure_indirection(terminus, cache = nil) + Certificate.terminus_class = terminus + CertificateRequest.terminus_class = terminus + CertificateRevocationList.terminus_class = terminus + + if cache + # This is weird; we don't actually cache our keys, we + # use what would otherwise be the cache as our normal + # terminus. + Key.terminus_class = cache + else + Key.terminus_class = terminus end - class << self - attr_reader :ca_location + if cache + Certificate.cache_class = cache + CertificateRequest.cache_class = cache + CertificateRevocationList.cache_class = cache + else + # Make sure we have no cache configured. puppet master + # switches the configurations around a bit, so it's important + # that we specify the configs for absolutely everything, every + # time. + Certificate.cache_class = nil + CertificateRequest.cache_class = nil + CertificateRevocationList.cache_class = nil end - - # Configure how our various classes interact with their various terminuses. - def self.configure_indirection(terminus, cache = nil) - Certificate.terminus_class = terminus - CertificateRequest.terminus_class = terminus - CertificateRevocationList.terminus_class = terminus - - if cache - # This is weird; we don't actually cache our keys, we - # use what would otherwise be the cache as our normal - # terminus. - Key.terminus_class = cache - else - Key.terminus_class = terminus - end - - if cache - Certificate.cache_class = cache - CertificateRequest.cache_class = cache - CertificateRevocationList.cache_class = cache - else - # Make sure we have no cache configured. puppet master - # switches the configurations around a bit, so it's important - # that we specify the configs for absolutely everything, every - # time. - Certificate.cache_class = nil - CertificateRequest.cache_class = nil - CertificateRevocationList.cache_class = nil - end + end + + CA_MODES = { + # Our ca is local, so we use it as the ultimate source of information + # And we cache files locally. + :local => [:ca, :file], + # We're a remote CA client. + :remote => [:rest, :file], + # We are the CA, so we don't have read/write access to the normal certificates. + :only => [:ca], + # We have no CA, so we just look in the local file store. + :none => [:file] + } + + # Specify how we expect to interact with our certificate authority. + def self.ca_location=(mode) + raise ArgumentError, "CA Mode can only be #{CA_MODES.collect { |m| m.to_s }.join(", ")}" unless CA_MODES.include?(mode) + + @ca_location = mode + + configure_indirection(*CA_MODES[@ca_location]) + end + + # Remove all traces of a given host + def self.destroy(name) + [Key, Certificate, CertificateRequest].collect { |part| part.destroy(name) }.any? { |x| x } + end + + # Search for more than one host, optionally only specifying + # an interest in hosts with a given file type. + # This just allows our non-indirected class to have one of + # indirection methods. + def self.search(options = {}) + classlist = [options[:for] || [Key, CertificateRequest, Certificate]].flatten + + # Collect the results from each class, flatten them, collect all of the names, make the name list unique, + # then create a Host instance for each one. + classlist.collect { |klass| klass.search }.flatten.collect { |r| r.name }.uniq.collect do |name| + new(name) end - - CA_MODES = { - # Our ca is local, so we use it as the ultimate source of information - # And we cache files locally. - :local => [:ca, :file], - # We're a remote CA client. - :remote => [:rest, :file], - # We are the CA, so we don't have read/write access to the normal certificates. - :only => [:ca], - # We have no CA, so we just look in the local file store. - :none => [:file] - } - - # Specify how we expect to interact with our certificate authority. - def self.ca_location=(mode) - raise ArgumentError, "CA Mode can only be #{CA_MODES.collect { |m| m.to_s }.join(", ")}" unless CA_MODES.include?(mode) - - @ca_location = mode - - configure_indirection(*CA_MODES[@ca_location]) + end + + # Is this a ca host, meaning that all of its files go in the CA location? + def ca? + ca + end + + def key + @key ||= Key.find(name) + end + + # This is the private key; we can create it from scratch + # with no inputs. + def generate_key + @key = Key.new(name) + @key.generate + begin + @key.save + rescue + @key = nil + raise end - - # Remove all traces of a given host - def self.destroy(name) - [Key, Certificate, CertificateRequest].collect { |part| part.destroy(name) }.any? { |x| x } + true + end + + def certificate_request + @certificate_request ||= CertificateRequest.find(name) + end + + # Our certificate request requires the key but that's all. + def generate_certificate_request + generate_key unless key + @certificate_request = CertificateRequest.new(name) + @certificate_request.generate(key.content) + begin + @certificate_request.save + rescue + @certificate_request = nil + raise end - # Search for more than one host, optionally only specifying - # an interest in hosts with a given file type. - # This just allows our non-indirected class to have one of - # indirection methods. - def self.search(options = {}) - classlist = [options[:for] || [Key, CertificateRequest, Certificate]].flatten - - # Collect the results from each class, flatten them, collect all of the names, make the name list unique, - # then create a Host instance for each one. - classlist.collect { |klass| klass.search }.flatten.collect { |r| r.name }.uniq.collect do |name| - new(name) - end - end + true + end - # Is this a ca host, meaning that all of its files go in the CA location? - def ca? - ca - end + def certificate + unless @certificate + generate_key unless key - def key - @key ||= Key.find(name) - end + # get the CA cert first, since it's required for the normal cert + # to be of any use. + return nil unless Certificate.find("ca") unless ca? + return nil unless @certificate = Certificate.find(name) - # This is the private key; we can create it from scratch - # with no inputs. - def generate_key - @key = Key.new(name) - @key.generate - begin - @key.save - rescue - @key = nil - raise - end - true + unless certificate_matches_key? + raise Puppet::Error, "Retrieved certificate does not match private key; please remove certificate from server and regenerate it with the current key" + end end - - def certificate_request - @certificate_request ||= CertificateRequest.find(name) + @certificate + end + + def certificate_matches_key? + return false unless key + return false unless certificate + + certificate.content.check_private_key(key.content) + end + + # Generate all necessary parts of our ssl host. + def generate + generate_key unless key + generate_certificate_request unless certificate_request + + # If we can get a CA instance, then we're a valid CA, and we + # should use it to sign our request; else, just try to read + # the cert. + if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance + ca.sign(self.name) end - - # Our certificate request requires the key but that's all. - def generate_certificate_request - generate_key unless key - @certificate_request = CertificateRequest.new(name) - @certificate_request.generate(key.content) - begin - @certificate_request.save - rescue - @certificate_request = nil - raise - end - - true + end + + def initialize(name = nil) + @name = (name || Puppet[:certname]).downcase + @key = @certificate = @certificate_request = nil + @ca = (name == self.class.ca_name) + end + + # Extract the public key from the private key. + def public_key + key.content.public_key + end + + # Create/return a store that uses our SSL info to validate + # connections. + def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) + unless @ssl_store + @ssl_store = OpenSSL::X509::Store.new + @ssl_store.purpose = purpose + + # Use the file path here, because we don't want to cause + # a lookup in the middle of setting our ssl connection. + @ssl_store.add_file(Puppet[:localcacert]) + + # If there's a CRL, add it to our store. + if crl = Puppet::SSL::CertificateRevocationList.find(CA_NAME) + @ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] + @ssl_store.add_crl(crl.content) + end + return @ssl_store end - - def certificate - unless @certificate - generate_key unless key - - # get the CA cert first, since it's required for the normal cert - # to be of any use. - return nil unless Certificate.find("ca") unless ca? - return nil unless @certificate = Certificate.find(name) - - unless certificate_matches_key? - raise Puppet::Error, "Retrieved certificate does not match private key; please remove certificate from server and regenerate it with the current key" - end - end - @certificate - end - - def certificate_matches_key? - return false unless key - return false unless certificate - - certificate.content.check_private_key(key.content) - end - - # Generate all necessary parts of our ssl host. - def generate - generate_key unless key - generate_certificate_request unless certificate_request - - # If we can get a CA instance, then we're a valid CA, and we - # should use it to sign our request; else, just try to read - # the cert. - if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance - ca.sign(self.name) - end - end - - def initialize(name = nil) - @name = (name || Puppet[:certname]).downcase - @key = @certificate = @certificate_request = nil - @ca = (name == self.class.ca_name) - end - - # Extract the public key from the private key. - def public_key - key.content.public_key + @ssl_store + end + + # Attempt to retrieve a cert, if we don't already have one. + def wait_for_cert(time) + begin + return if certificate + generate + return if certificate + rescue SystemExit,NoMemoryError + raise + rescue Exception => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not request certificate: #{detail}" + if time < 1 + puts "Exiting; failed to retrieve certificate and waitforcert is disabled" + exit(1) + else + sleep(time) + end + retry end - # Create/return a store that uses our SSL info to validate - # connections. - def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) - unless @ssl_store - @ssl_store = OpenSSL::X509::Store.new - @ssl_store.purpose = purpose - - # Use the file path here, because we don't want to cause - # a lookup in the middle of setting our ssl connection. - @ssl_store.add_file(Puppet[:localcacert]) - - # If there's a CRL, add it to our store. - if crl = Puppet::SSL::CertificateRevocationList.find(CA_NAME) - @ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] - @ssl_store.add_crl(crl.content) - end - return @ssl_store - end - @ssl_store + if time < 1 + puts "Exiting; no certificate found and waitforcert is disabled" + exit(1) end - # Attempt to retrieve a cert, if we don't already have one. - def wait_for_cert(time) - begin - return if certificate - generate - return if certificate - rescue SystemExit,NoMemoryError - raise - rescue Exception => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not request certificate: #{detail}" - if time < 1 - puts "Exiting; failed to retrieve certificate and waitforcert is disabled" - exit(1) - else - sleep(time) - end - retry - end - - if time < 1 - puts "Exiting; no certificate found and waitforcert is disabled" - exit(1) - end - - while true - sleep time - begin - break if certificate - Puppet.notice "Did not receive certificate" - rescue StandardError => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not request certificate: #{detail}" - end - end + while true + sleep time + begin + break if certificate + Puppet.notice "Did not receive certificate" + rescue StandardError => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not request certificate: #{detail}" + end end + end end require 'puppet/ssl/certificate_authority' diff --git a/lib/puppet/ssl/inventory.rb b/lib/puppet/ssl/inventory.rb index 6fb2ea8c2..b2b402a53 100644 --- a/lib/puppet/ssl/inventory.rb +++ b/lib/puppet/ssl/inventory.rb @@ -3,50 +3,50 @@ require 'puppet/ssl/certificate' # Keep track of all of our known certificates. class Puppet::SSL::Inventory - attr_reader :path + attr_reader :path - # Add a certificate to our inventory. - def add(cert) - cert = cert.content if cert.is_a?(Puppet::SSL::Certificate) + # Add a certificate to our inventory. + def add(cert) + cert = cert.content if cert.is_a?(Puppet::SSL::Certificate) - # Create our file, if one does not already exist. - rebuild unless FileTest.exist?(@path) + # Create our file, if one does not already exist. + rebuild unless FileTest.exist?(@path) - Puppet.settings.write(:cert_inventory, "a") do |f| - f.print format(cert) - end + Puppet.settings.write(:cert_inventory, "a") do |f| + f.print format(cert) end + end - # Format our certificate for output. - def format(cert) - iso = '%Y-%m-%dT%H:%M:%S%Z' - "0x%04x %s %s %s\n" % [cert.serial, cert.not_before.strftime(iso), cert.not_after.strftime(iso), cert.subject] - end - - def initialize - @path = Puppet[:cert_inventory] - end + # Format our certificate for output. + def format(cert) + iso = '%Y-%m-%dT%H:%M:%S%Z' + "0x%04x %s %s %s\n" % [cert.serial, cert.not_before.strftime(iso), cert.not_after.strftime(iso), cert.subject] + end - # Rebuild the inventory from scratch. This should happen if - # the file is entirely missing or if it's somehow corrupted. - def rebuild - Puppet.notice "Rebuilding inventory file" + def initialize + @path = Puppet[:cert_inventory] + end - Puppet.settings.write(:cert_inventory) do |f| - f.print "# Inventory of signed certificates\n# SERIAL NOT_BEFORE NOT_AFTER SUBJECT\n" - end + # Rebuild the inventory from scratch. This should happen if + # the file is entirely missing or if it's somehow corrupted. + def rebuild + Puppet.notice "Rebuilding inventory file" - Puppet::SSL::Certificate.search("*").each { |cert| add(cert) } + Puppet.settings.write(:cert_inventory) do |f| + f.print "# Inventory of signed certificates\n# SERIAL NOT_BEFORE NOT_AFTER SUBJECT\n" end - # Find the serial number for a given certificate. - def serial(name) - return nil unless FileTest.exist?(@path) + Puppet::SSL::Certificate.search("*").each { |cert| add(cert) } + end + + # Find the serial number for a given certificate. + def serial(name) + return nil unless FileTest.exist?(@path) - File.readlines(@path).each do |line| - next unless line =~ /^(\S+).+\/CN=#{name}$/ + File.readlines(@path).each do |line| + next unless line =~ /^(\S+).+\/CN=#{name}$/ - return Integer($1) - end + return Integer($1) end + end end diff --git a/lib/puppet/ssl/key.rb b/lib/puppet/ssl/key.rb index cb03729c1..0ddc9623c 100644 --- a/lib/puppet/ssl/key.rb +++ b/lib/puppet/ssl/key.rb @@ -3,54 +3,54 @@ require 'puppet/indirector' # Manage private and public keys as a pair. class Puppet::SSL::Key < Puppet::SSL::Base - wraps OpenSSL::PKey::RSA + wraps OpenSSL::PKey::RSA - extend Puppet::Indirector - indirects :key, :terminus_class => :file + extend Puppet::Indirector + indirects :key, :terminus_class => :file - # Because of how the format handler class is included, this - # can't be in the base class. - def self.supported_formats - [:s] - end + # Because of how the format handler class is included, this + # can't be in the base class. + def self.supported_formats + [:s] + end - attr_accessor :password_file + attr_accessor :password_file - # Knows how to create keys with our system defaults. - def generate - Puppet.info "Creating a new SSL key for #{name}" - @content = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i) - end + # Knows how to create keys with our system defaults. + def generate + Puppet.info "Creating a new SSL key for #{name}" + @content = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i) + end - def initialize(name) - super + def initialize(name) + super - if ca? - @password_file = Puppet[:capass] - else - @password_file = Puppet[:passfile] - end + if ca? + @password_file = Puppet[:capass] + else + @password_file = Puppet[:passfile] end + end - def password - return nil unless password_file and FileTest.exist?(password_file) + def password + return nil unless password_file and FileTest.exist?(password_file) - ::File.read(password_file) - end + ::File.read(password_file) + end - # Optionally support specifying a password file. - def read(path) - return super unless password_file + # Optionally support specifying a password file. + def read(path) + return super unless password_file - #@content = wrapped_class.new(::File.read(path), password) - @content = wrapped_class.new(::File.read(path), password) - end + #@content = wrapped_class.new(::File.read(path), password) + @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 + def to_s + if pass = password + @content.export(OpenSSL::Cipher::DES.new(:EDE3, :CBC), pass) + else + return super end + end end |