summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network/authconfig.rb
blob: b058012f5a225093b43e3255e101c5717b625609 (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
require 'puppet/util/loadedfile'
require 'puppet/network/rights'

module Puppet
    class ConfigurationError < Puppet::Error; end
    class Network::AuthConfig < Puppet::Util::LoadedFile

        def self.main
            @main ||= self.new()
            @main
        end

        # Just proxy the setting methods to our rights stuff
        [:allow, :deny].each do |method|
            define_method(method) do |*args|
                @rights.send(method, *args)
            end
        end

        # Here we add a little bit of semantics.  They can set auth on a whole
        # namespace or on just a single method in the namespace.
        def allowed?(request)
            name        = request.call.intern
            namespace   = request.handler.intern
            method      = request.method.intern

            read()

            if @rights.include?(name)
                return @rights[name].allowed?(request.name, request.ip)
            elsif @rights.include?(namespace)
                return @rights[namespace].allowed?(request.name, request.ip)
            end
            false
        end

        # Does the file exist?  Puppetmasterd does not require it, but
        # puppet agent does.
        def exists?
            FileTest.exists?(@file)
        end

        def initialize(file = nil, parsenow = true)
            @file = file || Puppet[:authconfig]

            raise Puppet::DevError, "No authconfig file defined" unless @file
            return unless self.exists?
            super(@file)
            @rights = Puppet::Network::Rights.new
            @configstamp = @configstatted = nil
            @configtimeout = 60

            read() if parsenow
        end

        # Read the configuration file.
        def read
            return unless FileTest.exists?(@file)

            if @configstamp
                if @configtimeout and @configstatted
                    if Time.now - @configstatted > @configtimeout
                        @configstatted = Time.now
                        tmp = File.stat(@file).ctime

                        if tmp == @configstamp
                            return
                        else
                            Puppet.notice "#{tmp} vs #{@configstamp}"
                        end
                    else
                        return
                    end
                else
                    Puppet.notice "#{@configtimeout} and #{@configstatted}"
                end
            end

            parse()

            @configstamp = File.stat(@file).ctime
            @configstatted = Time.now
        end

        private

        def parse
            newrights = Puppet::Network::Rights.new
            begin
                File.open(@file) { |f|
                    right = nil
                    count = 1
                    f.each { |line|
                        case line
                        when /^\s*#/ # skip comments
                            count += 1
                            next
                        when /^\s*$/  # skip blank lines
                            count += 1
                            next
                        when /^(?:(\[[\w.]+\])|(path)\s+((?:~\s+)?[^ ]+))\s*$/ # "namespace" or "namespace.method" or "path /path" or "path ~ regex"
                            name = $1
                            name = $3 if $2 == "path"
                            name.chomp!
                            right = newrights.newright(name, count, @file)
                        when /^\s*(allow|deny|method|environment|auth(?:enticated)?)\s+(.+)$/
                            parse_right_directive(right, $1, $2, count)
                        else
                            raise ConfigurationError, "Invalid line #{count}: #{line}"
                        end
                        count += 1
                    }
                }
            rescue Errno::EACCES => detail
                Puppet.err "Configuration error: Cannot read #{@file}; cannot serve"
                #raise Puppet::Error, "Cannot read #{@config}"
            rescue Errno::ENOENT => detail
                Puppet.err "Configuration error: '#{@file}' does not exit; cannot serve"
                #raise Puppet::Error, "#{@config} does not exit"
            #rescue FileServerError => detail
            #    Puppet.err "FileServer error: #{detail}"
            end

            # Verify each of the rights are valid.
            # We let the check raise an error, so that it can raise an error
            # pointing to the specific problem.
            newrights.each { |name, right|
                right.valid?
            }
            @rights = newrights
        end

        def parse_right_directive(right, var, value, count)
            case var
            when "allow"
                modify_right(right, :allow, value, "allowing %s access", count)
            when "deny"
                modify_right(right, :deny, value, "denying %s access", count)
            when "method"
                unless right.acl_type == :regex
                    raise ConfigurationError, "'method' directive not allowed in namespace ACL at line #{count} of #{@config}"
                end
                modify_right(right, :restrict_method, value, "allowing 'method' %s", count)
            when "environment"
                unless right.acl_type == :regex
                    raise ConfigurationError, "'environment' directive not allowed in namespace ACL at line #{count} of #{@config}"
                end
                modify_right(right, :restrict_environment, value, "adding environment %s", count)
            when /auth(?:enticated)?/
                unless right.acl_type == :regex
                    raise ConfigurationError, "'authenticated' directive not allowed in namespace ACL at line #{count} of #{@config}"
                end
                modify_right(right, :restrict_authenticated, value, "adding authentication %s", count)
            else
                raise ConfigurationError,
                    "Invalid argument '#{var}' at line #{count}"
            end
        end

        def modify_right(right, method, value, msg, count)
            value.split(/\s*,\s*/).each do |val|
                begin
                    right.info msg % val
                    right.send(method, val)
                rescue AuthStoreError => detail
                    raise ConfigurationError, "#{detail} at line #{count} of #{@file}"
                end
            end
        end

    end
end