~ubuntu-branches/ubuntu/lucid/puppet/lucid-security

« back to all changes in this revision

Viewing changes to lib/puppet/ssl/certificate_authority.rb

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2009-12-23 00:48:10 UTC
  • mfrom: (1.1.10 upstream) (3.1.7 squeeze)
  • Revision ID: james.westby@ubuntu.com-20091223004810-3i4oryds922g5n59
Tags: 0.25.1-3ubuntu1
* Merge from debian testing.  Remaining changes:
  - debian/rules:
    + Don't start puppet when first installing puppet.
  - debian/puppet.conf, lib/puppet/defaults.rb:
    + Move templates to /etc/puppet
  - lib/puppet/defaults.rb:
    + Fix /var/lib/puppet/state ownership.
  - man/man8/puppet.conf.8: 
    + Fix broken URL in manpage.
  - debian/control:
    + Update maintainer accordint to spec.
    + Puppetmaster Recommends -> Suggests
    + Created puppet-testsuite as a seperate. Allow the users to run puppet's 
      testsuite.
  - tests/Rakefile: Fix rakefile so that the testsuite can acutally be ran.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'puppet/ssl/host'
 
2
require 'puppet/ssl/certificate_request'
 
3
require 'puppet/util/cacher'
 
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
    require 'puppet/ssl/certificate_factory'
 
15
    require 'puppet/ssl/inventory'
 
16
    require 'puppet/ssl/certificate_revocation_list'
 
17
 
 
18
    require 'puppet/ssl/certificate_authority/interface'
 
19
 
 
20
    class CertificateVerificationError < RuntimeError
 
21
        attr_accessor :error_code
 
22
 
 
23
        def initialize(code)
 
24
            @error_code = code
 
25
        end
 
26
    end
 
27
 
 
28
    class << self
 
29
        include Puppet::Util::Cacher
 
30
 
 
31
        cached_attr(:singleton_instance) { new }
 
32
    end
 
33
 
 
34
    def self.ca?
 
35
        return false unless Puppet[:ca]
 
36
        return false unless Puppet[:name] == "puppetmasterd"
 
37
        return true
 
38
    end
 
39
 
 
40
    # If this process can function as a CA, then return a singleton
 
41
    # instance.
 
42
    def self.instance
 
43
        return nil unless ca?
 
44
 
 
45
        singleton_instance
 
46
    end
 
47
 
 
48
    attr_reader :name, :host
 
49
 
 
50
    # Create and run an applicator.  I wanted to build an interface where you could do
 
51
    # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible.
 
52
    def apply(method, options)
 
53
        unless options[:to]
 
54
            raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all"
 
55
        end
 
56
        applier = Interface.new(method, options[:to])
 
57
 
 
58
        applier.apply(self)
 
59
    end
 
60
 
 
61
    # If autosign is configured, then autosign all CSRs that match our configuration.
 
62
    def autosign
 
63
        return unless auto = autosign?
 
64
 
 
65
        store = nil
 
66
        if auto != true
 
67
            store = autosign_store(auto)
 
68
        end
 
69
 
 
70
        Puppet::SSL::CertificateRequest.search("*").each do |csr|
 
71
            sign(csr.name) if auto == true or store.allowed?(csr.name, "127.1.1.1")
 
72
        end
 
73
    end
 
74
 
 
75
    # Do we autosign?  This returns true, false, or a filename.
 
76
    def autosign?
 
77
        auto = Puppet[:autosign]
 
78
        return false if ['false', false].include?(auto)
 
79
        return true if ['true', true].include?(auto)
 
80
 
 
81
        raise ArgumentError, "The autosign configuration '%s' must be a fully qualified file" % auto unless auto =~ /^\//
 
82
        if FileTest.exist?(auto)
 
83
            return auto
 
84
        else
 
85
            return false
 
86
        end
 
87
    end
 
88
 
 
89
    # Create an AuthStore for autosigning.
 
90
    def autosign_store(file)
 
91
        auth = Puppet::Network::AuthStore.new
 
92
        File.readlines(file).each do |line|
 
93
            next if line =~ /^\s*#/
 
94
            next if line =~ /^\s*$/
 
95
            auth.allow(line.chomp)
 
96
        end
 
97
 
 
98
        auth
 
99
    end
 
100
 
 
101
    # Retrieve (or create, if necessary) the certificate revocation list.
 
102
    def crl
 
103
        unless defined?(@crl)
 
104
            unless @crl = Puppet::SSL::CertificateRevocationList.find("ca")
 
105
                @crl = Puppet::SSL::CertificateRevocationList.new("ca")
 
106
                @crl.generate(host.certificate.content, host.key.content)
 
107
                @crl.save
 
108
            end
 
109
        end
 
110
        @crl
 
111
    end
 
112
 
 
113
    # Delegate this to our Host class.
 
114
    def destroy(name)
 
115
        Puppet::SSL::Host.destroy(name)
 
116
    end
 
117
 
 
118
    # Generate a new certificate.
 
119
    def generate(name)
 
120
        raise ArgumentError, "A Certificate already exists for %s" % name if Puppet::SSL::Certificate.find(name)
 
121
        host = Puppet::SSL::Host.new(name)
 
122
 
 
123
        host.generate_certificate_request
 
124
 
 
125
        sign(name)
 
126
    end
 
127
 
 
128
    # Generate our CA certificate.
 
129
    def generate_ca_certificate
 
130
        generate_password unless password?
 
131
 
 
132
        host.generate_key unless host.key
 
133
 
 
134
        # Create a new cert request.  We do this
 
135
        # specially, because we don't want to actually
 
136
        # save the request anywhere.
 
137
        request = Puppet::SSL::CertificateRequest.new(host.name)
 
138
        request.generate(host.key)
 
139
 
 
140
        # Create a self-signed certificate.
 
141
        @certificate = sign(host.name, :ca, request)
 
142
 
 
143
        # And make sure we initialize our CRL.
 
144
        crl()
 
145
    end
 
146
 
 
147
    def initialize
 
148
        Puppet.settings.use :main, :ssl, :ca
 
149
 
 
150
        @name = Puppet[:certname]
 
151
 
 
152
        @host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name)
 
153
 
 
154
        setup()
 
155
    end
 
156
 
 
157
    # Retrieve (or create, if necessary) our inventory manager.
 
158
    def inventory
 
159
        unless defined?(@inventory)
 
160
            @inventory = Puppet::SSL::Inventory.new
 
161
        end
 
162
        @inventory
 
163
    end
 
164
 
 
165
    # Generate a new password for the CA.
 
166
    def generate_password
 
167
        pass = ""
 
168
        20.times { pass += (rand(74) + 48).chr }
 
169
 
 
170
        begin
 
171
            Puppet.settings.write(:capass) { |f| f.print pass }
 
172
        rescue Errno::EACCES => detail
 
173
            raise Puppet::Error, "Could not write CA password: %s" % detail.to_s
 
174
        end
 
175
 
 
176
        @password = pass
 
177
 
 
178
        return pass
 
179
    end
 
180
 
 
181
    # List all signed certificates.
 
182
    def list
 
183
        Puppet::SSL::Certificate.search("*").collect { |c| c.name }
 
184
    end
 
185
 
 
186
    # Read the next serial from the serial file, and increment the
 
187
    # file so this one is considered used.
 
188
    def next_serial
 
189
        serial = nil
 
190
 
 
191
        # This is slightly odd.  If the file doesn't exist, our readwritelock creates
 
192
        # it, but with a mode we can't actually read in some cases.  So, use
 
193
        # a default before the lock.
 
194
        unless FileTest.exist?(Puppet[:serial])
 
195
            serial = 0x1
 
196
        end
 
197
 
 
198
        Puppet.settings.readwritelock(:serial) { |f|
 
199
            if FileTest.exist?(Puppet[:serial])
 
200
                serial ||= File.read(Puppet.settings[:serial]).chomp.hex
 
201
            end
 
202
 
 
203
            # We store the next valid serial, not the one we just used.
 
204
            f << "%04X" % (serial + 1)
 
205
        }
 
206
 
 
207
        return serial
 
208
    end
 
209
 
 
210
    # Does the password file exist?
 
211
    def password?
 
212
        FileTest.exist? Puppet[:capass]
 
213
    end
 
214
 
 
215
    # Print a given host's certificate as text.
 
216
    def print(name)
 
217
        if cert = Puppet::SSL::Certificate.find(name)
 
218
            return cert.to_text
 
219
        else
 
220
            return nil
 
221
        end
 
222
    end
 
223
 
 
224
    # Revoke a given certificate.
 
225
    def revoke(name)
 
226
        raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl
 
227
 
 
228
        if cert = Puppet::SSL::Certificate.find(name)
 
229
            serial = cert.content.serial
 
230
        elsif ! serial = inventory.serial(name)
 
231
            raise ArgumentError, "Could not find a serial number for %s" % name
 
232
        end
 
233
        crl.revoke(serial, host.key.content)
 
234
    end
 
235
 
 
236
    # This initializes our CA so it actually works.  This should be a private
 
237
    # method, except that you can't any-instance stub private methods, which is
 
238
    # *awesome*.  This method only really exists to provide a stub-point during
 
239
    # testing.
 
240
    def setup
 
241
        generate_ca_certificate unless @host.certificate
 
242
    end
 
243
 
 
244
    # Sign a given certificate request.
 
245
    def sign(hostname, cert_type = :server, self_signing_csr = nil)
 
246
        # This is a self-signed certificate
 
247
        if self_signing_csr
 
248
            csr = self_signing_csr
 
249
            issuer = csr.content
 
250
        else
 
251
            unless csr = Puppet::SSL::CertificateRequest.find(hostname)
 
252
                raise ArgumentError, "Could not find certificate request for %s" % hostname
 
253
            end
 
254
            issuer = host.certificate.content
 
255
        end
 
256
 
 
257
        cert = Puppet::SSL::Certificate.new(hostname)
 
258
        cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result
 
259
        cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new)
 
260
 
 
261
        Puppet.notice "Signed certificate request for %s" % hostname
 
262
 
 
263
        # Add the cert to the inventory before we save it, since
 
264
        # otherwise we could end up with it being duplicated, if
 
265
        # this is the first time we build the inventory file.
 
266
        inventory.add(cert)
 
267
 
 
268
        # Save the now-signed cert.  This should get routed correctly depending
 
269
        # on the certificate type.
 
270
        cert.save
 
271
 
 
272
        # And remove the CSR if this wasn't self signed.
 
273
        Puppet::SSL::CertificateRequest.destroy(csr.name) unless self_signing_csr
 
274
 
 
275
        return cert
 
276
    end
 
277
 
 
278
    # Verify a given host's certificate.
 
279
    def verify(name)
 
280
        unless cert = Puppet::SSL::Certificate.find(name)
 
281
            raise ArgumentError, "Could not find a certificate for %s" % name
 
282
        end
 
283
        store = OpenSSL::X509::Store.new
 
284
        store.add_file Puppet[:cacert]
 
285
        store.add_crl crl.content if self.crl
 
286
        store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
 
287
        store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK
 
288
 
 
289
        unless store.verify(cert.content)
 
290
            raise CertificateVerificationError.new(store.error), store.error_string
 
291
        end
 
292
    end
 
293
 
 
294
    # List the waiting certificate requests.
 
295
    def waiting?
 
296
        Puppet::SSL::CertificateRequest.search("*").collect { |r| r.name }
 
297
    end
 
298
end