summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network/authconfig.rb
blob: 4ba89fa71226f90347acf0944285eeb1a4874d46 (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
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
    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