summaryrefslogtreecommitdiffstats
path: root/lib/puppet/sslcertificates/support.rb
blob: c8d2e846b483520658bccfc1f83e287a3c8fa1b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
require 'puppet/sslcertificates'

# A module to handle reading of certificates.
module Puppet::SSLCertificates::Support
    class MissingCertificate < Puppet::Error; end
    class InvalidCertificate < Puppet::Error; end

    attr_reader :cacert

    # Some metaprogramming to create methods for retrieving and creating keys.
    # This probably isn't fewer lines than defining each separately...
    def self.keytype(name, options, &block)
        var = "@%s" % name

        maker = "mk_%s" % name
        reader = "read_%s" % name

        unless param = options[:param]
            raise ArgumentError, "You must specify the parameter for the key"
        end

        unless klass = options[:class]
            raise ArgumentError, "You must specify the class for the key"
        end

        # Define the method that creates it.
        define_method(maker, &block)

        # Define the reading method.
        define_method(reader) do
            return nil unless FileTest.exists?(Puppet[param])
            begin
                instance_variable_set(var,
                    klass.new(File.read(Puppet[param])))
            rescue => detail
                raise InvalidCertificate, "Could not read %s: %s" %
                    [param, detail]
            end
        end

        # Define the overall method, which just calls the reader and maker
        # as appropriate.
        define_method(name) do
            unless instance_variable_get(var)
                unless cert = send(reader)
                    cert = send(maker)
                    Puppet.settings.write(param) { |f| f.puts cert.to_pem }
                end
                instance_variable_set(var, cert)
            end
            instance_variable_get(var)
        end
    end

    # The key pair.
    keytype :key, :param => :hostprivkey, :class => OpenSSL::PKey::RSA do
        Puppet.info "Creating a new SSL key at %s" % Puppet[:hostprivkey]
        key = OpenSSL::PKey::RSA.new(Puppet[:keylength])

        # Our key meta programming can only handle one file, so we have
        # to separately write out the public key.
        Puppet.settings.write(:hostpubkey) do |f|
            f.print key.public_key.to_pem
        end
        return key
    end

    # Our certificate request
    keytype :csr, :param => :hostcsr, :class => OpenSSL::X509::Request do
        Puppet.info "Creating a new certificate request for %s" %
            Puppet[:certname]

        csr = OpenSSL::X509::Request.new
        csr.version = 0
        csr.subject = OpenSSL::X509::Name.new([["CN", Puppet[:certname]]])
        csr.public_key = key.public_key
        csr.sign(key, OpenSSL::Digest::MD5.new)

        return csr
    end

    keytype :cert, :param => :hostcert, :class => OpenSSL::X509::Certificate do
        raise MissingCertificate, "No host certificate"
    end

    keytype :ca_cert, :param => :localcacert, :class => OpenSSL::X509::Certificate do
        raise MissingCertificate, "No CA certificate"
    end

    # Request a certificate from the remote system.  This does all of the work
    # of creating the cert request, contacting the remote system, and
    # storing the cert locally.
    def requestcert
        begin
            cert, cacert = caclient.getcert(@csr.to_pem)
        rescue => detail
            if Puppet[:trace]
                puts detail.backtrace
            end
            raise Puppet::Error.new("Certificate retrieval failed: %s" %
                detail)
        end

        if cert.nil? or cert == ""
            return nil
        end
        Puppet.settings.write(:hostcert) do |f| f.print cert end
        Puppet.settings.write(:localcacert) do |f| f.print cacert end
        #File.open(@certfile, "w", 0644) { |f| f.print cert }
        #File.open(@cacertfile, "w", 0644) { |f| f.print cacert }
        begin
            @cert = OpenSSL::X509::Certificate.new(cert)
            @cacert = OpenSSL::X509::Certificate.new(cacert)
            retrieved = true
        rescue => detail
            raise Puppet::Error.new(
                "Invalid certificate: %s" % detail
            )
        end

        unless @cert.check_private_key(@key)
            raise Puppet::DevError, "Received invalid certificate"
        end
        return retrieved
    end
end

# $Id$