summaryrefslogtreecommitdiffstats
path: root/lib/puppet
diff options
context:
space:
mode:
authorBrice Figureau <brice-puppet@daysofwonder.com>2009-04-21 23:53:48 +0200
committerBrice Figureau <brice-puppet@daysofwonder.com>2009-04-23 20:52:03 +0200
commitdc1cd6fb6b143b6525953e619a716f04e678727c (patch)
treeab38c7fac99ba1cddab062176142ba071caea831 /lib/puppet
parent85233768f080b4cbc4e20eb0c354b6d859a2fb23 (diff)
downloadpuppet-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')
-rw-r--r--lib/puppet/defaults.rb5
-rw-r--r--lib/puppet/network/http/handler.rb8
-rw-r--r--lib/puppet/network/rest_authconfig.rb72
-rw-r--r--lib/puppet/network/rest_authorization.rb49
4 files changed, 133 insertions, 1 deletions
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index f21fbc26a..0eed2f884 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -395,6 +395,11 @@ module Puppet
:group => "$group",
:desc => "Where FileBucket files are stored."
},
+ :rest_authconfig => [ "$confdir/auth.conf",
+ "The configuration file that defines the rights to the different
+ rest indirections. This can be used as a fine-grained
+ authorization system for ``puppetmasterd``."
+ ],
:ca => [true, "Wether the master should function as a certificate authority."],
:modulepath => {:default => "$confdir/modules:/usr/share/puppet/modules",
:desc => "The search path for modules as a colon-separated list of
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
+