diff --git a/lib/puppet/file_serving/configuration/parser.rb b/lib/puppet/file_serving/configuration/parser.rb index 334201d37..83b75e28f 100644 --- a/lib/puppet/file_serving/configuration/parser.rb +++ b/lib/puppet/file_serving/configuration/parser.rb @@ -1,122 +1,115 @@ require 'puppet/file_serving/configuration' require 'puppet/util/loadedfile' class Puppet::FileServing::Configuration::Parser < Puppet::Util::LoadedFile Mount = Puppet::FileServing::Mount MODULES = 'modules' # Parse our configuration file. def parse raise("File server configuration #{self.file} does not exist") unless FileTest.exists?(self.file) raise("Cannot read file server configuration #{self.file}") unless FileTest.readable?(self.file) @mounts = {} @count = 0 File.open(self.file) { |f| mount = nil f.each { |line| # Have the count increment at the top, in case we throw exceptions. @count += 1 case line when /^\s*#/; next # skip comments when /^\s*$/; next # skip blank lines when /\[([-\w]+)\]/ mount = newmount($1) - when /^\s*(\w+)\s+(.+)$/ + when /^\s*(\w+)\s+(.+?)(\s*#.*)?$/ var = $1 value = $2 + value.strip! raise(ArgumentError, "Fileserver configuration file does not use '=' as a separator") if value =~ /^=/ case var when "path" path(mount, value) when "allow" allow(mount, value) when "deny" deny(mount, value) else raise ArgumentError.new("Invalid argument '#{var}'", @count, file) end else raise ArgumentError.new("Invalid line '#{line.chomp}'", @count, file) end } } validate @mounts end private # Allow a given pattern access to a mount. def allow(mount, value) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = value.split(/\s*,\s*/).each { |val| begin mount.info "allowing #{val} access" mount.allow(val) - rescue AuthStoreError => detail - - raise ArgumentError.new( - detail.to_s, - - @count, file) + rescue Puppet::AuthStoreError => detail + raise ArgumentError.new(detail.to_s, @count, file) end } end # Deny a given pattern access to a mount. def deny(mount, value) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = value.split(/\s*,\s*/).each { |val| begin mount.info "denying #{val} access" mount.deny(val) - rescue AuthStoreError => detail - - raise ArgumentError.new( - detail.to_s, - - @count, file) + rescue Puppet::AuthStoreError => detail + raise ArgumentError.new(detail.to_s, @count, file) end } end # Create a new mount. def newmount(name) raise ArgumentError, "#{@mounts[name]} is already mounted at #{name}", @count, file if @mounts.include?(name) case name when "modules" mount = Mount::Modules.new(name) when "plugins" mount = Mount::Plugins.new(name) else mount = Mount::File.new(name) end @mounts[name] = mount mount end # Set the path for a mount. def path(mount, value) if mount.respond_to?(:path=) begin mount.path = value rescue ArgumentError => detail Puppet.err "Removing mount #{mount.name}: #{detail}" @mounts.delete(mount.name) end else Puppet.warning "The '#{mount.name}' module can not have a path. Ignoring attempt to set it" end end # Make sure all of our mounts are valid. We have to do this after the fact # because details are added over time as the file is parsed. def validate @mounts.each { |name, mount| mount.validate } end end diff --git a/lib/puppet/network/authconfig.rb b/lib/puppet/network/authconfig.rb index 4ba89fa71..1e486a2f9 100644 --- a/lib/puppet/network/authconfig.rb +++ b/lib/puppet/network/authconfig.rb @@ -1,172 +1,174 @@ 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+(.+)$/ + when /^\s*(allow|deny|method|environment|auth(?:enticated)?)\s+(.+?)(\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) + value.strip! 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 + val.strip! right.info msg % val right.send(method, val) rescue AuthStoreError => detail raise ConfigurationError, "#{detail} at line #{count} of #{@file}" end end end end end diff --git a/lib/puppet/network/rest_authconfig.rb b/lib/puppet/network/rest_authconfig.rb index dfe8f85c4..7dcc81ef4 100644 --- a/lib/puppet/network/rest_authconfig.rb +++ b/lib/puppet/network/rest_authconfig.rb @@ -1,89 +1,94 @@ require 'puppet/network/authconfig' module Puppet class Network::RestAuthConfig < Network::AuthConfig extend MonitorMixin attr_accessor :rights DEFAULT_ACL = [ { :acl => "~ ^\/catalog\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, { :acl => "~ ^\/node\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, # this one will allow all file access, and thus delegate # to fileserver.conf { :acl => "/file" }, { :acl => "/certificate_revocation_list/ca", :method => :find, :authenticated => true }, { :acl => "/report", :method => :save, :authenticated => true }, { :acl => "/certificate/ca", :method => :find, :authenticated => false }, { :acl => "/certificate/", :method => :find, :authenticated => false }, { :acl => "/certificate_request", :method => [:find, :save], :authenticated => false }, { :acl => "/status", :method => [:find], :authenticated => true }, ] def self.main synchronize do add_acl = @main.nil? super @main.insert_default_acl if add_acl and !@main.exists? end @main end + def allowed?(request) + Puppet.deprecation_warning "allowed? should not be called for REST authorization - use check_authorization instead" + check_authorization(request) + end + # check wether this request is allowed in our ACL # raise an Puppet::Network::AuthorizedError if the request # is denied. - def allowed?(indirection, method, key, params) + def check_authorization(indirection, method, key, params) read # we're splitting the request in part because # fail_on_deny could as well be called in the XMLRPC context # with a ClientRequest. if authorization_failure_exception = @rights.is_request_forbidden_and_why?(indirection, method, key, params) Puppet.warning("Denying access: #{authorization_failure_exception}") raise authorization_failure_exception end 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 |acl| unless rights[acl[:acl]] Puppet.info "Inserting default '#{acl[:acl]}'(#{acl[:authenticated] ? "auth" : "non-auth"}) ACL because #{( !exists? ? "#{Puppet[:rest_authconfig]} doesn't exist" : "none were found in '#{@file}'")}" mk_acl(acl) 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 unless rights["/"] rights.newright("/") rights.restrict_authenticated("/", :any) end end def mk_acl(acl) @rights.newright(acl[:acl]) @rights.allow(acl[:acl], acl[:allow] || "*") if method = acl[:method] method = [method] unless method.is_a?(Array) method.each { |m| @rights.restrict_method(acl[:acl], m) } end @rights.restrict_authenticated(acl[:acl], acl[:authenticated]) unless acl[:authenticated].nil? end end end diff --git a/lib/puppet/network/rest_authorization.rb b/lib/puppet/network/rest_authorization.rb index 50f094e3e..d636d486a 100644 --- a/lib/puppet/network/rest_authorization.rb +++ b/lib/puppet/network/rest_authorization.rb @@ -1,23 +1,23 @@ require 'puppet/network/client_request' require 'puppet/network/rest_authconfig' module Puppet::Network module RestAuthorization # Create our config object if necessary. If there's no configuration file # we install our defaults def authconfig @authconfig ||= Puppet::Network::RestAuthConfig.main @authconfig end # Verify that our client has access. def check_authorization(indirection, method, key, params) - authconfig.allowed?(indirection, method, key, params) + authconfig.check_authorization(indirection, method, key, params) end end end diff --git a/spec/integration/network/rest_authconfig_spec.rb b/spec/integration/network/rest_authconfig_spec.rb new file mode 100644 index 000000000..d2f539cd4 --- /dev/null +++ b/spec/integration/network/rest_authconfig_spec.rb @@ -0,0 +1,145 @@ +require 'spec_helper' + +require 'puppet/network/rest_authconfig' + +RSpec::Matchers.define :allow do |params| + + match do |auth| + begin + auth.check_authorization(params[0], params[1], params[2], params[3]) + true + rescue Puppet::Network::AuthorizationError + false + end + end + + failure_message_for_should do |instance| + "expected #{params[3][:node]}/#{params[3][:ip]} to be allowed" + end + + failure_message_for_should_not do |instance| + "expected #{params[3][:node]}/#{params[3][:ip]} to be forbidden" + end +end + +describe Puppet::Network::RestAuthConfig do + include PuppetSpec::Files + + before(:each) do + Puppet[:rest_authconfig] = tmpfile('auth.conf') + end + + def add_rule(rule) + File.open(Puppet[:rest_authconfig],"w+") do |f| + f.print "path /test\n#{rule}\n" + end + @auth = Puppet::Network::RestAuthConfig.new(Puppet[:rest_authconfig], true) + end + + def add_regex_rule(regex, rule) + File.open(Puppet[:rest_authconfig],"w+") do |f| + f.print "path ~ #{regex}\n#{rule}\n" + end + @auth = Puppet::Network::RestAuthConfig.new(Puppet[:rest_authconfig], true) + end + + def request(args = {}) + { :ip => '10.1.1.1', :node => 'host.domain.com', :key => 'key', :authenticated => true }.each do |k,v| + args[k] ||= v + end + ['test', :find, args[:key], args] + end + + it "should support IPv4 address" do + add_rule("allow 10.1.1.1") + + @auth.should allow(request) + end + + it "should support CIDR IPv4 address" do + add_rule("allow 10.0.0.0/8") + + @auth.should allow(request) + end + + it "should support wildcard IPv4 address" do + add_rule("allow 10.1.1.*") + + @auth.should allow(request) + end + + it "should support IPv6 address" do + add_rule("allow 2001:DB8::8:800:200C:417A") + + @auth.should allow(request(:ip => '2001:DB8::8:800:200C:417A')) + end + + it "should support hostname" do + add_rule("allow host.domain.com") + + @auth.should allow(request) + end + + it "should support wildcard host" do + add_rule("allow *.domain.com") + + @auth.should allow(request) + end + + it "should support hostname backreferences" do + add_regex_rule('^/test/([^/]+)$', "allow $1.domain.com") + + @auth.should allow(request(:key => 'host')) + end + + it "should support opaque strings" do + add_rule("allow this-is-opaque@or-not") + + @auth.should allow(request(:node => 'this-is-opaque@or-not')) + end + + it "should support opaque strings and backreferences" do + add_regex_rule('^/test/([^/]+)$', "allow $1") + + @auth.should allow(request(:key => 'this-is-opaque@or-not', :node => 'this-is-opaque@or-not')) + end + + it "should support hostname ending with '.'" do + pending('bug #7589') + add_rule("allow host.domain.com.") + + @auth.should allow(request(:node => 'host.domain.com.')) + end + + it "should support hostname ending with '.' and backreferences" do + pending('bug #7589') + add_regex_rule('^/test/([^/]+)$',"allow $1") + + @auth.should allow(request(:node => 'host.domain.com.')) + end + + it "should support trailing whitespace" do + add_rule('allow host.domain.com ') + + @auth.should allow(request) + end + + it "should support inlined comments" do + add_rule('allow host.domain.com # will it work?') + + @auth.should allow(request) + end + + it "should deny non-matching host" do + add_rule("allow inexistant") + + @auth.should_not allow(request) + end + + it "should deny denied hosts" do + add_rule("deny host.domain.com") + + @auth.should_not allow(request) + end + +end \ No newline at end of file diff --git a/spec/unit/file_serving/configuration/parser_spec.rb b/spec/unit/file_serving/configuration/parser_spec.rb index 3d6b3e234..5ccfc5075 100755 --- a/spec/unit/file_serving/configuration/parser_spec.rb +++ b/spec/unit/file_serving/configuration/parser_spec.rb @@ -1,180 +1,188 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/file_serving/configuration/parser' describe Puppet::FileServing::Configuration::Parser do it "should subclass the LoadedFile class" do Puppet::FileServing::Configuration::Parser.superclass.should equal(Puppet::Util::LoadedFile) end end module FSConfigurationParserTesting def mock_file_content(content) # We want an array, but we actually want our carriage returns on all of it. lines = content.split("\n").collect { |l| l + "\n" } @filehandle.stubs(:each).multiple_yields(*lines) end end describe Puppet::FileServing::Configuration::Parser do before :each do @path = "/my/config.conf" FileTest.stubs(:exists?).with(@path).returns(true) FileTest.stubs(:readable?).with(@path).returns(true) @filehandle = mock 'filehandle' File.expects(:open).with(@path).yields(@filehandle) @parser = Puppet::FileServing::Configuration::Parser.new(@path) end describe Puppet::FileServing::Configuration::Parser, " when parsing" do include FSConfigurationParserTesting it "should allow comments" do @filehandle.expects(:each).yields("# this is a comment\n") proc { @parser.parse }.should_not raise_error end it "should allow blank lines" do @filehandle.expects(:each).yields("\n") proc { @parser.parse }.should_not raise_error end it "should create a new mount for each section in the configuration" do mount1 = mock 'one', :validate => true mount2 = mock 'two', :validate => true Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) Puppet::FileServing::Mount::File.expects(:new).with("two").returns(mount2) mock_file_content "[one]\n[two]\n" @parser.parse end # This test is almost the exact same as the previous one. it "should return a hash of the created mounts" do mount1 = mock 'one', :validate => true mount2 = mock 'two', :validate => true Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) Puppet::FileServing::Mount::File.expects(:new).with("two").returns(mount2) mock_file_content "[one]\n[two]\n" result = @parser.parse result["one"].should equal(mount1) result["two"].should equal(mount2) end it "should only allow mount names that are alphanumeric plus dashes" do mock_file_content "[a*b]\n" proc { @parser.parse }.should raise_error(ArgumentError) end it "should fail if the value for path/allow/deny starts with an equals sign" do mock_file_content "[one]\npath = /testing" proc { @parser.parse }.should raise_error(ArgumentError) end it "should validate each created mount" do mount1 = mock 'one' Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) mock_file_content "[one]\n" mount1.expects(:validate) @parser.parse end it "should fail if any mount does not pass validation" do mount1 = mock 'one' Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) mock_file_content "[one]\n" mount1.expects(:validate).raises RuntimeError lambda { @parser.parse }.should raise_error(RuntimeError) end end describe Puppet::FileServing::Configuration::Parser, " when parsing mount attributes" do include FSConfigurationParserTesting before do @mount = stub 'testmount', :name => "one", :validate => true Puppet::FileServing::Mount::File.expects(:new).with("one").returns(@mount) @parser.stubs(:add_modules_mount) end it "should set the mount path to the path attribute from that section" do mock_file_content "[one]\npath /some/path\n" @mount.expects(:path=).with("/some/path") @parser.parse end it "should tell the mount to allow any allow values from the section" do mock_file_content "[one]\nallow something\n" @mount.expects(:info) @mount.expects(:allow).with("something") @parser.parse end + it "should support inline comments" do + mock_file_content "[one]\nallow something \# will it work?\n" + + @mount.expects(:info) + @mount.expects(:allow).with("something") + @parser.parse + end + it "should tell the mount to deny any deny values from the section" do mock_file_content "[one]\ndeny something\n" @mount.expects(:info) @mount.expects(:deny).with("something") @parser.parse end it "should fail on any attributes other than path, allow, and deny" do mock_file_content "[one]\ndo something\n" proc { @parser.parse }.should raise_error(ArgumentError) end end describe Puppet::FileServing::Configuration::Parser, " when parsing the modules mount" do include FSConfigurationParserTesting before do @mount = stub 'modulesmount', :name => "modules", :validate => true end it "should create an instance of the Modules Mount class" do mock_file_content "[modules]\n" Puppet::FileServing::Mount::Modules.expects(:new).with("modules").returns @mount @parser.parse end it "should warn if a path is set" do mock_file_content "[modules]\npath /some/path\n" Puppet::FileServing::Mount::Modules.expects(:new).with("modules").returns(@mount) Puppet.expects(:warning) @parser.parse end end describe Puppet::FileServing::Configuration::Parser, " when parsing the plugins mount" do include FSConfigurationParserTesting before do @mount = stub 'pluginsmount', :name => "plugins", :validate => true end it "should create an instance of the Plugins Mount class" do mock_file_content "[plugins]\n" Puppet::FileServing::Mount::Plugins.expects(:new).with("plugins").returns @mount @parser.parse end it "should warn if a path is set" do mock_file_content "[plugins]\npath /some/path\n" Puppet.expects(:warning) @parser.parse end end end diff --git a/spec/unit/network/authconfig_spec.rb b/spec/unit/network/authconfig_spec.rb index c47b2e0c5..ca94cc1ab 100755 --- a/spec/unit/network/authconfig_spec.rb +++ b/spec/unit/network/authconfig_spec.rb @@ -1,291 +1,314 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/authconfig' describe Puppet::Network::AuthConfig do before do @rights = stubs 'rights' Puppet::Network::Rights.stubs(:new).returns(@rights) @rights.stubs(:each).returns([]) FileTest.stubs(:exists?).returns(true) File.stubs(:stat).returns(stub('stat', :ctime => :now)) Time.stubs(:now).returns Time.now @authconfig = Puppet::Network::AuthConfig.new("dummy", false) end describe "when initializing" do before :each do Puppet::Network::AuthConfig.any_instance.stubs(:read) end it "should use the authconfig default pathname if none provided" do Puppet.expects(:[]).with(:authconfig).returns("dummy") Puppet::Network::AuthConfig.new end it "should raise an error if no file is defined finally" do Puppet.stubs(:[]).with(:authconfig).returns(nil) lambda { Puppet::Network::AuthConfig.new }.should raise_error(Puppet::DevError) end it "should read and parse the file if parsenow is true" do Puppet::Network::AuthConfig.any_instance.expects(:read) Puppet::Network::AuthConfig.new("dummy", true) end end describe "when checking authorization" do before :each do @authconfig.stubs(:read) @call = stub 'call', :intern => "name" @handler = stub 'handler', :intern => "handler" @method = stub_everything 'method' @request = stub 'request', :call => @call, :handler => @handler, :method => @method, :name => "me", :ip => "1.2.3.4" end it "should attempt to read the authconfig file" do @rights.stubs(:include?) @authconfig.expects(:read) @authconfig.allowed?(@request) end it "should use a name right if it exists" do right = stub 'right' @rights.stubs(:include?).with("name").returns(true) @rights.stubs(:[]).with("name").returns(right) right.expects(:allowed?).with("me", "1.2.3.4") @authconfig.allowed?(@request) end it "should use a namespace right otherwise" do right = stub 'right' @rights.stubs(:include?).with("name").returns(false) @rights.stubs(:include?).with("handler").returns(true) @rights.stubs(:[]).with("handler").returns(right) right.expects(:allowed?).with("me", "1.2.3.4") @authconfig.allowed?(@request) end it "should return whatever the found rights returns" do right = stub 'right' @rights.stubs(:include?).with("name").returns(true) @rights.stubs(:[]).with("name").returns(right) right.stubs(:allowed?).with("me", "1.2.3.4").returns(:returned) @authconfig.allowed?(@request).should == :returned end end describe "when parsing authconfig file" do before :each do @fd = stub 'fd' File.stubs(:open).yields(@fd) @rights.stubs(:include?).returns(false) @rights.stubs(:[]) end it "should skip comments" do @fd.stubs(:each).yields(' # comment') @rights.expects(:newright).never @authconfig.read end it "should increment line number even on commented lines" do @fd.stubs(:each).multiple_yields(' # comment','[puppetca]') @rights.expects(:newright).with('[puppetca]', 2, 'dummy') @authconfig.read end it "should skip blank lines" do @fd.stubs(:each).yields(' ') @rights.expects(:newright).never @authconfig.read end it "should increment line number even on blank lines" do @fd.stubs(:each).multiple_yields(' ','[puppetca]') @rights.expects(:newright).with('[puppetca]', 2, 'dummy') @authconfig.read end it "should throw an error if the current namespace right already exist" do @fd.stubs(:each).yields('[puppetca]') @rights.stubs(:include?).with("puppetca").returns(true) lambda { @authconfig.read }.should raise_error end it "should not throw an error if the current path right already exist" do @fd.stubs(:each).yields('path /hello') @rights.stubs(:newright).with("/hello",1, 'dummy') @rights.stubs(:include?).with("/hello").returns(true) lambda { @authconfig.read }.should_not raise_error end it "should create a new right for found namespaces" do @fd.stubs(:each).yields('[puppetca]') @rights.expects(:newright).with("[puppetca]", 1, 'dummy') @authconfig.read end it "should create a new right for each found namespace line" do @fd.stubs(:each).multiple_yields('[puppetca]', '[fileserver]') @rights.expects(:newright).with("[puppetca]", 1, 'dummy') @rights.expects(:newright).with("[fileserver]", 2, 'dummy') @authconfig.read end it "should create a new right for each found path line" do @fd.stubs(:each).multiple_yields('path /certificates') @rights.expects(:newright).with("/certificates", 1, 'dummy') @authconfig.read end it "should create a new right for each found regex line" do @fd.stubs(:each).multiple_yields('path ~ .rb$') @rights.expects(:newright).with("~ .rb$", 1, 'dummy') @authconfig.read end + it "should strip whitespace around ACE" do + acl = stub 'acl', :info + + @fd.stubs(:each).multiple_yields('[puppetca]', ' allow 127.0.0.1 , 172.16.10.0 ') + @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) + + acl.expects(:allow).with('127.0.0.1') + acl.expects(:allow).with('172.16.10.0') + + @authconfig.read + end + + it "should allow ACE inline comments" do + acl = stub 'acl', :info + + @fd.stubs(:each).multiple_yields('[puppetca]', ' allow 127.0.0.1 # will it work?') + @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) + + acl.expects(:allow).with('127.0.0.1') + + @authconfig.read + end + it "should create an allow ACE on each subsequent allow" do acl = stub 'acl', :info @fd.stubs(:each).multiple_yields('[puppetca]', 'allow 127.0.0.1') @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) acl.expects(:allow).with('127.0.0.1') @authconfig.read end it "should create a deny ACE on each subsequent deny" do acl = stub 'acl', :info @fd.stubs(:each).multiple_yields('[puppetca]', 'deny 127.0.0.1') @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) acl.expects(:deny).with('127.0.0.1') @authconfig.read end it "should inform the current ACL if we get the 'method' directive" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) @fd.stubs(:each).multiple_yields('path /certificates', 'method search,find') @rights.stubs(:newright).with("/certificates", 1, 'dummy').returns(acl) acl.expects(:restrict_method).with('search') acl.expects(:restrict_method).with('find') @authconfig.read end it "should raise an error if the 'method' directive is used in a right different than a path/regex one" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) @fd.stubs(:each).multiple_yields('[puppetca]', 'method search,find') @rights.stubs(:newright).with("puppetca", 1, 'dummy').returns(acl) lambda { @authconfig.read }.should raise_error end it "should inform the current ACL if we get the 'environment' directive" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) @fd.stubs(:each).multiple_yields('path /certificates', 'environment production,development') @rights.stubs(:newright).with("/certificates", 1, 'dummy').returns(acl) acl.expects(:restrict_environment).with('production') acl.expects(:restrict_environment).with('development') @authconfig.read end it "should raise an error if the 'environment' directive is used in a right different than a path/regex one" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) @fd.stubs(:each).multiple_yields('[puppetca]', 'environment env') @rights.stubs(:newright).with("puppetca", 1, 'dummy').returns(acl) lambda { @authconfig.read }.should raise_error end it "should inform the current ACL if we get the 'auth' directive" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) @fd.stubs(:each).multiple_yields('path /certificates', 'auth yes') @rights.stubs(:newright).with("/certificates", 1, 'dummy').returns(acl) acl.expects(:restrict_authenticated).with('yes') @authconfig.read end it "should also allow the longest 'authenticated' directive" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) @fd.stubs(:each).multiple_yields('path /certificates', 'authenticated yes') @rights.stubs(:newright).with("/certificates", 1, 'dummy').returns(acl) acl.expects(:restrict_authenticated).with('yes') @authconfig.read end it "should raise an error if the 'auth' directive is used in a right different than a path/regex one" do acl = stub 'acl', :info acl.stubs(:acl_type).returns(:regex) @fd.stubs(:each).multiple_yields('[puppetca]', 'auth yes') @rights.stubs(:newright).with("puppetca", 1, 'dummy').returns(acl) lambda { @authconfig.read }.should raise_error end end end diff --git a/spec/unit/network/rest_authconfig_spec.rb b/spec/unit/network/rest_authconfig_spec.rb index e1403997f..bebbb874f 100755 --- a/spec/unit/network/rest_authconfig_spec.rb +++ b/spec/unit/network/rest_authconfig_spec.rb @@ -1,127 +1,127 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/rest_authconfig' describe Puppet::Network::RestAuthConfig do DEFAULT_ACL = Puppet::Network::RestAuthConfig::DEFAULT_ACL before :each do FileTest.stubs(:exists?).returns(true) File.stubs(:stat).returns(stub('stat', :ctime => :now)) Time.stubs(:now).returns Time.now @authconfig = Puppet::Network::RestAuthConfig.new("dummy", false) @authconfig.stubs(:read) @acl = stub_everything 'rights' @authconfig.rights = @acl end it "should use the puppet default rest authorization file" do Puppet.expects(:[]).with(:rest_authconfig).returns("dummy") Puppet::Network::RestAuthConfig.new(nil, false) end it "should ask for authorization to the ACL subsystem" do params = {:ip => "127.0.0.1", :node => "me", :environment => :env, :authenticated => true} @acl.expects(:is_request_forbidden_and_why?).with("path", :save, "to/resource", params).returns(nil) - @authconfig.allowed?("path", :save, "to/resource", params) + @authconfig.check_authorization("path", :save, "to/resource", params) end describe "when defining an acl with mk_acl" do it "should create a new right for each default acl" do @acl.expects(:newright).with(:path) @authconfig.mk_acl(:acl => :path) end it "should allow everyone for each default right" do @acl.expects(:allow).with(:path, "*") @authconfig.mk_acl(:acl => :path) end it "should restrict the ACL to a method" do @acl.expects(:restrict_method).with(:path, :method) @authconfig.mk_acl(:acl => :path, :method => :method) end it "should restrict the ACL to a specific authentication state" do @acl.expects(:restrict_authenticated).with(:path, :authentication) @authconfig.mk_acl(:acl => :path, :authenticated => :authentication) end end describe "when parsing the configuration file" do it "should check for missing ACL after reading the authconfig file" do File.stubs(:open) @authconfig.expects(:insert_default_acl) @authconfig.parse end end DEFAULT_ACL.each do |acl| it "should insert #{acl[:acl]} if not present" do @authconfig.rights.stubs(:[]).returns(true) @authconfig.rights.stubs(:[]).with(acl[:acl]).returns(nil) @authconfig.expects(:mk_acl).with { |h| h[:acl] == acl[:acl] } @authconfig.insert_default_acl end it "should not insert #{acl[:acl]} if present" do @authconfig.rights.stubs(:[]).returns(true) @authconfig.rights.stubs(:[]).with(acl).returns(true) @authconfig.expects(:mk_acl).never @authconfig.insert_default_acl end end it "should create default ACL entries if no file have been read" do Puppet::Network::RestAuthConfig.any_instance.stubs(:exists?).returns(false) Puppet::Network::RestAuthConfig.any_instance.expects(:insert_default_acl) Puppet::Network::RestAuthConfig.main end describe "when adding default ACLs" do DEFAULT_ACL.each do |acl| it "should create a default right for #{acl[:acl]}" do @authconfig.stubs(:mk_acl) @authconfig.expects(:mk_acl).with(acl) @authconfig.insert_default_acl end end it "should log at info loglevel" do Puppet.expects(:info).at_least_once @authconfig.insert_default_acl end it "should create a last catch-all deny all rule" do @authconfig.stubs(:mk_acl) @acl.expects(:newright).with("/") @authconfig.insert_default_acl end it "should create a last catch-all deny all rule for any authenticated request state" do @authconfig.stubs(:mk_acl) @acl.stubs(:newright).with("/") @acl.expects(:restrict_authenticated).with("/", :any) @authconfig.insert_default_acl end end end