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

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