summaryrefslogtreecommitdiffstats
path: root/lib/puppet/server/authstore.rb
blob: a0a3c13ec1adb13628ea01a107066594aeee7620 (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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# standard module for determining whether a given hostname or IP has access to
# the requested resource

require 'ipaddr'

module Puppet
class Server
    class AuthStoreError < Puppet::Error; end
    class AuthorizationError < Puppet::Error; end

    class AuthStore
        ORDER = {
            :ip => [:ip],
            :name => [:hostname, :domain]
        }

        Puppet::Util.logmethods(self, false)

        def allow(pattern)
            # a simple way to allow anyone at all to connect
            if pattern == "*"
                @globalallow = true
            else
                store(pattern, @allow)
            end
        end

        def allowed?(name, ip)
            if name or ip
                unless name and ip
                    raise Puppet::DevError, "Name and IP must be passed to 'allowed?'"
                end
                # else, we're networked and such
            else
                # we're local
                return true
            end

            # yay insecure overrides
            if @globalallow
                return true
            end

            value = nil
            ORDER.each { |nametype, array|
                if nametype == :ip
                    value = IPAddr.new(ip)
                else
                    value = name.split(".").reverse
                end

                array.each { |type|
                    [[@deny, false], [@allow, true]].each { |ary|
                        hash, retval = ary
                        if hash.include?(type)
                            hash[type].each { |pattern|
                                if match?(nametype, value, pattern)
                                    return retval
                                end
                            }
                        end
                    }
                }
            }

            self.info "defaulting to no access for %s" % name
            # default to false
            return false
        end

        def deny(pattern)
            store(pattern, @deny)
        end

        def initialize
            @globalallow = nil
            @allow = Hash.new { |hash, key|
                hash[key] = []
            }
            @deny = Hash.new { |hash, key|
                hash[key] = []
            }
        end

        private

        def match?(nametype, value, pattern)
            if value == pattern # simplest shortcut
                return true
            end

            case nametype
            when :ip: matchip?(value, pattern)
            when :name: matchname?(value, pattern)
            else
                raise Puppet::DevError, "Invalid match type %s" % nametype
            end
        end

        def matchip?(value, pattern)
            # we're just using builtin stuff for this, thankfully
            if pattern.include?(value)
                return true
            else
                return false
            end
        end

        def matchname?(value, pattern)
            # yay, horribly inefficient
            if pattern[-1] != '*' # the pattern has no metachars and is not equal
                                    # thus, no match
                #Puppet.info "%s is not equal with no * in %s" % [value, pattern]
                return false
            else
                # we know the last field of the pattern is '*'
                # if everything up to that doesn't match, we're definitely false
                if pattern[0..-2] != value[0..pattern.length-2]
                    #Puppet.notice "subpatterns didn't match; %s vs %s" %
                    #    [pattern[0..-2], value[0..pattern.length-2]]
                    return false
                end

                case value.length <=> pattern.length
                when -1: # value is shorter than pattern
                    if pattern.length - value.length == 1
                        # only ever allowed when the value is the domain of a
                        # splatted pattern
                        #Puppet.info "allowing splatted domain %s" % [value]
                        return true
                    else
                        return false
                    end
                when 0: # value is the same length as pattern
                    if pattern[-1] == "*"
                        #Puppet.notice "same length with *"
                        return true
                    else
                        return false
                    end
                when 1: # value is longer than pattern
                    # at this point we've already verified that everything up to
                    # the '*' in the pattern matches, so we are true
                    return true
                end
            end
        end

        def store(pattern, hash)
            type, value = type(pattern)

            if type and value
                # this won't work once we get beyond simple stuff...
                hash[type] << value
            else
                raise AuthStoreError, "Invalid pattern %s" % pattern
            end
        end

        def type(pattern)
            type = value = nil
            case pattern
            when /^(\d+\.){3}\d+$/:
                type = :ip
                begin
                    value = IPAddr.new(pattern)
                rescue ArgumentError => detail
                    raise AuthStoreError, "Invalid IP address pattern %s" % pattern
                end
            when /^(\d+\.){3}\d+\/(\d+)$/:
                mask = Integer($2)
                if mask < 1 or mask > 32
                    raise AuthStoreError, "Invalid IP mask %s" % mask
                end
                type = :ip
                begin
                    value = IPAddr.new(pattern)
                rescue ArgumentError => detail
                    raise AuthStoreError, "Invalid IP address pattern %s" % pattern
                end
            when /^(\d+\.){1,3}\*$/: # an ip address with a '*' at the end
                type = :ip
                match = $1
                match.sub!(".", '')
                ary = pattern.split(".")

                mask = case ary.index(match)
                when 0: 8
                when 1: 16
                when 2: 24
                else
                    raise AuthStoreError, "Invalid IP pattern %s" % pattern
                end

                ary.pop
                while ary.length < 4
                    ary.push("0")
                end

                begin
                    value = IPAddr.new(ary.join(".") + "/" + mask.to_s)
                rescue ArgumentError => detail
                    raise AuthStoreError, "Invalid IP address pattern %s" % pattern
                end
            when /^[\d.]+$/: # necessary so incomplete IP addresses can't look
                             # like hostnames
                raise AuthStoreError, "Invalid IP address pattern %s" % pattern
            when /^([a-zA-Z][-\w]*\.)+[-\w]+$/: # a full hostname
                type = :hostname
                value = pattern.split(".").reverse
            when /^\*\.([a-zA-Z][-\w]*\.)+[-\w]+$/: # this doesn't match TLDs
                type = :domain
                value = pattern.split(".").reverse
            else
                raise AuthStoreError, "Invalid pattern %s" % pattern
            end

            return [type, value]
        end
    end
end
end
#
# $Id$