summaryrefslogtreecommitdiffstats
path: root/lib/puppet/sslcertificates/support.rb
blob: e63458b330b7e9e77ffdbef20dc05f7b21eb4c0d (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
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