def test_guard_does_not_fail_if_storage_returns_none(logger): class BadStorage(MemoryStorage): def find_for_inquiry(self, inquiry, checker=None): return None # set up logging log_capture_str = io.StringIO() h = logging.StreamHandler(log_capture_str) h.setLevel(logging.ERROR) logger.setLevel(logging.ERROR) logger.addHandler(h) # set up Guard storage = BadStorage() storage.add( Policy( uid='1', effect=ALLOW_ACCESS, subjects=['Max'], actions=['watch'], resources=['TV'], )) g = Guard(storage, RegexChecker()) assert not g.is_allowed( Inquiry(subject='Max', action='watch', resource='TV')) assert 'Storage returned None, but is supposed to return at least an empty list' == \ log_capture_str.getvalue().strip()
def test_guard_uses_audit_correctly(audit_log, inquiry, is_allowed, expect_msg): # setup logger consumer log_capture_str = io.StringIO() h = logging.StreamHandler(log_capture_str) h.setFormatter(logging.Formatter( 'msg: %(message)s | effect: %(effect)s | deciders: %(deciders)s | candidates: %(candidates)s | ' + 'inquiry: %(inquiry)s' )) h.setLevel(logging.INFO) audit_log.setLevel(logging.INFO) audit_log.addHandler(h) # setup guard st = MemoryStorage() st.add(PolicyAllow(uid='a', subjects=['Max'], actions=['<.*>'], resources=['<.*>'])) st.add(PolicyAllow(uid='b', subjects=['Max'], actions=['get'], resources=['<.*>'])) st.add(PolicyAllow(uid='c', subjects=['Jim'], actions=['<.*>'], resources=['<.*>'])) st.add(PolicyDeny(uid='d', subjects=['Jim'], actions=['<.*>'], resources=['<.*>'])) st.add(PolicyAllow(uid='e')) g = Guard(st, RegexChecker()) # Run tests assert is_allowed == g.is_allowed(inquiry) result = log_capture_str.getvalue().strip() # a little hack to get rid of <Object ID 4502567760> in inquiry output result = re.sub(r'<Object ID \d+>', '<Object ID some_ID>', result) assert expect_msg == result
def test_find_for_inquiry_with_regex_checker(self, st, policies, inquiry, expected_reference): mem_storage = MemoryStorage() # it returns all stored policies so we consider Guard as a reference for p in policies: st.add(p) mem_storage.add(p) reference_answer = Guard(mem_storage, RegexChecker()).is_allowed(inquiry) assert expected_reference == reference_answer, 'Check reference answer' assert reference_answer == Guard(st, RegexChecker()).is_allowed(inquiry), \ 'SQL storage should give the same answers as reference'
def test_guard_if_unexpected_exception_raised(): # for testing unexpected exception class BadMemoryStorage(MemoryStorage): def find_for_inquiry(self, inquiry=None, checker=None): raise Exception('This is test class that raises errors') g = Guard(BadMemoryStorage(), RegexChecker()) assert not g.is_allowed( Inquiry(subject='foo', action='bar', resource='baz'))
def test_guard_logs_messages_at_info_level(audit_log): log_capture_str = io.StringIO() h = logging.StreamHandler(log_capture_str) h.setLevel(logging.INFO) audit_log.setLevel(logging.INFO) audit_log.addHandler(h) g = Guard(MemoryStorage(), RegexChecker()) g.is_allowed(Inquiry()) assert 'No potential' in log_capture_str.getvalue().strip()
def test_guard_does_not_log_messages_at_more_than_info_level(audit_log): log_capture_str = io.StringIO() h = logging.StreamHandler(log_capture_str) h.setLevel(logging.WARN) audit_log.setLevel(logging.WARN) audit_log.addHandler(h) g = Guard(MemoryStorage(), RegexChecker()) g.is_allowed(Inquiry()) assert '' == log_capture_str.getvalue().strip()
def test_guard_can_use_specific_policies_message_class(audit_log): # setup logger consumer log_capture_str = io.StringIO() h = logging.StreamHandler(log_capture_str) h.setFormatter(logging.Formatter('decs: %(deciders)s, candidates: %(candidates)s')) h.setLevel(logging.INFO) audit_log.setLevel(logging.INFO) audit_log.addHandler(h) # setup guard st = MemoryStorage() st.add(PolicyAllow('122')) st.add(PolicyAllow('123', actions=['<.*>'], resources=['<.*>'], subjects=['<.*>'])) st.add(PolicyDeny('124', actions=['<.*>'], resources=['<.*>'], subjects=['<.*>'])) st.add(PolicyDeny('125', actions=['<.*>'], resources=['<.*>'], subjects=['<.*>'])) g = Guard(st, RegexChecker(), audit_policies_cls=PoliciesCountMsg) # Run tests g.is_allowed(Inquiry(action='get', subject='Kim', resource='TV')) assert 'decs: count = 1, candidates: count = 3' == log_capture_str.getvalue().strip()
def test_guard_logs_inquiry_decision(logger, inquiry, result, expect_message): # set up logging log_capture_str = io.StringIO() h = logging.StreamHandler(log_capture_str) h.setLevel(logging.INFO) logger.setLevel(logging.INFO) logger.addHandler(h) # set up Guard storage = MemoryStorage() storage.add( Policy( uid='1', effect=ALLOW_ACCESS, subjects=['Max'], actions=['watch'], resources=['TV'], )) g = Guard(storage, RegexChecker()) assert result == g.is_allowed(inquiry) log_res = log_capture_str.getvalue().strip() # a little hack to get rid of <Object ID 4502567760> in inquiry output log_res = re.sub(r'<Object ID \d+>', '<Object ID some_ID>', log_res) assert expect_message == log_res
def test_not_allowed_when_similar_policies_have_at_least_one_deny_access(): st = MemoryStorage() policies = ( Policy( uid='1', effect=ALLOW_ACCESS, subjects=['foo'], actions=['bar'], resources=['baz'], ), Policy( uid='2', effect=DENY_ACCESS, subjects=['foo'], actions=['bar'], resources=['baz'], ), ) for p in policies: st.add(p) g = Guard(st, RegexChecker()) assert not g.is_allowed( Inquiry(subject='foo', action='bar', resource='baz'))
def test_policy_inquiry_checker_examples(desc, policy, inquiry, checker, should_be_allowed): storage = MemoryStorage() storage.add(policy) g = Guard(storage, checker) assert should_be_allowed == g.is_allowed(inquiry)
def test_is_allowed(desc, inquiry, should_be_allowed): g = Guard(st, RegexChecker()) assert should_be_allowed == g.is_allowed(inquiry)
def test_up(self, storage): migration = Migration1x1x0To1x1x1(storage) # prepare docs that might have been saved by users in v 1.1.0 docs = [ (""" { "_id" : 10, "uid" : 10, "description" : null, "subjects" : [ ], "effect" : "allow", "resources" : [ ], "actions" : [ ], "rules" : { "secret" : "{\\"type\\": \\"vakt.rules.string.StringEqualRule\\", \\"contents\\": {\\"val\\": \\"i-am-a-foo\\"}}", "name":"{\\"type\\": \\"vakt.rules.string.StringEqualRule\\", \\"contents\\":{\\"val\\": \\"Max\\"}}" }} """, """ { "_id" : 10, "actions" : [ ], "description" : null, "effect" : "allow", "resources" : [ ], "rules" : { "name" : {"py/object": "vakt.rules.string.StringEqualRule", "val": "Max" }, "secret" : {"py/object": "vakt.rules.string.StringEqualRule", "val": "i-am-a-foo"} }, "subjects" : [ ], "uid" : 10 } """), (""" { "_id" : 20, "uid" : 20, "description" : "foo bar", "subjects" : [ "<.*>" ], "effect" : "allow", "resources" : [ "<.*>" ], "actions" : [ "<.*>" ], "rules" : { "secret" : "{\\"type\\": \\"vakt.rules.string.StringEqualRule\\", \\"contents\\": {\\"val\\": \\"John\\"}}" } } """, """ { "_id" : 20, "actions" : [ "<.*>" ], "description" : "foo bar", "effect" : "allow", "resources" : [ "<.*>" ], "rules" : { "secret" : { "py/object": "vakt.rules.string.StringEqualRule", "val": "John"} }, "subjects" : [ "<.*>" ], "uid" : 20 } """), (""" { "_id" : 30, "uid" : 30, "description" : "foo bar", "subjects" : [ "<.*>" ], "effect" : "allow", "resources" : [ "<.*>" ], "actions" : [ "<.*>" ], "rules" : { } } """, """ { "_id" : 30, "actions" : [ "<.*>" ], "description" : "foo bar", "effect" : "allow", "resources" : [ "<.*>" ], "rules" : { }, "subjects" : [ "<.*>" ], "uid" : 30 } """), (""" { "_id" : 40, "uid" : 40, "description" : null, "subjects" : [ "<.*>" ], "effect" : "allow", "resources" : [ "<.*>" ], "actions" : [ "<.*>" ], "rules" : { "num" : "{\\"type\\": \\"storage.test_mongo_migration.Simple\\", \\"contents\\": {\\"val\\": \\"123\\"}}", "a" : "{\\"type\\": \\"vakt.rules.string.StringEqualRule\\", \\"contents\\": {\\"val\\": \\"foo\\"}}" }} """, """ { "_id" : 40, "actions" : [ "<.*>" ], "description" : null, "effect" : "allow", "resources" : ["<.*>"], "rules" : { "a" : {"py/object": "vakt.rules.string.StringEqualRule", "val": "foo"}, "num" : { "py/object": "storage.test_mongo_migration.Simple", "val": "123"} }, "subjects" : ["<.*>"], "uid" : 40 } """), (""" { "_id" : 50, "uid" : 50, "description" : null, "subjects" : [ ], "effect" : "allow", "resources" : [ ], "actions" : [ ], "rules" : { "num" : "{\\"type\\": \\"storage.test_mongo_migration.Simple\\", \\"contents\\": {\\"val\\": \\"46\\"}}" } } """, """ { "_id" : 50, "actions" : [ ], "description" : null, "effect" : "allow", "resources" : [ ], "rules" : { "num" : { "py/object": "storage.test_mongo_migration.Simple", "val": "46"} }, "subjects" : [ ], "uid" : 50 } """), ] for (doc, _) in docs: d = b_json.loads(doc) migration.storage.collection.insert_one(d) migration.up() # test no new docs were added and no docs deleted assert len(docs) == len(list(migration.storage.collection.find({}))) # test Policy.from_json() is called without errors for each doc (implicitly) assert len(docs) == len(list(migration.storage.get_all(1000, 0))) # test string contents of each doc for (doc, result_doc) in docs: new_doc = migration.storage.collection.find_one( {'uid': json.loads(doc)['uid']}) expected = result_doc.replace("\n", '').replace(' ', '') actual = json.dumps(new_doc, sort_keys=True).replace("\n", '').replace(' ', '') assert expected == actual # test full guard allowance run if version_info() < (1, 2, 0): # pragma: no cover g = Guard(migration.storage, RegexChecker()) inq = Inquiry(action='foo', resource='bar', subject='Max', context={ 'val': 'foo', 'num': '123' }) assert g.is_allowed(inq)
print() print() def single_inquiry_benchmark(): global guard if guard.is_allowed(inq): return True return False overall_policies_created = 0 similar_regexp_policies_created = 0 store = MemoryStorage() checker = RegexChecker(CACHE_SIZE) if CACHE_SIZE else RegexChecker() guard = Guard(store, checker) inq = Inquiry(action='get', subject='xo', resource='library:books:1234', context={'ip': '127.0.0.1'}) if __name__ == '__main__': line_length = 80 print('=' * line_length) print('Populating MemoryStorage with Policies') print_generation(populate_storage, int(POLICIES_NUMBER / 100 * 1), line_length) print('START BENCHMARK!') start = timeit.default_timer() allowed = single_inquiry_benchmark() stop = timeit.default_timer()
def test_is_allowed_for_inquiry_match_rules(desc, policy, inquiry, result): storage = MemoryStorage() storage.add(policy) g = Guard(storage, RulesChecker()) assert result == g.is_allowed(inquiry), 'Failed for case: ' + desc
def test_is_allowed(desc, inquiry, should_be_allowed, checker): g = Guard(st, checker) assert should_be_allowed == g.is_allowed(inquiry)
def test_is_allowed_for_none_policies(): g = Guard(MemoryStorage(), RegexChecker()) assert not g.is_allowed( Inquiry(subject='foo', action='bar', resource='baz'))
def test_is_allowed(desc, inquiry, should_be_allowed, checker): # Create all required test policies st = MemoryStorage() policies = [ Policy( uid='1', description=""" Max, Nina, Ben, Henry are allowed to create, delete, get the resources only if the client IP matches and the inquiry states that any of them is the resource owner """, effect=ALLOW_ACCESS, subjects=('Max', 'Nina', '<Ben|Henry>'), resources=('myrn:example.com:resource:123', 'myrn:example.com:resource:345', 'myrn:something:foo:<.+>'), actions=('<create|delete>', 'get'), context={ 'ip': CIDR('127.0.0.1/32'), 'owner': SubjectEqual(), }, ), Policy( uid='2', description='Allows Max to update any resource', effect=ALLOW_ACCESS, subjects=['Max'], actions=['update'], resources=['<.*>'], ), Policy( uid='3', description='Max is not allowed to print any resource', effect=DENY_ACCESS, subjects=['Max'], actions=['print'], resources=['<.*>'], ), Policy(uid='4'), Policy( uid='5', description= 'Allows Nina to update any resources that have only digits', effect=ALLOW_ACCESS, subjects=['Nina'], actions=['update'], resources=[r'<[\d]+>'], ), Policy( uid='6', description= 'Allows Nina to update any resources that have only digits. Defined by rules', effect=ALLOW_ACCESS, subjects=[Eq('Nina')], actions=[Eq('update'), Eq('read')], resources=[{ 'id': RegexMatch(r'\d+'), 'magazine': RegexMatch(r'[\d\w]+') }], ), ] for p in policies: st.add(p) g = Guard(st, checker) assert should_be_allowed == g.is_allowed(inquiry)