~ubuntu-branches/ubuntu/quantal/puppet/quantal

« back to all changes in this revision

Viewing changes to .pc/2.7.17-Puppet-July-2012-CVE-fixes.patch/lib/puppet/ssl/certificate_authority.rb

  • Committer: Package Import Robot
  • Author(s): Marc Deslauriers
  • Date: 2012-07-14 01:56:30 UTC
  • mfrom: (1.1.29) (3.1.43 sid)
  • Revision ID: package-import@ubuntu.com-20120714015630-ntj41rkvkq4zph4y
Tags: 2.7.18-1ubuntu1
* Resynchronise with Debian. (LP: #1023931) Remaining changes:
  - debian/puppetmaster-passenger.postinst: Make sure we error if puppet
    config print doesn't work
  - debian/puppetmaster-passenger.postinst: Ensure upgrades from
    <= 2.7.11-1 fixup passenger apache configuration.
* Dropped upstreamed patches:
  - debian/patches/CVE-2012-1906_CVE-2012-1986_to_CVE-2012-1989.patch
  - debian/patches/puppet-12844
  - debian/patches/2.7.17-Puppet-July-2012-CVE-fixes.patch
* Drop Build-Depends on ruby-rspec (in universe):
  - debian/control: remove ruby-rspec from Build-Depends
  - debian/patches/no-rspec.patch: make Rakefile work anyway if rspec
    isn't installed so we can use it in debian/rules.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
require 'monitor'
2
 
require 'puppet/ssl/host'
3
 
require 'puppet/ssl/certificate_request'
4
 
 
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
9
 
# for us.
10
 
#   This class mostly just signs certs for us, but
11
 
# it can also be seen as a general interface into all of the
12
 
# SSL stuff.
13
 
class Puppet::SSL::CertificateAuthority
14
 
  # We will only sign extensions on this whitelist, ever.  Any CSR with a
15
 
  # requested extension that we don't recognize is rejected, against the risk
16
 
  # that it will introduce some security issue through our ignorance of it.
17
 
  #
18
 
  # Adding an extension to this whitelist simply means we will consider it
19
 
  # further, not that we will always accept a certificate with an extension
20
 
  # requested on this list.
21
 
  RequestExtensionWhitelist = %w{subjectAltName}
22
 
 
23
 
  require 'puppet/ssl/certificate_factory'
24
 
  require 'puppet/ssl/inventory'
25
 
  require 'puppet/ssl/certificate_revocation_list'
26
 
  require 'puppet/ssl/certificate_authority/interface'
27
 
  require 'puppet/network/authstore'
28
 
 
29
 
  extend MonitorMixin
30
 
 
31
 
  class CertificateVerificationError < RuntimeError
32
 
    attr_accessor :error_code
33
 
 
34
 
    def initialize(code)
35
 
      @error_code = code
36
 
    end
37
 
  end
38
 
 
39
 
  def self.singleton_instance
40
 
    synchronize do
41
 
      @singleton_instance ||= new
42
 
    end
43
 
  end
44
 
 
45
 
  class CertificateSigningError < RuntimeError
46
 
    attr_accessor :host
47
 
 
48
 
    def initialize(host)
49
 
      @host = host
50
 
    end
51
 
  end
52
 
 
53
 
  def self.ca?
54
 
    return false unless Puppet[:ca]
55
 
    return false unless Puppet.run_mode.master?
56
 
    true
57
 
  end
58
 
 
59
 
  # If this process can function as a CA, then return a singleton
60
 
  # instance.
61
 
  def self.instance
62
 
    return nil unless ca?
63
 
 
64
 
    singleton_instance
65
 
  end
66
 
 
67
 
  attr_reader :name, :host
68
 
 
69
 
  # Create and run an applicator.  I wanted to build an interface where you could do
70
 
  # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible.
71
 
  def apply(method, options)
72
 
    raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" unless options[:to]
73
 
    applier = Interface.new(method, options)
74
 
    applier.apply(self)
75
 
  end
76
 
 
77
 
  # If autosign is configured, then autosign all CSRs that match our configuration.
78
 
  def autosign
79
 
    return unless auto = autosign?
80
 
 
81
 
    store = nil
82
 
    store = autosign_store(auto) if auto != true
83
 
 
84
 
    Puppet::SSL::CertificateRequest.indirection.search("*").each do |csr|
85
 
      sign(csr.name) if auto == true or store.allowed?(csr.name, "127.1.1.1")
86
 
    end
87
 
  end
88
 
 
89
 
  # Do we autosign?  This returns true, false, or a filename.
90
 
  def autosign?
91
 
    auto = Puppet[:autosign]
92
 
    return false if ['false', false].include?(auto)
93
 
    return true if ['true', true].include?(auto)
94
 
 
95
 
    raise ArgumentError, "The autosign configuration '#{auto}' must be a fully qualified file" unless auto =~ /^\//
96
 
    FileTest.exist?(auto) && auto
97
 
  end
98
 
 
99
 
  # Create an AuthStore for autosigning.
100
 
  def autosign_store(file)
101
 
    auth = Puppet::Network::AuthStore.new
102
 
    File.readlines(file).each do |line|
103
 
      next if line =~ /^\s*#/
104
 
      next if line =~ /^\s*$/
105
 
      auth.allow(line.chomp)
106
 
    end
107
 
 
108
 
    auth
109
 
  end
110
 
 
111
 
  # Retrieve (or create, if necessary) the certificate revocation list.
112
 
  def crl
113
 
    unless defined?(@crl)
114
 
      unless @crl = Puppet::SSL::CertificateRevocationList.indirection.find(Puppet::SSL::CA_NAME)
115
 
        @crl = Puppet::SSL::CertificateRevocationList.new(Puppet::SSL::CA_NAME)
116
 
        @crl.generate(host.certificate.content, host.key.content)
117
 
        Puppet::SSL::CertificateRevocationList.indirection.save(@crl)
118
 
      end
119
 
    end
120
 
    @crl
121
 
  end
122
 
 
123
 
  # Delegate this to our Host class.
124
 
  def destroy(name)
125
 
    Puppet::SSL::Host.destroy(name)
126
 
  end
127
 
 
128
 
  # Generate a new certificate.
129
 
  def generate(name, options = {})
130
 
    raise ArgumentError, "A Certificate already exists for #{name}" if Puppet::SSL::Certificate.indirection.find(name)
131
 
    host = Puppet::SSL::Host.new(name)
132
 
 
133
 
    # Pass on any requested subjectAltName field.
134
 
    san = options[:dns_alt_names]
135
 
 
136
 
    host = Puppet::SSL::Host.new(name)
137
 
    host.generate_certificate_request(:dns_alt_names => san)
138
 
    sign(name, !!san)
139
 
  end
140
 
 
141
 
  # Generate our CA certificate.
142
 
  def generate_ca_certificate
143
 
    generate_password unless password?
144
 
 
145
 
    host.generate_key unless host.key
146
 
 
147
 
    # Create a new cert request.  We do this specially, because we don't want
148
 
    # to actually save the request anywhere.
149
 
    request = Puppet::SSL::CertificateRequest.new(host.name)
150
 
 
151
 
    # We deliberately do not put any subjectAltName in here: the CA
152
 
    # certificate absolutely does not need them. --daniel 2011-10-13
153
 
    request.generate(host.key)
154
 
 
155
 
    # Create a self-signed certificate.
156
 
    @certificate = sign(host.name, false, request)
157
 
 
158
 
    # And make sure we initialize our CRL.
159
 
    crl
160
 
  end
161
 
 
162
 
  def initialize
163
 
    Puppet.settings.use :main, :ssl, :ca
164
 
 
165
 
    @name = Puppet[:certname]
166
 
 
167
 
    @host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name)
168
 
 
169
 
    setup
170
 
  end
171
 
 
172
 
  # Retrieve (or create, if necessary) our inventory manager.
173
 
  def inventory
174
 
    @inventory ||= Puppet::SSL::Inventory.new
175
 
  end
176
 
 
177
 
  # Generate a new password for the CA.
178
 
  def generate_password
179
 
    pass = ""
180
 
    20.times { pass += (rand(74) + 48).chr }
181
 
 
182
 
    begin
183
 
      Puppet.settings.write(:capass) { |f| f.print pass }
184
 
    rescue Errno::EACCES => detail
185
 
      raise Puppet::Error, "Could not write CA password: #{detail}"
186
 
    end
187
 
 
188
 
    @password = pass
189
 
 
190
 
    pass
191
 
  end
192
 
 
193
 
  # List all signed certificates.
194
 
  def list
195
 
    Puppet::SSL::Certificate.indirection.search("*").collect { |c| c.name }
196
 
  end
197
 
 
198
 
  # Read the next serial from the serial file, and increment the
199
 
  # file so this one is considered used.
200
 
  def next_serial
201
 
    serial = nil
202
 
 
203
 
    # This is slightly odd.  If the file doesn't exist, our readwritelock creates
204
 
    # it, but with a mode we can't actually read in some cases.  So, use
205
 
    # a default before the lock.
206
 
    serial = 0x1 unless FileTest.exist?(Puppet[:serial])
207
 
 
208
 
    Puppet.settings.readwritelock(:serial) { |f|
209
 
      serial ||= File.read(Puppet.settings[:serial]).chomp.hex if FileTest.exist?(Puppet[:serial])
210
 
 
211
 
      # We store the next valid serial, not the one we just used.
212
 
      f << "%04X" % (serial + 1)
213
 
    }
214
 
 
215
 
    serial
216
 
  end
217
 
 
218
 
  # Does the password file exist?
219
 
  def password?
220
 
    FileTest.exist? Puppet[:capass]
221
 
  end
222
 
 
223
 
  # Print a given host's certificate as text.
224
 
  def print(name)
225
 
    (cert = Puppet::SSL::Certificate.indirection.find(name)) ? cert.to_text : nil
226
 
  end
227
 
 
228
 
  # Revoke a given certificate.
229
 
  def revoke(name)
230
 
    raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl
231
 
 
232
 
    if cert = Puppet::SSL::Certificate.indirection.find(name)
233
 
      serial = cert.content.serial
234
 
    elsif ! serial = inventory.serial(name)
235
 
      raise ArgumentError, "Could not find a serial number for #{name}"
236
 
    end
237
 
    crl.revoke(serial, host.key.content)
238
 
  end
239
 
 
240
 
  # This initializes our CA so it actually works.  This should be a private
241
 
  # method, except that you can't any-instance stub private methods, which is
242
 
  # *awesome*.  This method only really exists to provide a stub-point during
243
 
  # testing.
244
 
  def setup
245
 
    generate_ca_certificate unless @host.certificate
246
 
  end
247
 
 
248
 
  # Sign a given certificate request.
249
 
  def sign(hostname, allow_dns_alt_names = false, self_signing_csr = nil)
250
 
    # This is a self-signed certificate
251
 
    if self_signing_csr
252
 
      # # This is a self-signed certificate, which is for the CA.  Since this
253
 
      # # forces the certificate to be self-signed, anyone who manages to trick
254
 
      # # the system into going through this path gets a certificate they could
255
 
      # # generate anyway.  There should be no security risk from that.
256
 
      csr = self_signing_csr
257
 
      cert_type = :ca
258
 
      issuer = csr.content
259
 
    else
260
 
      allow_dns_alt_names = true if hostname == Puppet[:certname].downcase
261
 
      unless csr = Puppet::SSL::CertificateRequest.indirection.find(hostname)
262
 
        raise ArgumentError, "Could not find certificate request for #{hostname}"
263
 
      end
264
 
 
265
 
      cert_type = :server
266
 
      issuer = host.certificate.content
267
 
 
268
 
      # Make sure that the CSR conforms to our internal signing policies.
269
 
      # This will raise if the CSR doesn't conform, but just in case...
270
 
      check_internal_signing_policies(hostname, csr, allow_dns_alt_names) or
271
 
        raise CertificateSigningError.new(hostname), "CSR had an unknown failure checking internal signing policies, will not sign!"
272
 
    end
273
 
 
274
 
    cert = Puppet::SSL::Certificate.new(hostname)
275
 
    cert.content = Puppet::SSL::CertificateFactory.
276
 
      build(cert_type, csr, issuer, next_serial)
277
 
    cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new)
278
 
 
279
 
    Puppet.notice "Signed certificate request for #{hostname}"
280
 
 
281
 
    # Add the cert to the inventory before we save it, since
282
 
    # otherwise we could end up with it being duplicated, if
283
 
    # this is the first time we build the inventory file.
284
 
    inventory.add(cert)
285
 
 
286
 
    # Save the now-signed cert.  This should get routed correctly depending
287
 
    # on the certificate type.
288
 
    Puppet::SSL::Certificate.indirection.save(cert)
289
 
 
290
 
    # And remove the CSR if this wasn't self signed.
291
 
    Puppet::SSL::CertificateRequest.indirection.destroy(csr.name) unless self_signing_csr
292
 
 
293
 
    cert
294
 
  end
295
 
 
296
 
  def check_internal_signing_policies(hostname, csr, allow_dns_alt_names)
297
 
    # Reject unknown request extensions.
298
 
    unknown_req = csr.request_extensions.
299
 
      reject {|x| RequestExtensionWhitelist.include? x["oid"] }
300
 
 
301
 
    if unknown_req and not unknown_req.empty?
302
 
      names = unknown_req.map {|x| x["oid"] }.sort.uniq.join(", ")
303
 
      raise CertificateSigningError.new(hostname), "CSR has request extensions that are not permitted: #{names}"
304
 
    end
305
 
 
306
 
    # Wildcards: we don't allow 'em at any point.
307
 
    #
308
 
    # The stringification here makes the content visible, and saves us having
309
 
    # to scrobble through the content of the CSR subject field to make sure it
310
 
    # is what we expect where we expect it.
311
 
    if csr.content.subject.to_s.include? '*'
312
 
      raise CertificateSigningError.new(hostname), "CSR subject contains a wildcard, which is not allowed: #{csr.content.subject.to_s}"
313
 
    end
314
 
 
315
 
    unless csr.subject_alt_names.empty?
316
 
      # If you alt names are allowed, they are required. Otherwise they are
317
 
      # disallowed. Self-signed certs are implicitly trusted, however.
318
 
      unless allow_dns_alt_names
319
 
        raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' contains subject alternative names (#{csr.subject_alt_names.join(', ')}), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{csr.name}` to sign this request."
320
 
      end
321
 
 
322
 
      # If subjectAltNames are present, validate that they are only for DNS
323
 
      # labels, not any other kind.
324
 
      unless csr.subject_alt_names.all? {|x| x =~ /^DNS:/ }
325
 
        raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' contains a subjectAltName outside the DNS label space: #{csr.subject_alt_names.join(', ')}.  To continue, this CSR needs to be cleaned."
326
 
      end
327
 
 
328
 
      # Check for wildcards in the subjectAltName fields too.
329
 
      if csr.subject_alt_names.any? {|x| x.include? '*' }
330
 
        raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' subjectAltName contains a wildcard, which is not allowed: #{csr.subject_alt_names.join(', ')}  To continue, this CSR needs to be cleaned."
331
 
      end
332
 
    end
333
 
 
334
 
    return true                 # good enough for us!
335
 
  end
336
 
 
337
 
  # Verify a given host's certificate.
338
 
  def verify(name)
339
 
    unless cert = Puppet::SSL::Certificate.indirection.find(name)
340
 
      raise ArgumentError, "Could not find a certificate for #{name}"
341
 
    end
342
 
    store = OpenSSL::X509::Store.new
343
 
    store.add_file Puppet[:cacert]
344
 
    store.add_crl crl.content if self.crl
345
 
    store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
346
 
    store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation]
347
 
 
348
 
    raise CertificateVerificationError.new(store.error), store.error_string unless store.verify(cert.content)
349
 
  end
350
 
 
351
 
  def fingerprint(name, md = :MD5)
352
 
    unless cert = Puppet::SSL::Certificate.indirection.find(name) || Puppet::SSL::CertificateRequest.indirection.find(name)
353
 
      raise ArgumentError, "Could not find a certificate or csr for #{name}"
354
 
    end
355
 
    cert.fingerprint(md)
356
 
  end
357
 
 
358
 
  # List the waiting certificate requests.
359
 
  def waiting?
360
 
    Puppet::SSL::CertificateRequest.indirection.search("*").collect { |r| r.name }
361
 
  end
362
 
end