The class that manages all aspects of our SSL certificates – private keys, public keys, requests, etc.
Manage certificates themselves. This class has no ‘generate’ method because the CA is responsible for turning CSRs into certificates; we can only retrieve them from the CA (or not, as is often the case).
Manage certificate requests.
Manage the CRL.
Yay, ruby’s strange constant lookups.
This accessor is used in instances for indirector requests to hold desired state
Specify how we expect to interact with our certificate authority.
# File lib/puppet/ssl/host.rb, line 101 def self.ca_location=(mode) modes = CA_MODES.collect { |m, vals| m.to_s }.join(", ") raise ArgumentError, "CA Mode can only be one of: #{modes}" unless CA_MODES.include?(mode) @ca_location = mode configure_indirection(*CA_MODES[@ca_location]) end
This is the constant that people will use to mark that a given host is a certificate authority.
# File lib/puppet/ssl/host.rb, line 43 def self.ca_name CA_NAME end
Configure how our various classes interact with their various terminuses.
# File lib/puppet/ssl/host.rb, line 52 def self.configure_indirection(terminus, cache = nil) Certificate.indirection.terminus_class = terminus CertificateRequest.indirection.terminus_class = terminus CertificateRevocationList.indirection.terminus_class = terminus host_map = {:ca => :file, :disabled_ca => nil, :file => nil, :rest => :rest} if term = host_map[terminus] self.indirection.terminus_class = term else self.indirection.reset_terminus_class end 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.indirection.terminus_class = cache else Key.indirection.terminus_class = terminus end if cache Certificate.indirection.cache_class = cache CertificateRequest.indirection.cache_class = cache CertificateRevocationList.indirection.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.indirection.cache_class = nil CertificateRequest.indirection.cache_class = nil CertificateRevocationList.indirection.cache_class = nil end end
Puppet::SSL::Host is actually indirected now so the original implementation has been moved into the certificate_status indirector. This method is in-use in `puppet cert -c <certname>`.
# File lib/puppet/ssl/host.rb, line 113 def self.destroy(name) indirection.destroy(name) end
# File lib/puppet/ssl/host.rb, line 117 def self.from_pson(pson) instance = new(pson["name"]) if pson["desired_state"] instance.desired_state = pson["desired_state"] end instance end
# File lib/puppet/ssl/host.rb, line 29 def self.localhost return @localhost if @localhost @localhost = new @localhost.generate unless @localhost.certificate @localhost.key @localhost end
# File lib/puppet/ssl/host.rb, line 237 def initialize(name = nil) @name = (name || Puppet[:certname]).downcase Puppet::SSL::Base.validate_certname(@name) @key = @certificate = @certificate_request = nil @ca = (name == self.class.ca_name) end
# File lib/puppet/ssl/host.rb, line 37 def self.reset @localhost = nil end
Puppet::SSL::Host is actually indirected now so the original implementation has been moved into the certificate_status indirector. This method does not appear to be in use in `puppet cert -l`.
# File lib/puppet/ssl/host.rb, line 128 def self.search(options = {}) indirection.search("*", options) end
Is this a ca host, meaning that all of its files go in the CA location?
# File lib/puppet/ssl/host.rb, line 133 def ca? ca end
# File lib/puppet/ssl/host.rb, line 193 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.indirection.find("ca") unless ca? return nil unless @certificate = Certificate.indirection.find(name) validate_certificate_with_key end @certificate end
# File lib/puppet/ssl/host.rb, line 155 def certificate_request @certificate_request ||= CertificateRequest.indirection.find(name) end
Generate all necessary parts of our ssl host.
# File lib/puppet/ssl/host.rb, line 225 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, true) end end
Our certificate request requires the key but that’s all.
# File lib/puppet/ssl/host.rb, line 168 def generate_certificate_request(options = {}) generate_key unless key # If this is for the current machine... if this_csr_is_for_the_current_host # ...add our configured dns_alt_names if Puppet[:dns_alt_names] and Puppet[:dns_alt_names] != '' options[:dns_alt_names] ||= Puppet[:dns_alt_names] elsif Puppet::SSL::CertificateAuthority.ca? and fqdn = Facter.value(:fqdn) and domain = Facter.value(:domain) options[:dns_alt_names] = "puppet, #{fqdn}, puppet.#{domain}" end end @certificate_request = CertificateRequest.new(name) @certificate_request.generate(key.content, options) begin CertificateRequest.indirection.save(@certificate_request) rescue @certificate_request = nil raise end true end
This is the private key; we can create it from scratch with no inputs.
# File lib/puppet/ssl/host.rb, line 143 def generate_key @key = Key.new(name) @key.generate begin Key.indirection.save(@key) rescue @key = nil raise end true end
# File lib/puppet/ssl/host.rb, line 137 def key @key ||= Key.indirection.find(name) end
Extract the public key from the private key.
# File lib/puppet/ssl/host.rb, line 245 def public_key key.content.public_key end
Create/return a store that uses our SSL info to validate connections.
# File lib/puppet/ssl/host.rb, line 251 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.indirection.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 end
# File lib/puppet/ssl/host.rb, line 348 def state if certificate_request return 'requested' end begin Puppet::SSL::CertificateAuthority.new.verify(name) return 'signed' rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError return 'revoked' end end
eventually we’ll probably want to move this somewhere else or make it configurable –jeffweiss 29 aug 2012
# File lib/puppet/ssl/host.rb, line 309 def suitable_message_digest_algorithms [:SHA1, :SHA256, :SHA512] end
# File lib/puppet/ssl/host.rb, line 159 def this_csr_is_for_the_current_host name == Puppet[:certname].downcase end
# File lib/puppet/ssl/host.rb, line 270 def to_pson(*args) my_cert = Puppet::SSL::Certificate.indirection.find(name) pson_hash = { :name => name } my_state = state pson_hash[:state] = my_state pson_hash[:desired_state] = desired_state if desired_state thing_to_use = (my_state == 'requested') ? certificate_request : my_cert # this is for backwards-compatibility # we should deprecate it and transition people to using # pson[:fingerprints][:default] # It appears that we have no internal consumers of this api # --jeffweiss 30 aug 2012 pson_hash[:fingerprint] = thing_to_use.fingerprint # The above fingerprint doesn't tell us what message digest algorithm was used # No problem, except that the default is changing between 2.7 and 3.0. Also, as # we move to FIPS 140-2 compliance, MD5 is no longer allowed (and, gasp, will # segfault in rubies older than 1.9.3) # So, when we add the newer fingerprints, we're explicit about the hashing # algorithm used. # --jeffweiss 31 july 2012 pson_hash[:fingerprints] = {} pson_hash[:fingerprints][:default] = thing_to_use.fingerprint suitable_message_digest_algorithms.each do |md| pson_hash[:fingerprints][md] = thing_to_use.fingerprint md end pson_hash[:dns_alt_names] = thing_to_use.subject_alt_names pson_hash.to_pson(*args) end
# File lib/puppet/ssl/host.rb, line 207 def validate_certificate_with_key raise Puppet::Error, "No certificate to validate." unless certificate raise Puppet::Error, "No private key with which to validate certificate with fingerprint: #{certificate.fingerprint}" unless key unless certificate.content.check_private_key(key.content) raise Puppet::Error, <<ERROR_STRING The certificate retrieved from the master does not match the agent's private key. Certificate fingerprint: #{certificate.fingerprint} To fix this, remove the certificate from both the master and the agent and then start a puppet run, which will automatically regenerate a certficate. On the master: puppet cert clean #{Puppet[:certname]} On the agent: rm -f #{Puppet[:hostcert]} puppet agent -t ERROR_STRING end end
Attempt to retrieve a cert, if we don’t already have one.
# File lib/puppet/ssl/host.rb, line 314 def wait_for_cert(time) begin return if certificate generate return if certificate rescue SystemExit,NoMemoryError raise rescue Exception => detail Puppet.log_exception(detail, "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 Puppet.log_exception(detail, "Could not request certificate: #{detail}") end end end