summaryrefslogtreecommitdiffstats
path: root/lib/puppet/ssl/host.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/ssl/host.rb')
-rw-r--r--lib/puppet/ssl/host.rb185
1 files changed, 185 insertions, 0 deletions
diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb
new file mode 100644
index 000000000..e366bfbdd
--- /dev/null
+++ b/lib/puppet/ssl/host.rb
@@ -0,0 +1,185 @@
+require 'puppet/ssl'
+require 'puppet/ssl/key'
+require 'puppet/ssl/certificate'
+require 'puppet/ssl/certificate_request'
+require 'puppet/ssl/certificate_revocation_list'
+require 'puppet/util/constant_inflector'
+
+# 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
+ Certificate = Puppet::SSL::Certificate
+ CertificateRequest = Puppet::SSL::CertificateRequest
+ CertificateRevocationList = Puppet::SSL::CertificateRevocationList
+
+ extend Puppet::Util::ConstantInflector
+
+ attr_reader :name
+ attr_accessor :ca
+
+ attr_writer :key, :certificate, :certificate_request
+
+ 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
+
+ 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 or CRL, 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
+ end
+ end
+
+ # Specify how we expect to interact with our certificate authority.
+ def self.ca_location=(mode)
+ raise ArgumentError, "CA Mode can only be :local, :remote, or :none" unless [:local, :remote, :none].include?(mode)
+
+ @ca_mode = mode
+
+ case @ca_mode
+ when :local:
+ # Our ca is local, so we use it as the ultimate source of information
+ # And we cache files locally.
+ configure_indirection :ca, :file
+ when :remote:
+ configure_indirection :rest, :file
+ when :none:
+ # We have no CA, so we just look in the local file store.
+ configure_indirection :file
+ end
+ end
+
+ # Remove all traces of a given host
+ def self.destroy(name)
+ [Key, Certificate, CertificateRequest].inject(false) do |result, klass|
+ if klass.destroy(name)
+ result = true
+ end
+ result
+ end
+ 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 = {})
+ classes = [Key, CertificateRequest, Certificate]
+ if klass = options[:for]
+ classlist = [klass].flatten
+ else
+ classlist = [Key, CertificateRequest, Certificate]
+ end
+
+ # 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
+
+ # 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 @key ||= Key.find(name)
+ @key
+ end
+
+ # This is the private key; we can create it from scratch
+ # with no inputs.
+ def generate_key
+ @key = Key.new(name)
+ @key.generate
+ @key.save
+ true
+ end
+
+ def certificate_request
+ return nil unless @certificate_request ||= CertificateRequest.find(name)
+ @certificate_request
+ 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)
+ @certificate_request.save
+ return true
+ end
+
+ def certificate
+ return nil unless @certificate ||= Certificate.find(name)
+ @certificate
+ 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]
+ @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)
+ store = OpenSSL::X509::Store.new
+ store.purpose = purpose
+
+ store.add_file(Puppet[:localcacert])
+
+ # If there's a CRL, add it to our store.
+ if crl = Puppet::SSL::CertificateRevocationList.find("ca")
+ store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK
+ store.add_crl(crl.content)
+ end
+ return store
+ end
+end
+
+require 'puppet/ssl/certificate_authority'