Class: Puppet::SSLCertificates::CA

Inherits:
Object
  • Object
show all
Includes:
Util::Warnings
Defined in:
lib/puppet/sslcertificates/ca.rb

Constant Summary collapse

Certificate =
Puppet::SSLCertificates::Certificate

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Util::Warnings

clear_warnings, notice_once, warnonce

Constructor Details

#initialize(hash = {}) ⇒ CA

Returns a new instance of CA.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/puppet/sslcertificates/ca.rb', line 54

def initialize(hash = {})
  Puppet.settings.use(:main, :ca, :ssl)
  self.setconfig(hash)

  if Puppet[:capass]
    if FileTest.exists?(Puppet[:capass])
      #puts "Reading #{Puppet[:capass]}"
      #system "ls -al #{Puppet[:capass]}"
      #File.read Puppet[:capass]
      @config[:password] = self.getpass
    else
      # Don't create a password if the cert already exists
      @config[:password] = self.genpass unless FileTest.exists?(@config[:cacert])
    end
  end

  self.getcert
  init_crl
  unless FileTest.exists?(@config[:serial])
    Puppet.settings.write(:serial) do |f|
      f << "%04X" % 1
    end
  end
end

Instance Attribute Details

#certObject

Returns the value of attribute cert.



7
8
9
# File 'lib/puppet/sslcertificates/ca.rb', line 7

def cert
  @cert
end

#configObject

Returns the value of attribute config.



7
8
9
# File 'lib/puppet/sslcertificates/ca.rb', line 7

def config
  @config
end

#crlObject

Returns the value of attribute crl.



7
8
9
# File 'lib/puppet/sslcertificates/ca.rb', line 7

def crl
  @crl
end

#dirObject

Returns the value of attribute dir.



7
8
9
# File 'lib/puppet/sslcertificates/ca.rb', line 7

def dir
  @dir
end

#fileObject

Returns the value of attribute file.



7
8
9
# File 'lib/puppet/sslcertificates/ca.rb', line 7

def file
  @file
end

#keyfileObject

Returns the value of attribute keyfile.



7
8
9
# File 'lib/puppet/sslcertificates/ca.rb', line 7

def keyfile
  @keyfile
end

Instance Method Details

#certfileObject



9
10
11
# File 'lib/puppet/sslcertificates/ca.rb', line 9

def certfile
  @config[:cacert]
end

#clean(host) ⇒ Object

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



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/puppet/sslcertificates/ca.rb', line 14

def clean(host)
  host = host.downcase
  [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name|
    dir = Puppet[name]

    file = File.join(dir, host + ".pem")

    if FileTest.exists?(file)
      begin
        if Puppet[:name] == "cert"
          puts "Removing #{file}"
        else
          Puppet.info "Removing #{file}"
        end
        File.unlink(file)
      rescue => detail
        raise Puppet::Error, "Could not delete #{file}: #{detail}"
      end
    end

  end
end

#genpassObject

Generate a new password for the CA.



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/puppet/sslcertificates/ca.rb', line 80

def genpass
  pass = ""
  20.times { pass += (rand(74) + 48).chr }

  begin
    Puppet.settings.write(:capass) { |f| f.print pass }
  rescue Errno::EACCES => detail
    raise Puppet::Error, detail.to_s
  end
  pass
end

#getcertObject

Get the CA cert.



102
103
104
105
106
107
108
109
110
# File 'lib/puppet/sslcertificates/ca.rb', line 102

def getcert
  if FileTest.exists?(@config[:cacert])
    @cert = OpenSSL::X509::Certificate.new(
      File.read(@config[:cacert])
    )
  else
    self.mkrootcert
  end
end

#getclientcert(host) ⇒ Object

Retrieve a client’s certificate.



121
122
123
124
125
126
# File 'lib/puppet/sslcertificates/ca.rb', line 121

def getclientcert(host)
  certfile = host2certfile(host)
  return [nil, nil] unless File.exists?(certfile)

  [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert]
end

#getclientcsr(host) ⇒ Object

Retrieve a client’s CSR.



113
114
115
116
117
118
# File 'lib/puppet/sslcertificates/ca.rb', line 113

def getclientcsr(host)
  csrfile = host2csrfile(host)
  return nil unless File.exists?(csrfile)

  OpenSSL::X509::Request.new(File.read(csrfile))
end

#getpassObject

Get the CA password.



93
94
95
96
97
98
99
# File 'lib/puppet/sslcertificates/ca.rb', line 93

def getpass
  if @config[:capass] and File.readable?(@config[:capass])
    return File.read(@config[:capass])
  else
    raise Puppet::Error, "Could not decrypt CA key with password: #{detail}"
  end
end

#host2certfile(hostname) ⇒ Object

this stores signed certs in a directory unrelated to normal client certs



43
44
45
# File 'lib/puppet/sslcertificates/ca.rb', line 43

def host2certfile(hostname)
  File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join("."))
end

#host2csrfile(hostname) ⇒ Object



37
38
39
# File 'lib/puppet/sslcertificates/ca.rb', line 37

def host2csrfile(hostname)
  File.join(Puppet[:csrdir], [hostname.downcase, "pem"].join("."))
end

#list(dummy_argument = :work_arround_for_ruby_GC_bug) ⇒ Object

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.



130
131
132
133
134
135
136
# File 'lib/puppet/sslcertificates/ca.rb', line 130

def list(dummy_argument=:work_arround_for_ruby_GC_bug)
  return Dir.entries(Puppet[:csrdir]).find_all { |file|
    file =~ /\.pem$/
  }.collect { |file|
    file.sub(/\.pem$/, '')
  }
end

#list_signed(dummy_argument = :work_arround_for_ruby_GC_bug) ⇒ Object

List signed certificates. This returns a list of hostnames, not actual files – the names can be converted to full paths with host2csrfile.



140
141
142
143
144
145
146
# File 'lib/puppet/sslcertificates/ca.rb', line 140

def list_signed(dummy_argument=:work_arround_for_ruby_GC_bug)
  return Dir.entries(Puppet[:signeddir]).find_all { |file|
    file =~ /\.pem$/
  }.collect { |file|
    file.sub(/\.pem$/, '')
  }
end

#mkrootcertObject

Create the root certificate.



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/puppet/sslcertificates/ca.rb', line 149

def mkrootcert
  # Make the root cert's name "Puppet CA: " plus the FQDN of the host running the CA.
  name = "Puppet CA: #{Facter["hostname"].value}"
  if domain = Facter["domain"].value
    name += ".#{domain}"
  end

  cert = Certificate.new(
    :name => name,
    :cert => @config[:cacert],
    :encrypt => @config[:capass],
    :key => @config[:cakey],
    :selfsign => true,
    :ttl => ttl,
    :type => :ca
  )

  # This creates the cakey file
  Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do
    @cert = cert.mkselfsigned
  end
  Puppet.settings.write(:cacert) do |f|
    f.puts @cert.to_pem
  end
  Puppet.settings.write(:capub) do |f|
    f.puts @cert.public_key
  end
  cert
end

#removeclientcsr(host) ⇒ Object

Raises:



179
180
181
182
183
184
# File 'lib/puppet/sslcertificates/ca.rb', line 179

def removeclientcsr(host)
  csrfile = host2csrfile(host)
  raise Puppet::Error, "No certificate request for #{host}" unless File.exists?(csrfile)

  File.unlink(csrfile)
end

#revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) ⇒ Object

Revoke the certificate with serial number SERIAL issued by this CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/puppet/sslcertificates/ca.rb', line 188

def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE)
  time = Time.now
  revoked = OpenSSL::X509::Revoked.new
  revoked.serial = serial
  revoked.time = time
  enum = OpenSSL::ASN1::Enumerated(reason)
  ext = OpenSSL::X509::Extension.new("CRLReason", enum)
  revoked.add_extension(ext)
  @crl.add_revoked(revoked)
  store_crl
end

#setconfig(hash) ⇒ Object

Take the Puppet config and store it locally.

Raises:

  • (ArgumentError)


201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/puppet/sslcertificates/ca.rb', line 201

def setconfig(hash)
  @config = {}
  Puppet.settings.params("ca").each { |param|
    param = param.intern if param.is_a? String
    if hash.include?(param)
      @config[param] = hash[param]
      Puppet[param] = hash[param]
      hash.delete(param)
    else
      @config[param] = Puppet[param]
    end
  }

  if hash.include?(:password)
    @config[:password] = hash[:password]
    hash.delete(:password)
  end

  raise ArgumentError, "Unknown parameters #{hash.keys.join(",")}" if hash.length > 0

  [:cadir, :csrdir, :signeddir].each { |dir|
    raise Puppet::DevError, "#{dir} is undefined" unless @config[dir]
  }
end

#sign(csr) ⇒ Object

Sign a given certificate request.

Raises:



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/puppet/sslcertificates/ca.rb', line 227

def sign(csr)
  unless csr.is_a?(OpenSSL::X509::Request)
    raise Puppet::Error,
      "CA#sign only accepts OpenSSL::X509::Request objects, not #{csr.class}"
  end

  raise Puppet::Error, "CSR sign verification failed" unless csr.verify(csr.public_key)

  serial = nil
  Puppet.settings.readwritelock(:serial) { |f|
    serial = File.read(@config[:serial]).chomp.hex
    # increment the serial
    f << "%04X" % (serial + 1)
  }

  newcert = Puppet::SSLCertificates.mkcert(
    :type => :server,
    :name => csr.subject,
    :ttl => ttl,
    :issuer => @cert,
    :serial => serial,
    :publickey => csr.public_key
  )

  sign_with_key(newcert)

  self.storeclientcert(newcert)

  [newcert, @cert]
end

#storeclientcert(cert) ⇒ Object

Store the certificate that we generate.



273
274
275
276
277
278
279
280
281
282
283
# File 'lib/puppet/sslcertificates/ca.rb', line 273

def storeclientcert(cert)
  host = thing2name(cert)

  certfile = host2certfile(host)
  Puppet.notice "Overwriting signed certificate #{certfile} for #{host}" if File.exists?(certfile)

  Puppet::SSLCertificates::Inventory::add(cert)
  Puppet.settings.writesub(:signeddir, certfile) do |f|
    f.print cert.to_pem
  end
end

#storeclientcsr(csr) ⇒ Object

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.

Raises:



261
262
263
264
265
266
267
268
269
270
# File 'lib/puppet/sslcertificates/ca.rb', line 261

def storeclientcsr(csr)
  host = thing2name(csr)

  csrfile = host2csrfile(host)
  raise Puppet::Error, "Certificate request for #{host} already exists" if File.exists?(csrfile)

  Puppet.settings.writesub(:csrdir, csrfile) do |f|
    f.print csr.to_pem
  end
end

#thing2name(thing) ⇒ Object

Turn our hostname into a Name object



48
49
50
51
52
# File 'lib/puppet/sslcertificates/ca.rb', line 48

def thing2name(thing)
  thing.subject.to_a.find { |ary|
    ary[0] == "CN"
  }[1]
end

#ttlObject

TTL for new certificates in seconds. If config param :ca_ttl is set, use that, otherwise use :ca_days for backwards compatibility



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/puppet/sslcertificates/ca.rb', line 287

def ttl
  days = @config[:ca_days]
  if days && days.size > 0
    warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead."
    return @config[:ca_days] * 24 * 60 * 60
  else
    ttl = @config[:ca_ttl]
    if ttl.is_a?(String)
      unless ttl =~ /^(\d+)(y|d|h|s)$/
        raise ArgumentError, "Invalid ca_ttl #{ttl}"
      end
      case $2
      when 'y'
        unit = 365 * 24 * 60 * 60
      when 'd'
        unit = 24 * 60 * 60
      when 'h'
        unit = 60 * 60
      when 's'
        unit = 1
      else
        raise ArgumentError, "Invalid unit for ca_ttl #{ttl}"
      end
      return $1.to_i * unit
    else
      return ttl
    end
  end
end