~lynxman/ubuntu/precise/puppet/puppetlabsfixbug12844

« back to all changes in this revision

Viewing changes to .pc/CVE-2011-3872.patch/lib/puppet/ssl/host.rb

  • Committer: Bazaar Package Importer
  • Author(s): Marc Deslauriers
  • Date: 2011-10-24 15:05:12 UTC
  • Revision ID: james.westby@ubuntu.com-20111024150512-yxqwfdp6hcs6of5l
Tags: 2.7.1-1ubuntu3.2
* SECURITY UPDATE: puppet master impersonation via incorrect certificates
  - debian/patches/CVE-2011-3872.patch: refactor certificate handling.
  - Thanks to upstream for providing the patch.
  - CVE-2011-3872

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'puppet/indirector'
 
2
require 'puppet/ssl'
 
3
require 'puppet/ssl/key'
 
4
require 'puppet/ssl/certificate'
 
5
require 'puppet/ssl/certificate_request'
 
6
require 'puppet/ssl/certificate_revocation_list'
 
7
require 'puppet/util/cacher'
 
8
 
 
9
# The class that manages all aspects of our SSL certificates --
 
10
# private keys, public keys, requests, etc.
 
11
class Puppet::SSL::Host
 
12
  # Yay, ruby's strange constant lookups.
 
13
  Key = Puppet::SSL::Key
 
14
  CA_NAME = Puppet::SSL::CA_NAME
 
15
  Certificate = Puppet::SSL::Certificate
 
16
  CertificateRequest = Puppet::SSL::CertificateRequest
 
17
  CertificateRevocationList = Puppet::SSL::CertificateRevocationList
 
18
 
 
19
  extend Puppet::Indirector
 
20
  indirects :certificate_status, :terminus_class => :file
 
21
 
 
22
  attr_reader :name
 
23
  attr_accessor :ca
 
24
 
 
25
  attr_writer :key, :certificate, :certificate_request
 
26
 
 
27
  # This accessor is used in instances for indirector requests to hold desired state
 
28
  attr_accessor :desired_state
 
29
 
 
30
  class << self
 
31
    include Puppet::Util::Cacher
 
32
 
 
33
    cached_attr(:localhost) do
 
34
      result = new
 
35
      result.generate unless result.certificate
 
36
      result.key # Make sure it's read in
 
37
      result
 
38
    end
 
39
  end
 
40
 
 
41
  # This is the constant that people will use to mark that a given host is
 
42
  # a certificate authority.
 
43
  def self.ca_name
 
44
    CA_NAME
 
45
  end
 
46
 
 
47
  class << self
 
48
    attr_reader :ca_location
 
49
  end
 
50
 
 
51
  # Configure how our various classes interact with their various terminuses.
 
52
  def self.configure_indirection(terminus, cache = nil)
 
53
    Certificate.indirection.terminus_class = terminus
 
54
    CertificateRequest.indirection.terminus_class = terminus
 
55
    CertificateRevocationList.indirection.terminus_class = terminus
 
56
 
 
57
    host_map = {:ca => :file, :file => nil, :rest => :rest}
 
58
    if term = host_map[terminus]
 
59
      self.indirection.terminus_class = term
 
60
    else
 
61
      self.indirection.reset_terminus_class
 
62
    end
 
63
 
 
64
    if cache
 
65
      # This is weird; we don't actually cache our keys, we
 
66
      # use what would otherwise be the cache as our normal
 
67
      # terminus.
 
68
      Key.indirection.terminus_class = cache
 
69
    else
 
70
      Key.indirection.terminus_class = terminus
 
71
    end
 
72
 
 
73
    if cache
 
74
      Certificate.indirection.cache_class = cache
 
75
      CertificateRequest.indirection.cache_class = cache
 
76
      CertificateRevocationList.indirection.cache_class = cache
 
77
    else
 
78
      # Make sure we have no cache configured.  puppet master
 
79
      # switches the configurations around a bit, so it's important
 
80
      # that we specify the configs for absolutely everything, every
 
81
      # time.
 
82
      Certificate.indirection.cache_class = nil
 
83
      CertificateRequest.indirection.cache_class = nil
 
84
      CertificateRevocationList.indirection.cache_class = nil
 
85
    end
 
86
  end
 
87
 
 
88
  CA_MODES = {
 
89
    # Our ca is local, so we use it as the ultimate source of information
 
90
    # And we cache files locally.
 
91
    :local => [:ca, :file],
 
92
    # We're a remote CA client.
 
93
    :remote => [:rest, :file],
 
94
    # We are the CA, so we don't have read/write access to the normal certificates.
 
95
    :only => [:ca],
 
96
    # We have no CA, so we just look in the local file store.
 
97
    :none => [:file]
 
98
  }
 
99
 
 
100
  # Specify how we expect to interact with our certificate authority.
 
101
  def self.ca_location=(mode)
 
102
    modes = CA_MODES.collect { |m, vals| m.to_s }.join(", ")
 
103
    raise ArgumentError, "CA Mode can only be one of: #{modes}" unless CA_MODES.include?(mode)
 
104
 
 
105
    @ca_location = mode
 
106
 
 
107
    configure_indirection(*CA_MODES[@ca_location])
 
108
  end
 
109
 
 
110
  # Puppet::SSL::Host is actually indirected now so the original implementation
 
111
  # has been moved into the certificate_status indirector.  This method is in-use
 
112
  # in `puppet cert -c <certname>`.
 
113
  def self.destroy(name)
 
114
    indirection.destroy(name)
 
115
  end
 
116
 
 
117
  def self.from_pson(pson)
 
118
    instance = new(pson["name"])
 
119
    if pson["desired_state"]
 
120
      instance.desired_state = pson["desired_state"]
 
121
    end
 
122
    instance
 
123
  end
 
124
 
 
125
  # Puppet::SSL::Host is actually indirected now so the original implementation
 
126
  # has been moved into the certificate_status indirector.  This method does not
 
127
  # appear to be in use in `puppet cert -l`.
 
128
  def self.search(options = {})
 
129
    indirection.search("*", options)
 
130
  end
 
131
 
 
132
  # Is this a ca host, meaning that all of its files go in the CA location?
 
133
  def ca?
 
134
    ca
 
135
  end
 
136
 
 
137
  def key
 
138
    @key ||= Key.indirection.find(name)
 
139
  end
 
140
 
 
141
  # This is the private key; we can create it from scratch
 
142
  # with no inputs.
 
143
  def generate_key
 
144
    @key = Key.new(name)
 
145
    @key.generate
 
146
    begin
 
147
      Key.indirection.save(@key)
 
148
    rescue
 
149
      @key = nil
 
150
      raise
 
151
    end
 
152
    true
 
153
  end
 
154
 
 
155
  def certificate_request
 
156
    @certificate_request ||= CertificateRequest.indirection.find(name)
 
157
  end
 
158
 
 
159
  # Our certificate request requires the key but that's all.
 
160
  def generate_certificate_request
 
161
    generate_key unless key
 
162
    @certificate_request = CertificateRequest.new(name)
 
163
    @certificate_request.generate(key.content)
 
164
    begin
 
165
      CertificateRequest.indirection.save(@certificate_request)
 
166
    rescue
 
167
      @certificate_request = nil
 
168
      raise
 
169
    end
 
170
 
 
171
    true
 
172
  end
 
173
 
 
174
  def certificate
 
175
    unless @certificate
 
176
      generate_key unless key
 
177
 
 
178
      # get the CA cert first, since it's required for the normal cert
 
179
      # to be of any use.
 
180
      return nil unless Certificate.indirection.find("ca") unless ca?
 
181
      return nil unless @certificate = Certificate.indirection.find(name)
 
182
 
 
183
      unless certificate_matches_key?
 
184
        raise Puppet::Error, "Retrieved certificate does not match private key; please remove certificate from server and regenerate it with the current key"
 
185
      end
 
186
    end
 
187
    @certificate
 
188
  end
 
189
 
 
190
  def certificate_matches_key?
 
191
    return false unless key
 
192
    return false unless certificate
 
193
 
 
194
    certificate.content.check_private_key(key.content)
 
195
  end
 
196
 
 
197
  # Generate all necessary parts of our ssl host.
 
198
  def generate
 
199
    generate_key unless key
 
200
    generate_certificate_request unless certificate_request
 
201
 
 
202
    # If we can get a CA instance, then we're a valid CA, and we
 
203
    # should use it to sign our request; else, just try to read
 
204
    # the cert.
 
205
    if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance
 
206
      ca.sign(self.name)
 
207
    end
 
208
  end
 
209
 
 
210
  def initialize(name = nil)
 
211
    @name = (name || Puppet[:certname]).downcase
 
212
    @key = @certificate = @certificate_request = nil
 
213
    @ca = (name == self.class.ca_name)
 
214
  end
 
215
 
 
216
  # Extract the public key from the private key.
 
217
  def public_key
 
218
    key.content.public_key
 
219
  end
 
220
 
 
221
  # Create/return a store that uses our SSL info to validate
 
222
  # connections.
 
223
  def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY)
 
224
    unless @ssl_store
 
225
      @ssl_store = OpenSSL::X509::Store.new
 
226
      @ssl_store.purpose = purpose
 
227
 
 
228
      # Use the file path here, because we don't want to cause
 
229
      # a lookup in the middle of setting our ssl connection.
 
230
      @ssl_store.add_file(Puppet[:localcacert])
 
231
 
 
232
      # If there's a CRL, add it to our store.
 
233
      if crl = Puppet::SSL::CertificateRevocationList.indirection.find(CA_NAME)
 
234
        @ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation]
 
235
        @ssl_store.add_crl(crl.content)
 
236
      end
 
237
      return @ssl_store
 
238
    end
 
239
    @ssl_store
 
240
  end
 
241
 
 
242
  def to_pson(*args)
 
243
    my_cert = Puppet::SSL::Certificate.indirection.find(name)
 
244
    pson_hash = { :name  => name }
 
245
 
 
246
    my_state = state
 
247
 
 
248
    pson_hash[:state] = my_state
 
249
    pson_hash[:desired_state] = desired_state if desired_state
 
250
 
 
251
    if my_state == 'requested'
 
252
      pson_hash[:fingerprint] = certificate_request.fingerprint
 
253
    else
 
254
      pson_hash[:fingerprint] = my_cert.fingerprint
 
255
    end
 
256
 
 
257
    pson_hash.to_pson(*args)
 
258
  end
 
259
 
 
260
  # Attempt to retrieve a cert, if we don't already have one.
 
261
  def wait_for_cert(time)
 
262
    begin
 
263
      return if certificate
 
264
      generate
 
265
      return if certificate
 
266
    rescue SystemExit,NoMemoryError
 
267
      raise
 
268
    rescue Exception => detail
 
269
      puts detail.backtrace if Puppet[:trace]
 
270
      Puppet.err "Could not request certificate: #{detail}"
 
271
      if time < 1
 
272
        puts "Exiting; failed to retrieve certificate and waitforcert is disabled"
 
273
        exit(1)
 
274
      else
 
275
        sleep(time)
 
276
      end
 
277
      retry
 
278
    end
 
279
 
 
280
    if time < 1
 
281
      puts "Exiting; no certificate found and waitforcert is disabled"
 
282
      exit(1)
 
283
    end
 
284
 
 
285
    while true
 
286
      sleep time
 
287
      begin
 
288
        break if certificate
 
289
        Puppet.notice "Did not receive certificate"
 
290
      rescue StandardError => detail
 
291
        puts detail.backtrace if Puppet[:trace]
 
292
        Puppet.err "Could not request certificate: #{detail}"
 
293
      end
 
294
    end
 
295
  end
 
296
 
 
297
  def state
 
298
    my_cert = Puppet::SSL::Certificate.indirection.find(name)
 
299
    if certificate_request
 
300
      return 'requested'
 
301
    end
 
302
 
 
303
    begin
 
304
      Puppet::SSL::CertificateAuthority.new.verify(my_cert)
 
305
      return 'signed'
 
306
    rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError
 
307
      return 'revoked'
 
308
    end
 
309
  end
 
310
end
 
311
 
 
312
require 'puppet/ssl/certificate_authority'