| Class | Puppet::SSLCertificates::CA |
| In: |
lib/puppet/sslcertificates/ca.rb
|
| Parent: | Object |
| Certificate | = | Puppet::SSLCertificates::Certificate |
| cert | [RW] | |
| config | [RW] | |
| crl | [RW] | |
| dir | [RW] | |
| file | [RW] | |
| keyfile | [RW] |
# File lib/puppet/sslcertificates/ca.rb, line 55
55: def initialize(hash = {})
56: Puppet.settings.use(:main, :ca, :ssl)
57: self.setconfig(hash)
58:
59: if Puppet[:capass]
60: if FileTest.exists?(Puppet[:capass])
61: #puts "Reading %s" % Puppet[:capass]
62: #system "ls -al %s" % Puppet[:capass]
63: #File.read Puppet[:capass]
64: @config[:password] = self.getpass
65: else
66: # Don't create a password if the cert already exists
67: unless FileTest.exists?(@config[:cacert])
68: @config[:password] = self.genpass
69: end
70: end
71: end
72:
73: self.getcert
74: init_crl
75: unless FileTest.exists?(@config[:serial])
76: Puppet.settings.write(:serial) do |f|
77: f << "%04X" % 1
78: end
79: end
80: end
Remove all traces of a given host. This is kind of hackish, but, eh.
# File lib/puppet/sslcertificates/ca.rb, line 14
14: def clean(host)
15: host = host.downcase
16: [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name|
17: dir = Puppet[name]
18:
19: file = File.join(dir, host + ".pem")
20:
21: if FileTest.exists?(file)
22: begin
23: if Puppet[:name] == "puppetca"
24: puts "Removing %s" % file
25: else
26: Puppet.info "Removing %s" % file
27: end
28: File.unlink(file)
29: rescue => detail
30: raise Puppet::Error, "Could not delete %s: %s" %
31: [file, detail]
32: end
33: end
34:
35: end
36: end
Generate a new password for the CA.
# File lib/puppet/sslcertificates/ca.rb, line 83
83: def genpass
84: pass = ""
85: 20.times { pass += (rand(74) + 48).chr }
86:
87: begin
88: Puppet.settings.write(:capass) { |f| f.print pass }
89: rescue Errno::EACCES => detail
90: raise Puppet::Error, detail.to_s
91: end
92: return pass
93: end
Retrieve a client‘s certificate.
# File lib/puppet/sslcertificates/ca.rb, line 126
126: def getclientcert(host)
127: certfile = host2certfile(host)
128: unless File.exists?(certfile)
129: return [nil, nil]
130: end
131:
132: return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert]
133: end
Retrieve a client‘s CSR.
# File lib/puppet/sslcertificates/ca.rb, line 116
116: def getclientcsr(host)
117: csrfile = host2csrfile(host)
118: unless File.exists?(csrfile)
119: return nil
120: end
121:
122: return OpenSSL::X509::Request.new(File.read(csrfile))
123: end
Get the CA password.
# File lib/puppet/sslcertificates/ca.rb, line 96
96: def getpass
97: if @config[:capass] and File.readable?(@config[:capass])
98: return File.read(@config[:capass])
99: else
100: raise Puppet::Error, "Could not decrypt CA key with password: %s" % detail
101: end
102: end
this stores signed certs in a directory unrelated to normal client certs
# File lib/puppet/sslcertificates/ca.rb, line 44
44: def host2certfile(hostname)
45: File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join("."))
46: end
# File lib/puppet/sslcertificates/ca.rb, line 38
38: def host2csrfile(hostname)
39: File.join(Puppet[:csrdir], [hostname.downcase, "pem"].join("."))
40: end
List certificates waiting to be signed. This returns a list of hostnames, not actual files — the names can be converted to full paths with host2csrfile.
# File lib/puppet/sslcertificates/ca.rb, line 137
137: def list
138: return Dir.entries(Puppet[:csrdir]).find_all { |file|
139: file =~ /\.pem$/
140: }.collect { |file|
141: file.sub(/\.pem$/, '')
142: }
143: end
List signed certificates. This returns a list of hostnames, not actual files — the names can be converted to full paths with host2csrfile.
# File lib/puppet/sslcertificates/ca.rb, line 147
147: def list_signed
148: return Dir.entries(Puppet[:signeddir]).find_all { |file|
149: file =~ /\.pem$/
150: }.collect { |file|
151: file.sub(/\.pem$/, '')
152: }
153: end
Create the root certificate.
# File lib/puppet/sslcertificates/ca.rb, line 156
156: def mkrootcert
157: # Make the root cert's name the FQDN of the host running the CA.
158: name = Facter["hostname"].value
159: if domain = Facter["domain"].value
160: name += "." + domain
161: end
162: cert = Certificate.new(
163: :name => name,
164: :cert => @config[:cacert],
165: :encrypt => @config[:capass],
166: :key => @config[:cakey],
167: :selfsign => true,
168: :ttl => ttl,
169: :type => :ca
170: )
171:
172: # This creates the cakey file
173: Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do
174: @cert = cert.mkselfsigned
175: end
176: Puppet.settings.write(:cacert) do |f|
177: f.puts @cert.to_pem
178: end
179: Puppet.settings.write(:capub) do |f|
180: f.puts @cert.public_key
181: end
182: return cert
183: end
# File lib/puppet/sslcertificates/ca.rb, line 185
185: def removeclientcsr(host)
186: csrfile = host2csrfile(host)
187: unless File.exists?(csrfile)
188: raise Puppet::Error, "No certificate request for %s" % host
189: end
190:
191: File.unlink(csrfile)
192: end
Revoke the certificate with serial number SERIAL issued by this CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons
# File lib/puppet/sslcertificates/ca.rb, line 196
196: def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE)
197: if @config[:cacrl] == 'false'
198: raise Puppet::Error, "Revocation requires a CRL, but ca_crl is set to 'false'"
199: end
200: time = Time.now
201: revoked = OpenSSL::X509::Revoked.new
202: revoked.serial = serial
203: revoked.time = time
204: enum = OpenSSL::ASN1::Enumerated(reason)
205: ext = OpenSSL::X509::Extension.new("CRLReason", enum)
206: revoked.add_extension(ext)
207: @crl.add_revoked(revoked)
208: store_crl
209: end
Take the Puppet config and store it locally.
# File lib/puppet/sslcertificates/ca.rb, line 212
212: def setconfig(hash)
213: @config = {}
214: Puppet.settings.params("ca").each { |param|
215: param = param.intern if param.is_a? String
216: if hash.include?(param)
217: @config[param] = hash[param]
218: Puppet[param] = hash[param]
219: hash.delete(param)
220: else
221: @config[param] = Puppet[param]
222: end
223: }
224:
225: if hash.include?(:password)
226: @config[:password] = hash[:password]
227: hash.delete(:password)
228: end
229:
230: if hash.length > 0
231: raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",")
232: end
233:
234: [:cadir, :csrdir, :signeddir].each { |dir|
235: unless @config[dir]
236: raise Puppet::DevError, "%s is undefined" % dir
237: end
238: }
239: end
Sign a given certificate request.
# File lib/puppet/sslcertificates/ca.rb, line 242
242: def sign(csr)
243: unless csr.is_a?(OpenSSL::X509::Request)
244: raise Puppet::Error,
245: "CA#sign only accepts OpenSSL::X509::Request objects, not %s" %
246: csr.class
247: end
248:
249: unless csr.verify(csr.public_key)
250: raise Puppet::Error, "CSR sign verification failed"
251: end
252:
253: serial = nil
254: Puppet.settings.readwritelock(:serial) { |f|
255: serial = File.read(@config[:serial]).chomp.hex
256: # increment the serial
257: f << "%04X" % (serial + 1)
258: }
259:
260: newcert = Puppet::SSLCertificates.mkcert(
261: :type => :server,
262: :name => csr.subject,
263: :ttl => ttl,
264: :issuer => @cert,
265: :serial => serial,
266: :publickey => csr.public_key
267: )
268:
269:
270: sign_with_key(newcert)
271:
272: self.storeclientcert(newcert)
273:
274: return [newcert, @cert]
275: end
Store the certificate that we generate.
# File lib/puppet/sslcertificates/ca.rb, line 294
294: def storeclientcert(cert)
295: host = thing2name(cert)
296:
297: certfile = host2certfile(host)
298: if File.exists?(certfile)
299: Puppet.notice "Overwriting signed certificate %s for %s" %
300: [certfile, host]
301: end
302:
303: Puppet::SSLCertificates::Inventory::add(cert)
304: Puppet.settings.writesub(:signeddir, certfile) do |f|
305: f.print cert.to_pem
306: end
307: end
Store the client‘s CSR for later signing. This is called from server/ca.rb, and the CSRs are deleted once the certificate is actually signed.
# File lib/puppet/sslcertificates/ca.rb, line 280
280: def storeclientcsr(csr)
281: host = thing2name(csr)
282:
283: csrfile = host2csrfile(host)
284: if File.exists?(csrfile)
285: raise Puppet::Error, "Certificate request for %s already exists" % host
286: end
287:
288: Puppet.settings.writesub(:csrdir, csrfile) do |f|
289: f.print csr.to_pem
290: end
291: end
Turn our hostname into a Name object
# File lib/puppet/sslcertificates/ca.rb, line 49
49: def thing2name(thing)
50: thing.subject.to_a.find { |ary|
51: ary[0] == "CN"
52: }[1]
53: end
TTL for new certificates in seconds. If config param :ca_ttl is set, use that, otherwise use :ca_days for backwards compatibility
# File lib/puppet/sslcertificates/ca.rb, line 311
311: def ttl
312: days = @config[:ca_days]
313: if days && days.size > 0
314: warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead."
315: return @config[:ca_days] * 24 * 60 * 60
316: else
317: ttl = @config[:ca_ttl]
318: if ttl.is_a?(String)
319: unless ttl =~ /^(\d+)(y|d|h|s)$/
320: raise ArgumentError, "Invalid ca_ttl #{ttl}"
321: end
322: case $2
323: when 'y'
324: unit = 365 * 24 * 60 * 60
325: when 'd'
326: unit = 24 * 60 * 60
327: when 'h'
328: unit = 60 * 60
329: when 's'
330: unit = 1
331: else
332: raise ArgumentError, "Invalid unit for ca_ttl #{ttl}"
333: end
334: return $1.to_i * unit
335: else
336: return ttl
337: end
338: end
339: end