# class Puppet::SSL::Host

The class that manages all aspects of our SSL certificates – private keys, public keys, requests, etc.

### Constants

CA_MODES
CA_NAME
Certificate

  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).

CertificateRequest

  Manage certificate requests.

CertificateRevocationList

  Manage the CRL.

Key

  Yay, ruby’s strange constant lookups.

### Attributes

ca_location[R]
ca[RW]
certificate[W]
certificate_request[W]
desired_state[RW]

This accessor is used in instances for indirector requests to hold desired state

key[W]
name[R]

### Public Class Methods

ca_location=(mode) click to toggle source

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

ca_name() click to toggle source

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_indirection(terminus, cache = nil) click to toggle source

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

destroy(name) click to toggle source

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

from_pson(pson) click to toggle source

    # 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

localhost() click to toggle source

    # 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

new(name = nil) click to toggle source

    # 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

reset() click to toggle source

    # File lib/puppet/ssl/host.rb, line 37
    def self.reset
      @localhost = nil
    end

search(options = {}) click to toggle source

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

### Public Instance Methods

ca?() click to toggle source

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

certificate() click to toggle source

    # 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

certificate_request() click to toggle source

    # File lib/puppet/ssl/host.rb, line 155
    def certificate_request
      @certificate_request ||= CertificateRequest.indirection.find(name)
    end

generate() click to toggle source

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

generate_certificate_request(options = {}) click to toggle source

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

generate_key() click to toggle source

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

key() click to toggle source

    # File lib/puppet/ssl/host.rb, line 137
    def key
      @key ||= Key.indirection.find(name)
    end

public_key() click to toggle source

Extract the public key from the private key.

    # File lib/puppet/ssl/host.rb, line 245
    def public_key
      key.content.public_key
    end

ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) click to toggle source

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

state() click to toggle source

    # 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

suitable_message_digest_algorithms() click to toggle source

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

this_csr_is_for_the_current_host() click to toggle source

    # File lib/puppet/ssl/host.rb, line 159
    def this_csr_is_for_the_current_host
      name == Puppet[:certname].downcase
    end

to_pson(*args) click to toggle source

    # 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

validate_certificate_with_key() click to toggle source

    # 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

wait_for_cert(time) click to toggle source

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