summaryrefslogtreecommitdiffstats
path: root/lib/puppet/ca.rb
blob: 9a6f8aeda098c2a92fc38f06bda8741488bcbff9 (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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
require 'openssl'
require 'puppet'
require 'puppet/sslcertificates'
require 'xmlrpc/server'

# Much of this was taken from QuickCert:
#   http://segment7.net/projects/ruby/QuickCert/

module Puppet
    class CAError < Puppet::Error; end
    class CA
        attr_reader :ca

        def self.interface
            XMLRPC::Service::Interface.new("puppetca") { |iface|
                iface.add_method("array getcert(csr)")
            }
        end

        def autosign?(hostname)
            # simple values are easy
            asign = Puppet[:autosign]
            if asign == true or asign == false
                return asign
            end

            # we only otherwise know how to handle files
            unless asign =~ /^\//
                raise Puppet::Error, "Invalid autosign value %s" %
                    asign
            end

            unless FileTest.exists?(asign)
                Puppet.warning "Autosign is enabled but %s is missing" % asign
                return false
            end
            File.open(asign) { |f|
                f.each { |line|
                    line.chomp!
                    if line =~ /^[.\w-]+$/ and line == hostname
                        Puppet.info "%s exactly matched %s" % [hostname, line]
                        return true
                    else
                        begin
                            rx = Regexp.new(line)
                        rescue => detail
                            Puppet.err(
                                "Could not create regexp out of autosign line %s: %s" %
                                [line, detail]
                            )
                            next
                        end

                        if hostname =~ rx
                            Puppet.info "%s matched %s" % [hostname, line]
                            return true
                        end
                    end
                }
            }

            return false
        end

        def initialize(hash = {})
            @ca = Puppet::SSLCertificates::CA.new()
        end

        # our client sends us a csr, and we either store it for later signing,
        # or we sign it right away
        def getcert(csrtext, request = nil)
            # okay, i need to retrieve the hostname from the csr, and then
            # verify that i get the same hostname through reverse lookup or
            # something

            Puppet.info "Someone's trying for a cert"
            csr = OpenSSL::X509::Request.new(csrtext)

            subject = csr.subject

            nameary = subject.to_a.find { |ary|
                ary[0] == "CN"
            }

            if nameary.nil?
                Puppet.err "Invalid certificate request"
                return "invalid"
            end

            hostname = nameary[1]

            unless @ca
                Puppet.notice "Host %s asked for signing from non-CA master" % hostname
                return ""
            end

            # okay, we're now going to store the public key if we don't already
            # have it
            public_key = csr.public_key
            unless FileTest.directory?(Puppet[:publickeydir])
                Puppet.recmkdir(Puppet[:publickeydir])
            end
            pkeyfile = File.join(Puppet[:publickeydir], [hostname, "pem"].join('.'))

            if FileTest.exists?(pkeyfile)
                currentkey = File.open(pkeyfile) { |k| k.read }
                unless currentkey == public_key.to_s
                    raise Puppet::Error, "public keys for %s differ" % hostname
                end
            else
                File.open(pkeyfile, "w", 0644) { |f|
                    f.print public_key.to_s
                }
            end
            unless FileTest.directory?(Puppet[:certdir])
                Puppet.recmkdir(Puppet[:certdir], 0770)
            end
            certfile = File.join(Puppet[:certdir], [hostname, "pem"].join("."))

            #puts hostname
            #puts certfile

            unless FileTest.directory?(Puppet[:csrdir])
                Puppet.recmkdir(Puppet[:csrdir], 0770)
            end
            # first check to see if we already have a signed cert for the host
            cert, cacert = ca.getclientcert(hostname)
            if cert and cacert
                Puppet.info "Retrieving existing certificate for %s" % hostname
                Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class]
                return [cert.to_pem, cacert.to_pem]
            elsif @ca
                if self.autosign?(hostname)
                    # okay, we don't have a signed cert
                    # if we're a CA and autosign is turned on, then go ahead and sign
                    # the csr and return the results
                    Puppet.info "Signing certificate for %s" % hostname
                    cert, cacert = @ca.sign(csr)
                    Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class]
                    return [cert.to_pem, cacert.to_pem]
                else # just write out the csr for later signing
                    if @ca.getclientcsr(hostname)
                        Puppet.info "Not replacing existing request from %s" % hostname
                    else
                        Puppet.info "Storing certificate request for %s" % hostname
                        @ca.storeclientcsr(csr)
                    end
                    return ["", ""]
                end
            else
                raise "huh?"
            end
        end
    end
end