1
require 'puppet/indirector'
3
require 'puppet/ssl/key'
4
require 'puppet/ssl/certificate'
5
require 'puppet/ssl/certificate_request'
6
require 'puppet/ssl/certificate_revocation_list'
7
require 'puppet/util/cacher'
9
# The class that manages all aspects of our SSL certificates --
10
# private keys, public keys, requests, etc.
11
class Puppet::SSL::Host
12
# Yay, ruby's strange constant lookups.
13
Key = Puppet::SSL::Key
14
CA_NAME = Puppet::SSL::CA_NAME
15
Certificate = Puppet::SSL::Certificate
16
CertificateRequest = Puppet::SSL::CertificateRequest
17
CertificateRevocationList = Puppet::SSL::CertificateRevocationList
19
extend Puppet::Indirector
20
indirects :certificate_status, :terminus_class => :file
25
attr_writer :key, :certificate, :certificate_request
27
# This accessor is used in instances for indirector requests to hold desired state
28
attr_accessor :desired_state
31
include Puppet::Util::Cacher
33
cached_attr(:localhost) do
35
result.generate unless result.certificate
36
result.key # Make sure it's read in
41
# This is the constant that people will use to mark that a given host is
42
# a certificate authority.
48
attr_reader :ca_location
51
# Configure how our various classes interact with their various terminuses.
52
def self.configure_indirection(terminus, cache = nil)
53
Certificate.indirection.terminus_class = terminus
54
CertificateRequest.indirection.terminus_class = terminus
55
CertificateRevocationList.indirection.terminus_class = terminus
57
host_map = {:ca => :file, :file => nil, :rest => :rest}
58
if term = host_map[terminus]
59
self.indirection.terminus_class = term
61
self.indirection.reset_terminus_class
65
# This is weird; we don't actually cache our keys, we
66
# use what would otherwise be the cache as our normal
68
Key.indirection.terminus_class = cache
70
Key.indirection.terminus_class = terminus
74
Certificate.indirection.cache_class = cache
75
CertificateRequest.indirection.cache_class = cache
76
CertificateRevocationList.indirection.cache_class = cache
78
# Make sure we have no cache configured. puppet master
79
# switches the configurations around a bit, so it's important
80
# that we specify the configs for absolutely everything, every
82
Certificate.indirection.cache_class = nil
83
CertificateRequest.indirection.cache_class = nil
84
CertificateRevocationList.indirection.cache_class = nil
89
# Our ca is local, so we use it as the ultimate source of information
90
# And we cache files locally.
91
:local => [:ca, :file],
92
# We're a remote CA client.
93
:remote => [:rest, :file],
94
# We are the CA, so we don't have read/write access to the normal certificates.
96
# We have no CA, so we just look in the local file store.
100
# Specify how we expect to interact with our certificate authority.
101
def self.ca_location=(mode)
102
modes = CA_MODES.collect { |m, vals| m.to_s }.join(", ")
103
raise ArgumentError, "CA Mode can only be one of: #{modes}" unless CA_MODES.include?(mode)
107
configure_indirection(*CA_MODES[@ca_location])
110
# Puppet::SSL::Host is actually indirected now so the original implementation
111
# has been moved into the certificate_status indirector. This method is in-use
112
# in `puppet cert -c <certname>`.
113
def self.destroy(name)
114
indirection.destroy(name)
117
def self.from_pson(pson)
118
instance = new(pson["name"])
119
if pson["desired_state"]
120
instance.desired_state = pson["desired_state"]
125
# Puppet::SSL::Host is actually indirected now so the original implementation
126
# has been moved into the certificate_status indirector. This method does not
127
# appear to be in use in `puppet cert -l`.
128
def self.search(options = {})
129
indirection.search("*", options)
132
# Is this a ca host, meaning that all of its files go in the CA location?
138
@key ||= Key.indirection.find(name)
141
# This is the private key; we can create it from scratch
147
Key.indirection.save(@key)
155
def certificate_request
156
@certificate_request ||= CertificateRequest.indirection.find(name)
159
# Our certificate request requires the key but that's all.
160
def generate_certificate_request
161
generate_key unless key
162
@certificate_request = CertificateRequest.new(name)
163
@certificate_request.generate(key.content)
165
CertificateRequest.indirection.save(@certificate_request)
167
@certificate_request = nil
176
generate_key unless key
178
# get the CA cert first, since it's required for the normal cert
180
return nil unless Certificate.indirection.find("ca") unless ca?
181
return nil unless @certificate = Certificate.indirection.find(name)
183
unless certificate_matches_key?
184
raise Puppet::Error, "Retrieved certificate does not match private key; please remove certificate from server and regenerate it with the current key"
190
def certificate_matches_key?
191
return false unless key
192
return false unless certificate
194
certificate.content.check_private_key(key.content)
197
# Generate all necessary parts of our ssl host.
199
generate_key unless key
200
generate_certificate_request unless certificate_request
202
# If we can get a CA instance, then we're a valid CA, and we
203
# should use it to sign our request; else, just try to read
205
if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance
210
def initialize(name = nil)
211
@name = (name || Puppet[:certname]).downcase
212
@key = @certificate = @certificate_request = nil
213
@ca = (name == self.class.ca_name)
216
# Extract the public key from the private key.
218
key.content.public_key
221
# Create/return a store that uses our SSL info to validate
223
def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY)
225
@ssl_store = OpenSSL::X509::Store.new
226
@ssl_store.purpose = purpose
228
# Use the file path here, because we don't want to cause
229
# a lookup in the middle of setting our ssl connection.
230
@ssl_store.add_file(Puppet[:localcacert])
232
# If there's a CRL, add it to our store.
233
if crl = Puppet::SSL::CertificateRevocationList.indirection.find(CA_NAME)
234
@ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation]
235
@ssl_store.add_crl(crl.content)
243
my_cert = Puppet::SSL::Certificate.indirection.find(name)
244
pson_hash = { :name => name }
248
pson_hash[:state] = my_state
249
pson_hash[:desired_state] = desired_state if desired_state
251
if my_state == 'requested'
252
pson_hash[:fingerprint] = certificate_request.fingerprint
254
pson_hash[:fingerprint] = my_cert.fingerprint
257
pson_hash.to_pson(*args)
260
# Attempt to retrieve a cert, if we don't already have one.
261
def wait_for_cert(time)
263
return if certificate
265
return if certificate
266
rescue SystemExit,NoMemoryError
268
rescue Exception => detail
269
puts detail.backtrace if Puppet[:trace]
270
Puppet.err "Could not request certificate: #{detail}"
272
puts "Exiting; failed to retrieve certificate and waitforcert is disabled"
281
puts "Exiting; no certificate found and waitforcert is disabled"
289
Puppet.notice "Did not receive certificate"
290
rescue StandardError => detail
291
puts detail.backtrace if Puppet[:trace]
292
Puppet.err "Could not request certificate: #{detail}"
298
my_cert = Puppet::SSL::Certificate.indirection.find(name)
299
if certificate_request
304
Puppet::SSL::CertificateAuthority.new.verify(my_cert)
306
rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError
312
require 'puppet/ssl/certificate_authority'