summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network/rights.rb
diff options
context:
space:
mode:
authorBrice Figureau <brice-puppet@daysofwonder.com>2009-03-31 20:29:37 +0200
committerBrice Figureau <brice-puppet@daysofwonder.com>2009-04-23 20:52:02 +0200
commit85233768f080b4cbc4e20eb0c354b6d859a2fb23 (patch)
tree19d32e670fe84cfb53f31adfd63953dd3a04fd5c /lib/puppet/network/rights.rb
parent22b82abcd27834e43426f2758fba5728c146be61 (diff)
downloadpuppet-85233768f080b4cbc4e20eb0c354b6d859a2fb23.tar.gz
puppet-85233768f080b4cbc4e20eb0c354b6d859a2fb23.tar.xz
puppet-85233768f080b4cbc4e20eb0c354b6d859a2fb23.zip
Enhance authconfig format to support uri paths and regex
This patch introduces a new set of directive to the authconfig parser/file format: path /uripath or patch ~ <regex> This directive declares a new kind of ACL based on the uri path. method save, find This directive which is to be used under path directive restricts a path ACL to only some REST verbs. The ACL path system matches on path prefix possible, or on regex matches (first match wins). If no path are matching, then the authorization is not allowed. The same if no ACL matches for the given REST verb. The old namespace right matching still works as usual. Signed-off-by: Brice Figureau <brice-puppet@daysofwonder.com>
Diffstat (limited to 'lib/puppet/network/rights.rb')
-rwxr-xr-xlib/puppet/network/rights.rb202
1 files changed, 176 insertions, 26 deletions
diff --git a/lib/puppet/network/rights.rb b/lib/puppet/network/rights.rb
index a4133f22c..6b2082cdb 100755
--- a/lib/puppet/network/rights.rb
+++ b/lib/puppet/network/rights.rb
@@ -1,15 +1,16 @@
-require 'ipaddr'
require 'puppet/network/authstore'
# Define a set of rights and who has access to them.
-class Puppet::Network::Rights < Hash
+# There are two types of rights:
+# * named rights (ie a common string)
+# * path based rights (which are matched on a longest prefix basis)
+class Puppet::Network::Rights
+
# We basically just proxy directly to our rights. Each Right stores
# its own auth abilities.
- [:allow, :allowed?, :deny].each do |method|
+ [:allow, :deny].each do |method|
define_method(method) do |name, *args|
- name = name.intern if name.is_a? String
-
- if obj = right(name)
+ if obj = self[name]
obj.send(method, *args)
else
raise ArgumentError, "Unknown right '%s'" % name
@@ -17,45 +18,115 @@ class Puppet::Network::Rights < Hash
end
end
+ # this method is used to add a new allowed +method+ to +name+
+ # method applies only to path rights
+ def restrict_method(name, *args)
+ if right = self[name]
+ right.restrict_method(*args)
+ else
+ raise ArgumentError, "'%s' right is not allowing method specification" % name
+ end
+ end
+
+ def allowed?(name, *args)
+ res = :nomatch
+ right = @rights.find do |acl|
+ # an acl can return :dunno, which means "I'm not qualified to answer your question,
+ # please ask someone else". This is used when for instance an acl matches, but not for the
+ # current rest method, where we might think some other acl might be more specific.
+ if match = acl.match?(name)
+ args << match
+ if (res = acl.allowed?(*args)) != :dunno
+ return res
+ end
+ end
+ false
+ end
+
+ # if allowed or denied, tell it to the world
+ return res unless res == :nomatch
+
+ # there were no rights allowing/denying name
+ # if name is not a path, let's throw
+ raise ArgumentError, "Unknown namespace right '%s'" % name unless name =~ /^\//
+
+ # but if this was a path, we implement a deny all policy by default
+ # on unknown rights.
+ return false
+ end
+
+ def initialize()
+ @rights = []
+ end
+
def [](name)
- name = name.intern if name.is_a? String
- super(name)
+ @rights.find { |acl| acl == name }
+ end
+
+ def include?(name)
+ @rights.include?(name)
+ end
+
+ def each
+ @rights.each { |r| yield r.name,r }
end
# Define a new right to which access can be provided.
- def newright(name)
- name = name.intern if name.is_a? String
- shortname = Right.shortname(name)
- if self.include? name
- raise ArgumentError, "Right '%s' is already defined" % name
- else
- self[name] = Right.new(name, shortname)
- end
+ def newright(name, line=nil)
+ add_right( Right.new(name, line) )
end
private
+ def add_right(right)
+ if right.acl_type == :name and include?(right.key)
+ raise ArgumentError, "Right '%s' already exists"
+ end
+ @rights << right
+ sort_rights
+ right
+ end
+
+ def sort_rights
+ @rights.sort!
+ end
+
# Retrieve a right by name.
def right(name)
- name = name.intern if name.is_a? String
self[name]
end
# A right.
class Right < Puppet::Network::AuthStore
- attr_accessor :name, :shortname
+ attr_accessor :name, :key, :acl_type, :line
+ attr_accessor :methods, :length
- Puppet::Util.logmethods(self, true)
+ ALL = [:save, :destroy, :find, :search]
- def self.shortname(name)
- name.to_s[0..0]
- end
+ Puppet::Util.logmethods(self, true)
- def initialize(name, shortname = nil)
+ def initialize(name, line)
+ @methods = []
@name = name
- @shortname = shortname
- unless @shortname
- @shortname = Right.shortname(name)
+ @line = line || 0
+ case name
+ when Symbol
+ @acl_type = :name
+ @key = name
+ when /^\[(.+)\]$/
+ @acl_type = :name
+ @key = $1.intern if name.is_a?(String)
+ when /^\//
+ @acl_type = :regex
+ @key = Regexp.new("^" + Regexp.escape(name))
+ @methods = ALL
+ when /^~/ # this is a regex
+ @acl_type = :regex
+ @name = name.gsub(/^~\s+/,'')
+ @key = Regexp.new(@name)
+ @methods = ALL
+ else
+ raise ArgumentError, "Unknown right type '%s'" % name
end
super()
end
@@ -68,6 +139,85 @@ class Puppet::Network::Rights < Hash
def valid?
true
end
+
+ def regex?
+ acl_type == :regex
+ end
+
+ # does this right is allowed for this triplet?
+ # if this right is too restrictive (ie we don't match this access method)
+ # then return :dunno so that upper layers have a chance to try another right
+ # tailored to the given method
+ def allowed?(name, ip, method = nil, match = nil)
+ return :dunno if acl_type == :regex and not @methods.include?(method)
+
+ if acl_type == :regex and match # make sure any capture are replaced
+ interpolate(match)
+ end
+
+ res = super(name,ip)
+
+ if acl_type == :regex
+ reset_interpolation
+ end
+ res
+ end
+
+ # restrict this right to some method only
+ def restrict_method(m)
+ m = m.intern if m.is_a?(String)
+
+ unless ALL.include?(m)
+ raise ArgumentError, "'%s' is not an allowed value for method directive" % m
+ end
+
+ # if we were allowing all methods, then starts from scratch
+ if @methods === ALL
+ @methods = []
+ end
+
+ if @methods.include?(m)
+ raise ArgumentError, "'%s' is already in the '%s' ACL" % [m, name]
+ end
+
+ @methods << m
+ end
+
+ def match?(key)
+ # if we are a namespace compare directly
+ return self.key == namespace_to_key(key) if acl_type == :name
+
+ # otherwise match with the regex
+ return self.key.match(key)
+ end
+
+ def namespace_to_key(key)
+ key = key.intern if key.is_a?(String)
+ key
+ end
+
+ # this is where all the magic happens.
+ # we're sorting the rights array with this scheme:
+ # * namespace rights are all in front
+ # * regex path rights are then all queued in file order
+ def <=>(rhs)
+ # move namespace rights at front
+ if self.acl_type != rhs.acl_type
+ return self.acl_type == :name ? -1 : 1
+ end
+
+ # sort by creation order (ie first match appearing in the file will win)
+ # that is don't sort, in which case the sort algorithm will order in the
+ # natural array order (ie the creation order)
+ return 0
+ end
+
+ def ==(name)
+ return self.key == namespace_to_key(name) if acl_type == :name
+ return self.name == name
+ end
+
end
+
end