Files
mcollective-actionpolicy-auth/spec/actionpolicy/actionpolicy_spec.rb
Ben Roberts 233790ff54 Support multiple callerids in policy files
This patch adds support for multiple callerids in the policy files, just
as the other fields (actions, facts, classes) can. Updated poliicy files
look like this:
```
policy default deny
allow	uid=500 uid=600	*	*		*
```

This is useful because it allows bulk granting of permissions when using
mcollective::actionpolicy::rule from puppetlabs-mcollective:
```
    $admin_users = ['foo','bar']
    mcollective::actionpolicy {
        'default':
            default => 'deny';
        'nrpe':
            default => 'deny';
    }
     mcollective::actionpolicy::rule {
        'admins-allow-all':
            agent    => 'default',
            callerid => join(prefix($admin_users, 'cert='), ' ');
        'admins-allow-all-nrpe':
            agent    => 'nrpe',
            callerid => join(prefix($admin_users, 'cert='), ' ');
        'nrpe-nagios':
            agent    => 'nrpe',
            callerid => 'cert=nagios';
    }
```

This is especially helpful when there are large numbers of admin users being
managed by puppet (say ~10) since any `mcollective::actionpolicy::rule` added
for an agent prevents the default policy being used and so the admins have to
be explicitly re-added for each agent, rapidly bloating the size of the
manifest and causing massive duplication of code.

Backward compatibility change:
* Certificates with spaces in the filename (if even supported) would be
    broken by this change.

This commit also includes tests that verify both positive and negative lookups
in a policy file with multiple callerids.
2014-11-09 13:26:20 +00:00

534 lines
21 KiB
Ruby

#!/bin/env rspec
require 'spec_helper'
require File.join(File.dirname(__FILE__), '../../', 'util', 'actionpolicy.rb')
module MCollective
module Util
describe ActionPolicy do
let(:request) do
request = mock
request.stubs(:agent).returns('rspec_agent')
request.stubs(:caller).returns('rspec_caller')
request.stubs(:action).returns('rspec_action')
request
end
let(:config) do
config = mock
config.stubs(:configdir).returns('/rspecdir')
config.stubs(:pluginconf).returns({})
config
end
let(:actionpolicy) { ActionPolicy.new(request) }
before do
Config.stubs(:instance).returns(config)
@fixtures_dir = File.join(File.dirname(__FILE__), 'fixtures')
end
describe '#authorize' do
it 'should create a new ActionPolicy object and call #authorize_request' do
actionpolicy.expects(:authorize_request)
ActionPolicy.expects(:new).returns(actionpolicy)
ActionPolicy.authorize(request)
end
end
describe '#initialize' do
it 'should set the default values' do
actionpolicy.config.should == config
actionpolicy.agent.should == 'rspec_agent'
actionpolicy.caller.should == 'rspec_caller'
actionpolicy.action.should == 'rspec_action'
actionpolicy.allow_unconfigured.should == false
actionpolicy.configdir.should == '/rspecdir'
end
it 'should set allow_unconfigured if set in config file' do
config.stubs(:pluginconf).returns({'actionpolicy.allow_unconfigured' => '1'})
result = ActionPolicy.new(request)
result.allow_unconfigured.should == true
end
end
describe '#authorize_request' do
before do
Log.stubs(:debug)
end
it 'should deny the request if policy file does not exist and allow_unconfigured is false' do
ActionPolicy.any_instance.expects(:lookup_policy_file).returns(nil)
expect{
actionpolicy.authorize_request
}.to raise_error RPCAborted
end
it 'should return true if policy file does not exist but allow_unconfigured is true' do
ActionPolicy.any_instance.expects(:lookup_policy_file).returns(nil)
config.stubs(:pluginconf).returns({'actionpolicy.allow_unconfigured' => 'y'})
actionpolicy.authorize_request.should be_true
end
it 'should parse the policy file if it exists' do
ActionPolicy.any_instance.expects(:lookup_policy_file).returns('/rspecdir/policyfile')
ActionPolicy.any_instance.expects(:parse_policy_file).with('/rspecdir/policyfile')
actionpolicy.authorize_request
end
it 'should enforce precedence of enable_default over allow_unconfigured' do
config.stubs(:pluginconf).returns({'actionpolicy.allow_unconfigured' => 'y',
'actionpolicy.enable_default' => 'y'})
ActionPolicy.any_instance.expects(:lookup_policy_file).returns('/rspec/default')
ActionPolicy.any_instance.expects(:parse_policy_file).with('/rspec/default')
actionpolicy.authorize_request
end
end
describe '#parse_policy_file' do
before do
Log.stubs(:debug)
end
it 'should deny the request if allow_unconfigured is false and no lines match' do
File.expects(:read).with('policyfile').returns('')
expect{
actionpolicy.parse_policy_file('policyfile')
}.to raise_error RPCAborted
end
it 'should skip comment lines' do
File.expects(:read).with('policyfile').returns('#')
expect{
actionpolicy.parse_policy_file('policyfile')
}.to raise_error RPCAborted
end
# Fixtures
it 'should parse the default alllow policy' do
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'default_allow')).should be_true
end
it 'should parse the default deny policy' do
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'default_deny'))
}.to raise_error RPCAborted
end
# Example fixtures
it 'should parse example1 correctly' do
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example1')).should be_true
end
it 'should parse example2 correctly' do
request.stubs(:caller).returns('uid=500')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example2')).should be_true
request.stubs(:caller).returns('uid=501')
actionpolicy = ActionPolicy.new(request)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example2'))
}.to raise_error RPCAborted
end
it 'should parse example3 correctly' do
request.stubs(:action).returns('rspec')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example3')).should be_true
request.stubs(:action).returns('notrspec')
actionpolicy = ActionPolicy.new(request)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example3'))
}.to raise_error RPCAborted
end
it 'should parse example4 correctly' do
Util.stubs(:get_fact).with('foo').returns('bar')
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example4')).should be_true
Util.stubs(:get_fact).with('foo').returns('notbar')
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example4'))
}.to raise_error RPCAborted
end
it 'should parse example5 correctly' do
Util.stubs(:has_cf_class?).with('rspec').returns(true)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example5')).should be_true
Util.stubs(:has_cf_class?).with('rspec').returns(false)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example5'))
}.to raise_error RPCAborted
end
it 'should parse example6 correctly' do
request.stubs(:caller).returns('uid=500')
request.stubs(:action).returns('rspec')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example6')).should be_true
request.stubs(:caller).returns('uid=501')
request.stubs(:action).returns('notrspec')
actionpolicy = ActionPolicy.new(request)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example6'))
}.to raise_error RPCAborted
end
it 'should parse example7 correctly' do
request.stubs(:caller).returns('uid=500')
Util.stubs(:get_fact).with('foo').returns('bar')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example7')).should be_true
request.stubs(:caller).returns('uid=501')
Util.stubs(:get_fact).with('foo').returns('notbar')
actionpolicy = ActionPolicy.new(request)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example7'))
}.to raise_error RPCAborted
end
it 'should parse example8 correctly' do
request.stubs(:caller).returns('uid=500')
Util.stubs(:has_cf_class?).with('rspec').returns(true)
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example8')).should be_true
Util.stubs(:has_cf_class?).with('rspec').returns(false)
actionpolicy = ActionPolicy.new(request)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example8'))
}.to raise_error RPCAborted
end
it 'should parse example9 correctly' do
request.stubs(:caller).returns('uid=500')
request.stubs(:action).returns('rspec')
Util.stubs(:get_fact).with('foo').returns('bar')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example9')).should be_true
request.stubs(:caller).returns('uid=501')
request.stubs(:action).returns('notrspec')
Util.stubs(:get_fact).with('foo').returns('notbar')
actionpolicy = ActionPolicy.new(request)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example9'))
}.to raise_error RPCAborted
end
it 'should parse example10 correctly' do
request.stubs(:caller).returns('uid=500')
request.stubs(:action).returns('rspec')
Util.stubs(:has_cf_class?).with('rspec').returns(true)
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example10')).should be_true
request.stubs(:caller).returns('uid=501')
request.stubs(:action).returns('notrspec')
Util.stubs(:has_cf_class?).with('rspec').returns(false)
actionpolicy = ActionPolicy.new(request)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example10'))
}.to raise_error RPCAborted
end
it 'should parse example11 correctly' do
request.stubs(:caller).returns('uid=500')
request.stubs(:action).returns('rspec')
Util.stubs(:has_cf_class?).with('rspec').returns(true)
Util.stubs(:get_fact).with('foo').returns('bar')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example10')).should be_true
request.stubs(:caller).returns('uid=501')
request.stubs(:action).returns('notrspec')
Util.stubs(:has_cf_class?).with('rspec').returns(false)
Util.stubs(:get_fact).with('foo').returns('notbar')
actionpolicy = ActionPolicy.new(request)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example10'))
}.to raise_error RPCAborted
end
it 'should parse example12 correctly' do
request.stubs(:caller).returns('uid=500')
request.stubs(:action).returns('rspec')
Util.stubs(:has_cf_class?).with('rspec').returns(true)
Util.stubs(:get_fact).with('foo').returns('bar')
Util.stubs(:get_fact).with('bar').returns('foo')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example12')).should be_true
end
it 'should parse example13 correctly' do
request.stubs(:caller).returns('uid=500')
request.stubs(:action).returns('rspec')
Util.stubs(:has_cf_class?).with('one').returns(true)
Util.stubs(:has_cf_class?).with('two').returns(true)
Util.stubs(:has_cf_class?).with('three').returns(false)
Util.stubs(:get_fact).with('foo').returns('bar')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example13')).should be_true
end
it 'should parse example14 correctly' do
request.stubs(:caller).returns('uid=500')
request.stubs(:action).returns('rspec')
Util.stubs(:has_cf_class?).with('one').returns(true)
Util.stubs(:has_cf_class?).with('two').returns(false)
Util.stubs(:get_fact).with('foo').returns('bar')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example14')).should be_true
end
it 'should parse example15 correctly' do
# first field
request.stubs(:caller).returns('uid=500')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
# second field
request.stubs(:caller).returns('uid=600')
Util.stubs(:get_fact).with('customer').returns('acme')
Util.stubs(:has_cf_class?).with('acme::devserver').returns(true)
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
# third field
request.stubs(:caller).returns('uid=600')
request.stubs(:action).returns('status')
Util.stubs(:get_fact).with('customer').returns('acme')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
# forth field
request.stubs(:caller).returns('uid=600')
request.stubs(:action).returns('status')
Util.stubs(:get_fact).with('customer').returns('acme')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
# fith field
request.stubs(:caller).returns('uid=700')
request.stubs(:action).returns('restart')
Util.stubs(:get_fact).with('environment').returns('development')
Matcher.stubs(:eval_compound_fstatement).with('value' => 'enabled', 'name' => 'puppet', 'operator' => '==', 'params' => nil, 'r_compare' => 'false').returns(true)
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
end
it 'should parse example16 correctly' do
# match uid in the list
request.stubs(:caller).returns('uid=600')
actionpolicy = ActionPolicy.new(request)
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example16')).should be_true
# match uid not in the list
request.stubs(:caller).returns('uid=800')
actionpolicy = ActionPolicy.new(request)
expect{
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example16'))
}.to raise_error RPCAborted
end
end
describe '#check_policy' do
it 'should return false if the policy line does not include the caller' do
actionpolicy.check_policy('caller', nil, nil, nil).should be_false
end
it 'should return false if the policy line does not include the action' do
actionpolicy.check_policy(nil, 'action', nil, nil).should be_false
end
it 'should parse both facts and classes if callers and actions match' do
actionpolicy.expects(:parse_facts).with('*').returns(true)
actionpolicy.expects(:parse_classes).with('*').returns(true)
actionpolicy.check_policy('rspec_caller', 'rspec_action', '*', '*').should be_true
end
it 'should parse a compound statement if callers and actions match but classes are excluded' do
actionpolicy.expects(:parse_compound).with('*').returns(true)
actionpolicy.check_policy('rspec_caller', 'rspec_action', '*', nil).should be_true
end
end
describe '#parse_facts' do
it 'should return true if facts is a wildcard' do
actionpolicy.parse_facts('*').should be_true
end
it 'should parse compound fact statements' do
actionpolicy.stubs(:is_compound?).returns(true)
actionpolicy.expects(:parse_compound).with('foo=bar and bar=foo').returns(true)
actionpolicy.parse_facts('foo=bar and bar=foo').should be_true
end
it 'should parse all facts' do
actionpolicy.stubs(:is_compound?).returns(false)
actionpolicy.expects(:lookup_fact).twice.returns(true)
actionpolicy.parse_facts('foo=bar bar=foo').should be_true
end
end
describe '#parse_classes' do
it 'should return true if classes is a wildcard' do
actionpolicy.parse_classes('*').should be_true
end
it 'should parse compound class statements' do
actionpolicy.stubs(:is_compound?).returns(true)
actionpolicy.expects(:parse_compound).with('foo=bar and bar=foo').returns(true)
actionpolicy.parse_facts('foo=bar and bar=foo').should be_true
end
it 'should parse all classes' do
actionpolicy.stubs(:is_compound?).returns(false)
actionpolicy.expects(:lookup_fact).times(3).returns(true)
actionpolicy.parse_facts('foo bar baz').should be_true
end
end
describe '#lookup_fact' do
it 'should return false if a class is found in the fact field' do
Log.expects(:warn).with('Class found where fact was expected')
actionpolicy.lookup_fact('rspec').should be_false
end
it 'should lookup a fact value and return its true value' do
Util.expects(:get_fact).with('foo').returns('bar')
actionpolicy.lookup_fact('foo=bar').should be_true
end
end
describe '#lookup_class' do
it 'should return false if a fact is found in the class field' do
Log.expects(:warn).with('Fact found where class was expected')
actionpolicy.lookup_class('foo=bar').should be_false
end
it 'should lookup a fact value and return its true value' do
Util.expects(:has_cf_class?).with('rspec').returns(true)
actionpolicy.lookup_class('rspec').should be_true
end
end
describe '#lookup' do
it 'should call #lookup_fact if a fact was passed' do
actionpolicy.expects(:lookup_fact).with('foo=bar').returns(true)
actionpolicy.lookup('foo=bar').should be_true
end
it 'should call #lookup_class if a class was passed' do
actionpolicy.expects(:lookup_class).with('/rspec/').returns(true)
actionpolicy.lookup('/rspec/').should be_true
end
end
describe '#lookup_policy_file' do
before do
Log.stubs(:debug)
end
it 'should return the path of the policyfile is present' do
File.expects(:exist?).with('/rspecdir/policies/rspec_agent.policy').returns(true)
actionpolicy.lookup_policy_file.should == '/rspecdir/policies/rspec_agent.policy'
end
it 'should return the default file path if one is specified' do
config.stubs(:pluginconf).returns({'actionpolicy.enable_default' => '1'})
File.expects(:exist?).with('/rspecdir/policies/rspec_agent.policy').returns(false)
File.expects(:exist?).with('/rspecdir/policies/default.policy').returns(true)
actionpolicy.lookup_policy_file.should == '/rspecdir/policies/default.policy'
end
it 'should return a custom default file path if one is specified' do
config.stubs(:pluginconf).returns({'actionpolicy.enable_default' => '1',
'actionpolicy.default_name' => 'rspec'})
File.expects(:exist?).with('/rspecdir/policies/rspec_agent.policy').returns(false)
File.expects(:exist?).with('/rspecdir/policies/rspec.policy').returns(true)
actionpolicy.lookup_policy_file.should == '/rspecdir/policies/rspec.policy'
end
it 'should return nil if no policy file exists' do
File.expects(:exist?).with('/rspecdir/policies/rspec_agent.policy').returns(false)
actionpolicy.lookup_policy_file.should == nil
end
end
describe '#eval_statement' do
it 'should return the logical string if param is not an statement or fstatement' do
actionpolicy.eval_statement({'and' => 'and'}).should == 'and'
end
it 'should lookup the value of a statement if param is a statement' do
actionpolicy.expects(:lookup).with('foo=bar').returns(true)
actionpolicy.eval_statement({'statement' => 'foo=bar'}).should be_true
end
it 'should lookup the value of a data function if param is a fstatement' do
Matcher.expects(:eval_compound_fstatement).with("rspec('data').value=result").returns(true)
actionpolicy.eval_statement({'fstatement' => "rspec('data').value=result"}).should be_true
end
it 'should log a failure message and return false if the fstatement cannot be parsed' do
Matcher.expects(:eval_compound_fstatement).with("rspec('data').value=result").raises('error')
Log.expects(:warn).with('Could not call Data function in policy file: error')
actionpolicy.eval_statement({'fstatement' => "rspec('data').value=result"}).should be_false
end
end
describe '#is_compound?' do
it 'should return false if a compound statement was not identified' do
actionpolicy.is_compound?('not').should be_true
actionpolicy.is_compound?('!rspec').should be_true
actionpolicy.is_compound?('and').should be_true
actionpolicy.is_compound?('or').should be_true
actionpolicy.is_compound?("data('field').value=othervalue").should be_true
end
it 'should return true if a compound statement was identified' do
actionpolicy.is_compound?('f1=v1 f1=v2').should be_false
actionpolicy.is_compound?('class1 class2 /class*/').should be_false
end
end
describe '#deny' do
it 'should log the failure and raise an RPCAborted error' do
Log.expects(:debug).with('fail')
expect{
actionpolicy.deny('fail')
}.to raise_error RPCAborted
end
end
end
end
end