1
require 'puppet/ssl/host'
2
require 'puppet/ssl/certificate_request'
3
require 'puppet/util/cacher'
5
# The class that knows how to sign certificates. It creates
6
# a 'special' SSL::Host whose name is 'ca', thus indicating
7
# that, well, it's the CA. There's some magic in the
8
# indirector/ssl_file terminus base class that does that
10
# This class mostly just signs certs for us, but
11
# it can also be seen as a general interface into all of the
13
class Puppet::SSL::CertificateAuthority
14
require 'puppet/ssl/certificate_factory'
15
require 'puppet/ssl/inventory'
16
require 'puppet/ssl/certificate_revocation_list'
18
require 'puppet/ssl/certificate_authority/interface'
20
class CertificateVerificationError < RuntimeError
21
attr_accessor :error_code
29
include Puppet::Util::Cacher
31
cached_attr(:singleton_instance) { new }
35
return false unless Puppet[:ca]
36
return false unless Puppet[:name] == "puppetmasterd"
40
# If this process can function as a CA, then return a singleton
48
attr_reader :name, :host
50
# Create and run an applicator. I wanted to build an interface where you could do
51
# something like 'ca.apply(:generate).to(:all) but I don't think it's really possible.
52
def apply(method, options)
54
raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all"
56
applier = Interface.new(method, options[:to])
61
# If autosign is configured, then autosign all CSRs that match our configuration.
63
return unless auto = autosign?
67
store = autosign_store(auto)
70
Puppet::SSL::CertificateRequest.search("*").each do |csr|
71
sign(csr.name) if auto == true or store.allowed?(csr.name, "127.1.1.1")
75
# Do we autosign? This returns true, false, or a filename.
77
auto = Puppet[:autosign]
78
return false if ['false', false].include?(auto)
79
return true if ['true', true].include?(auto)
81
raise ArgumentError, "The autosign configuration '%s' must be a fully qualified file" % auto unless auto =~ /^\//
82
if FileTest.exist?(auto)
89
# Create an AuthStore for autosigning.
90
def autosign_store(file)
91
auth = Puppet::Network::AuthStore.new
92
File.readlines(file).each do |line|
93
next if line =~ /^\s*#/
94
next if line =~ /^\s*$/
95
auth.allow(line.chomp)
101
# Retrieve (or create, if necessary) the certificate revocation list.
103
unless defined?(@crl)
104
unless @crl = Puppet::SSL::CertificateRevocationList.find("ca")
105
@crl = Puppet::SSL::CertificateRevocationList.new("ca")
106
@crl.generate(host.certificate.content, host.key.content)
113
# Delegate this to our Host class.
115
Puppet::SSL::Host.destroy(name)
118
# Generate a new certificate.
120
raise ArgumentError, "A Certificate already exists for %s" % name if Puppet::SSL::Certificate.find(name)
121
host = Puppet::SSL::Host.new(name)
123
host.generate_certificate_request
128
# Generate our CA certificate.
129
def generate_ca_certificate
130
generate_password unless password?
132
host.generate_key unless host.key
134
# Create a new cert request. We do this
135
# specially, because we don't want to actually
136
# save the request anywhere.
137
request = Puppet::SSL::CertificateRequest.new(host.name)
138
request.generate(host.key)
140
# Create a self-signed certificate.
141
@certificate = sign(host.name, :ca, request)
143
# And make sure we initialize our CRL.
148
Puppet.settings.use :main, :ssl, :ca
150
@name = Puppet[:certname]
152
@host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name)
157
# Retrieve (or create, if necessary) our inventory manager.
159
unless defined?(@inventory)
160
@inventory = Puppet::SSL::Inventory.new
165
# Generate a new password for the CA.
166
def generate_password
168
20.times { pass += (rand(74) + 48).chr }
171
Puppet.settings.write(:capass) { |f| f.print pass }
172
rescue Errno::EACCES => detail
173
raise Puppet::Error, "Could not write CA password: %s" % detail.to_s
181
# List all signed certificates.
183
Puppet::SSL::Certificate.search("*").collect { |c| c.name }
186
# Read the next serial from the serial file, and increment the
187
# file so this one is considered used.
191
# This is slightly odd. If the file doesn't exist, our readwritelock creates
192
# it, but with a mode we can't actually read in some cases. So, use
193
# a default before the lock.
194
unless FileTest.exist?(Puppet[:serial])
198
Puppet.settings.readwritelock(:serial) { |f|
199
if FileTest.exist?(Puppet[:serial])
200
serial ||= File.read(Puppet.settings[:serial]).chomp.hex
203
# We store the next valid serial, not the one we just used.
204
f << "%04X" % (serial + 1)
210
# Does the password file exist?
212
FileTest.exist? Puppet[:capass]
215
# Print a given host's certificate as text.
217
if cert = Puppet::SSL::Certificate.find(name)
224
# Revoke a given certificate.
226
raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl
228
if cert = Puppet::SSL::Certificate.find(name)
229
serial = cert.content.serial
230
elsif ! serial = inventory.serial(name)
231
raise ArgumentError, "Could not find a serial number for %s" % name
233
crl.revoke(serial, host.key.content)
236
# This initializes our CA so it actually works. This should be a private
237
# method, except that you can't any-instance stub private methods, which is
238
# *awesome*. This method only really exists to provide a stub-point during
241
generate_ca_certificate unless @host.certificate
244
# Sign a given certificate request.
245
def sign(hostname, cert_type = :server, self_signing_csr = nil)
246
# This is a self-signed certificate
248
csr = self_signing_csr
251
unless csr = Puppet::SSL::CertificateRequest.find(hostname)
252
raise ArgumentError, "Could not find certificate request for %s" % hostname
254
issuer = host.certificate.content
257
cert = Puppet::SSL::Certificate.new(hostname)
258
cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result
259
cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new)
261
Puppet.notice "Signed certificate request for %s" % hostname
263
# Add the cert to the inventory before we save it, since
264
# otherwise we could end up with it being duplicated, if
265
# this is the first time we build the inventory file.
268
# Save the now-signed cert. This should get routed correctly depending
269
# on the certificate type.
272
# And remove the CSR if this wasn't self signed.
273
Puppet::SSL::CertificateRequest.destroy(csr.name) unless self_signing_csr
278
# Verify a given host's certificate.
280
unless cert = Puppet::SSL::Certificate.find(name)
281
raise ArgumentError, "Could not find a certificate for %s" % name
283
store = OpenSSL::X509::Store.new
284
store.add_file Puppet[:cacert]
285
store.add_crl crl.content if self.crl
286
store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
287
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK
289
unless store.verify(cert.content)
290
raise CertificateVerificationError.new(store.error), store.error_string
294
# List the waiting certificate requests.
296
Puppet::SSL::CertificateRequest.search("*").collect { |r| r.name }