The class that manages all aspects of our SSL certificates – private keys, public keys, requests, etc.
Yay, ruby’s strange constant lookups.
This accessor is used in instances for indirector requests to hold desired state
Is this a ca host, meaning that all of its files go in the CA location?
# File lib/puppet/ssl/host.rb, line 142 def ca? ca end
# File lib/puppet/ssl/host.rb, line 200 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", :fail_on_404 => true) unless ca? return nil unless @certificate = Certificate.indirection.find(name) validate_certificate_with_key end @certificate end
# File lib/puppet/ssl/host.rb, line 164 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 233 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 169 def generate_certificate_request(options = {}) generate_key unless key # If this CSR is for the current machine... if name == Puppet[:certname].downcase # ...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 csr_attributes = Puppet::SSL::CertificateRequestAttributes.new(Puppet[:csr_attributes]) if csr_attributes.load options[:csr_attributes] = csr_attributes.custom_attributes options[:extension_requests] = csr_attributes.extension_requests 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 152 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 146 def key @key ||= Key.indirection.find(name) end
Extract the public key from the private key.
# File lib/puppet/ssl/host.rb, line 253 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 259 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 we're doing revocation and there's a CRL, add it to our store. if Puppet.settings[:certificate_revocation] 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 @ssl_store.add_crl(crl.content) end end return @ssl_store end @ssl_store end
# File lib/puppet/ssl/host.rb, line 358 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 319 def suitable_message_digest_algorithms [:SHA1, :SHA256, :SHA512] end
# File lib/puppet/ssl/host.rb, line 280 def to_data_hash my_cert = Puppet::SSL::Certificate.indirection.find(name) result = { :name => name } my_state = state result[:state] = my_state result[: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 result[: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 result[:fingerprints] = {} result[:fingerprints][:default] = thing_to_use.fingerprint suitable_message_digest_algorithms.each do |md| result[:fingerprints][md] = thing_to_use.fingerprint md end result[:dns_alt_names] = thing_to_use.subject_alt_names result end
# File lib/puppet/ssl/host.rb, line 214 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: 1a. On most platforms: find #{Puppet[:ssldir]} -name #{Puppet[:certname]}.pem -delete 1b. On Windows: del "#{Puppet[:ssldir]}/#{Puppet[:certname]}.pem" /f 2. 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 324 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.message}") 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.message}") end end end
Specify how we expect to interact with our certificate authority.
# File lib/puppet/ssl/host.rb, line 105 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 47 def self.ca_name CA_NAME end
Configure how our various classes interact with their various terminuses.
# File lib/puppet/ssl/host.rb, line 56 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 117 def self.destroy(name) indirection.destroy(name) end
# File lib/puppet/ssl/host.rb, line 121 def self.from_data_hash(data) instance = new(data["name"]) if data["desired_state"] instance.desired_state = data["desired_state"] end instance end
# File lib/puppet/ssl/host.rb, line 129 def self.from_pson(pson) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(pson) end
# File lib/puppet/ssl/host.rb, line 33 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 245 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 41 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 137 def self.search(options = {}) indirection.search("*", options) end