Class Puppet::SSLCertificates::CA
In: lib/puppet/sslcertificates/ca.rb
Parent: Object

Methods

Included Modules

Puppet::Util::Warnings

Constants

Certificate = Puppet::SSLCertificates::Certificate

Attributes

cert  [RW] 
config  [RW] 
crl  [RW] 
dir  [RW] 
file  [RW] 
keyfile  [RW] 

Public Class methods

[Source]

    # 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

Public Instance methods

[Source]

    # File lib/puppet/sslcertificates/ca.rb, line 9
 9:     def certfile
10:         @config[:cacert]
11:     end

Remove all traces of a given host. This is kind of hackish, but, eh.

[Source]

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

[Source]

    # 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

Get the CA cert.

[Source]

     # File lib/puppet/sslcertificates/ca.rb, line 105
105:     def getcert
106:         if FileTest.exists?(@config[:cacert])
107:             @cert = OpenSSL::X509::Certificate.new(
108:                 File.read(@config[:cacert])
109:             )
110:         else
111:             self.mkrootcert
112:         end
113:     end

Retrieve a client‘s certificate.

[Source]

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

[Source]

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

[Source]

     # 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

[Source]

    # File lib/puppet/sslcertificates/ca.rb, line 44
44:     def host2certfile(hostname)
45:         File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join("."))
46:     end

[Source]

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

[Source]

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

[Source]

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

[Source]

     # 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

[Source]

     # 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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

     # 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

[Source]

    # 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

[Source]

     # 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

[Validate]