diff options
author | Brice Figureau <brice-puppet@daysofwonder.com> | 2009-04-21 23:53:48 +0200 |
---|---|---|
committer | Brice Figureau <brice-puppet@daysofwonder.com> | 2009-04-23 20:52:03 +0200 |
commit | dc1cd6fb6b143b6525953e619a716f04e678727c (patch) | |
tree | ab38c7fac99ba1cddab062176142ba071caea831 /lib/puppet/network | |
parent | 85233768f080b4cbc4e20eb0c354b6d859a2fb23 (diff) | |
download | puppet-dc1cd6fb6b143b6525953e619a716f04e678727c.tar.gz puppet-dc1cd6fb6b143b6525953e619a716f04e678727c.tar.xz puppet-dc1cd6fb6b143b6525953e619a716f04e678727c.zip |
Fix #1875 - Add a REST authorization system
This patch introduces a new configuration file (and configuration
setting to set it).
Each REST request is checked against this configuration file, and is
either allowed or denied.
The configuration file has the following format:
path /uripath
method <methods>
allow <ip> or <name>
deny <ip> or <name>
or
path ~ <regex>
method <methods>
allow <ip> or <name>
deny <ip> or <name>
where regex is a ruby regex.
This last syntax allows deny/allow interpolation from
the regex captures:
path ~ /files[^/]+/files/([^/]+)/([^/])/
method find
allow $2.$1
If you arrange your files/ directory to have files in
'domain.com/host/', then only the referenced host will
be able to access their files, other hosts will be denied.
For instance:
files/reductivelabs.com/dns/...
files/reductivelabs.com/www/...
then only files in dns can be accessible by dns.reductivelabs.com
and so on...
If the auth.conf file doesn't exist puppet uses sane defaults that allows
clients to check-in and ask for their configurations...
Signed-off-by: Brice Figureau <brice-puppet@daysofwonder.com>
Diffstat (limited to 'lib/puppet/network')
-rw-r--r-- | lib/puppet/network/http/handler.rb | 8 | ||||
-rw-r--r-- | lib/puppet/network/rest_authconfig.rb | 72 | ||||
-rw-r--r-- | lib/puppet/network/rest_authorization.rb | 49 |
3 files changed, 128 insertions, 1 deletions
diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb index 04ba14401..20234b2da 100644 --- a/lib/puppet/network/http/handler.rb +++ b/lib/puppet/network/http/handler.rb @@ -2,9 +2,11 @@ module Puppet::Network::HTTP end require 'puppet/network/http/api/v1' +require 'puppet/network/rest_authorization' module Puppet::Network::HTTP::Handler include Puppet::Network::HTTP::API::V1 + include Puppet::Network::RestAuthorization attr_reader :server, :handler @@ -38,7 +40,11 @@ module Puppet::Network::HTTP::Handler def process(request, response) indirection_request = uri2indirection(http_method(request), path(request), params(request)) - send("do_%s" % indirection_request.method, indirection_request, request, response) + if authorized?(indirection_request) + send("do_%s" % indirection_request.method, indirection_request, request, response) + else + return do_exception(response, "Request forbidden by configuration %s %s" % [indirection_request.indirection_name, indirection_request.key], 403) + end rescue Exception => e return do_exception(response, e) end diff --git a/lib/puppet/network/rest_authconfig.rb b/lib/puppet/network/rest_authconfig.rb new file mode 100644 index 000000000..58708e120 --- /dev/null +++ b/lib/puppet/network/rest_authconfig.rb @@ -0,0 +1,72 @@ +require 'puppet/network/authconfig' + +module Puppet + class Network::RestAuthConfig < Network::AuthConfig + + attr_accessor :rights + + DEFAULT_ACL = { + :facts => { :acl => "/facts", :method => [:save, :find] }, + :catalog => { :acl => "/catalog", :method => :find }, + # this one will allow all file access, and thus delegate + # to fileserver.conf + :file => { :acl => "/file" }, + :cert => { :acl => "/certificate", :method => :find }, + :reports => { :acl => "/report", :method => :save } + } + + def self.main + add_acl = @main.nil? + super + @main.insert_default_acl if add_acl and !@main.exists? + @main + end + + # check wether this request is allowed in our ACL + def allowed?(request) + read() + return @rights.allowed?(build_uri(request), request.node, request.ip, request.method) + end + + def initialize(file = nil, parsenow = true) + super(file || Puppet[:rest_authconfig], parsenow) + + # if we didn't read a file (ie it doesn't exist) + # make sure we can create some default rights + @rights ||= Puppet::Network::Rights.new + end + + def parse() + super() + insert_default_acl + end + + # force regular ACLs to be present + def insert_default_acl + DEFAULT_ACL.each do |name, acl| + unless rights[acl[:acl]] + Puppet.warning "Inserting default '#{acl[:acl]}' acl because none were found in '%s'" % ( @file || "no file configured") + mk_acl(acl[:acl], acl[:method]) + end + end + # queue an empty (ie deny all) right for every other path + # actually this is not strictly necessary as the rights system + # denies not explicitely allowed paths + rights.newright("/") unless rights["/"] + end + + def mk_acl(path, method = nil) + @rights.newright(path) + @rights.allow(path, "*") + + if method + method = [method] unless method.is_a?(Array) + method.each { |m| @rights.restrict_method(path, m) } + end + end + + def build_uri(request) + "/#{request.indirection_name}/#{request.key}" + end + end +end diff --git a/lib/puppet/network/rest_authorization.rb b/lib/puppet/network/rest_authorization.rb new file mode 100644 index 000000000..3278640fe --- /dev/null +++ b/lib/puppet/network/rest_authorization.rb @@ -0,0 +1,49 @@ +require 'puppet/network/client_request' +require 'puppet/network/rest_authconfig' + +module Puppet::Network + # Most of our subclassing is just so that we can get + # access to information from the request object, like + # the client name and IP address. + class InvalidClientRequest < Puppet::Error; end + module RestAuthorization + + # Create our config object if necessary. If there's no configuration file + # we install our defaults + def authconfig + unless defined? @authconfig + @authconfig = Puppet::Network::RestAuthConfig.main + end + + @authconfig + end + + # Verify that our client has access. We allow untrusted access to + # certificates terminus but no others. + def authorized?(request) + msg = "%s client %s access to %s [%s]" % + [ request.authenticated? ? "authenticated" : "unauthenticated", + (request.node.nil? ? request.ip : "#{request.node}(#{request.ip})"), + request.indirection_name, request.method ] + + if request.authenticated? + res = authenticated_authorized?(request, msg ) + else + res = unauthenticated_authorized?(request, msg) + end + Puppet.notice((res ? "Allowing " : "Denying ") + msg) + return res + end + + # delegate to our authorization file + def authenticated_authorized?(request, msg) + authconfig.allowed?(request) + end + + # allow only certificate requests when not authenticated + def unauthenticated_authorized?(request, msg) + request.indirection_name == :certificate or request.indirection_name == :certificate_request + end + end +end + |