def get_inquiry(): if ARGS.checker == 'rules': return Inquiry( subject={'name': 'Nick', 'stars': 900, 'status': 'registered'}, resource={'method': ['post', 'get'], 'path': '/acme/users', 'id': rand_string()}, action={'before': 'foo', 'after': rand_string()}, context={'ip': '127.0.0.1'} ) return Inquiry(action='get', subject='xo', resource='library:books:1234', context={'ip': '127.0.0.1'})
def authorize_aa_resource(user, handle_id, get_aa_func): ''' This function checks if an user is authorized to do a specific action over a node specified by its handle_id. It forges an inquiry and check it against vakt's guard. ''' ret = False # deny by default # get storage and guard storage, guard = get_vakt_storage_and_guard() # get authaction authaction = get_aa_func() # get contexts for this resource nodehandle = NodeHandle.objects.prefetch_related('contexts').get( handle_id=handle_id) contexts = get_nh_contexts(nodehandle) # forge read resource inquiry inquiry = Inquiry(action=authaction.name, resource=nodehandle, subject=user, context={'module': contexts}) ret = guard.is_allowed(inquiry) return ret
def test_general_flow(self, log_mock): cache_storage = MemoryStorage() back_storage = MemoryStorage() inq = Inquiry(action='get') chk1 = RulesChecker() p1 = Policy(1, description='initial') p2 = Policy(2, description='initial') p3 = Policy(3, description='added later') # initialize backend storage back_storage.add(p1) back_storage.add(p2) # create enfold-cache but do not populate it ec = EnfoldCache(back_storage, cache=cache_storage, populate=False) # make sure policies are returned from the backend assert [p1, p2] == list(ec.find_for_inquiry(inquiry=inq, checker=chk1)) # make sure we logged warning about cache miss log_mock.warning.assert_called_with( '%s cache miss for find_for_inquiry. Trying it from backend storage', 'EnfoldCache') log_mock.reset_mock() # populate cache with backend policies so that we do not make cache hit misses ec.populate() # make sure policies are returned assert [p1, p2] == list(ec.find_for_inquiry(inquiry=inq, checker=chk1)) # make sure we do not have cache misses assert 0 == log_mock.warning.call_count log_mock.reset_mock() # let's add a new policy via enfold-cache ec.add(p3) # make sure policies are returned assert [p1, p2, p3] == list(ec.find_for_inquiry(inquiry=inq, checker=chk1)) # make sure we do not have cache misses assert 0 == log_mock.warning.call_count log_mock.reset_mock() # make sure we have those policies in the backend-storage assert [p1, p2, p3] == list( back_storage.find_for_inquiry(inquiry=inq, checker=chk1)) # make sure we have those policies in the cache-storage assert [p1, p2, p3] == list( cache_storage.find_for_inquiry(inquiry=inq, checker=chk1)) # ----------------------- # ----------------------- # ----------------------- # let's re-create enfold cache. This time with initial population cache_storage2 = MemoryStorage() ec2 = EnfoldCache(back_storage, cache=cache_storage2, populate=True) # make sure we have all policies in the cache-storage assert [p1, p2, p3] == list( cache_storage2.find_for_inquiry(inquiry=inq, checker=chk1)) # make sure policies are returned by find_for_inquiry assert [p1, p2, p3] == list(ec2.find_for_inquiry(inquiry=inq, checker=chk1)) # make sure we do not have cache misses assert 0 == log_mock.warning.call_count log_mock.reset_mock()
def test_cache_is_invalidated_on_policy_change(self): def assert_after_modification(): assert 0 == cache.info().hits assert 0 == cache.info().misses assert 0 == cache.info().currsize assert not guard.is_allowed(inq1) assert not guard.is_allowed(inq1) assert guard.is_allowed(inq2) assert guard.is_allowed(inq2) assert guard.is_allowed(inq2) assert 3 == cache.info().hits assert 2 == cache.info().misses assert 2 == cache.info().currsize inq1 = Inquiry(action='get', resource='book', subject='Max') inq2 = Inquiry(action='get', resource='book', subject='Jim') guard, storage, cache = create_cached_guard(MemoryStorage(), RulesChecker(), maxsize=256) p1 = Policy(1, actions=[Eq('get')], resources=[Eq('book')], subjects=[Eq('Max')], effect=ALLOW_ACCESS) p2 = Policy(2, actions=[Eq('get')], resources=[Eq('magazine')], subjects=[Eq('Max')], effect=ALLOW_ACCESS) storage.add(p1) assert guard.is_allowed(inq1) assert guard.is_allowed(inq1) assert 1 == cache.info().hits assert 1 == cache.info().misses assert 1 == cache.info().currsize # start modifications p1.subjects = [Eq('Jim')] storage.update(p1) assert_after_modification() storage.add(p2) assert_after_modification() storage.delete(p2) assert_after_modification()
def login(): user = request.form['name'] inquiry = Inquiry(subject=user, action='login', context={'ip': request.remote_addr}) if guard.is_allowed(inquiry): # check password here session['fullname'] = user session['secret'] = request.form.get('secret', '') return "You've been logged-in", 200 else: return "Go away, you violator!", 401
def serve_book(action, book): inquiry = Inquiry(subject=session.get('fullname'), action=action, resource='library:books:%s' % book, context={ 'ip': request.remote_addr, 'secret': session.get('secret', '') }) if guard.is_allowed(inquiry): return "Enjoy! Here is the book you requested: '{}'!".format(book), 200 else: return 'Sorry, but you are not allowed to do this!', 401
def test_same_inquiries_are_cached(self): guard, storage, cache = create_cached_guard(MemoryStorage(), RulesChecker(), maxsize=256) p1 = Policy(1, actions=[Eq('get')], resources=[Eq('book')], subjects=[Eq('Max')], effect=ALLOW_ACCESS) storage.add(p1) inq1 = Inquiry(action='get', resource='book', subject='Max') inq2 = Inquiry(action='get', resource='book', subject='Jamey') assert guard.is_allowed(inq1) assert guard.is_allowed(inq1) assert guard.is_allowed(inq1) assert guard.is_allowed(inq1) assert guard.is_allowed(inq1) assert not guard.is_allowed(inq2) assert 4 == cache.info().hits assert 2 == cache.info().misses assert 2 == cache.info().currsize
def test_find_for_inquiry_return_value(self, storage): cache_storage = storage back_storage = MemoryStorage() ec = EnfoldCache(back_storage, cache=cache_storage) inq = Inquiry(action='get') p1 = Policy(1) p2 = Policy(2) ec.add(p1) ec.add(p2) backend_return = back_storage.find_for_inquiry(inq) ec_return = ec.find_for_inquiry(inq) assert list(backend_return) == list(ec_return)
def auth_user(self, req, mac, identity, **_kwargs): # TODO Investigate the use of diffie-hellman log('%s (%s) authenticated successfuly' % (identity, mac)) ip = IEDS[identity]['ip'] port = IEDS[identity]['port'] log('Installing MMS flows (%s <-> controller)' % identity) add_mms_flow(self.s1, mac, ip) add_mms_flow(self.s2, mac, ip, port) log('Controller connecting to %s (MMS)' % identity) with Mms(ip) as ied: # TODO create second read for the sub publish_goose_to = ied.read() # TODO fix messages if publish_goose_to in self.authenticated: action = 'subscribe' else: action = 'publish' log('%s wants to %s GOOSE %s frames' % (identity, action, publish_goose_to)) log('ABAC: can %s %s GOOSE %s frames?' % (identity, action, publish_goose_to)) publish_goose = Inquiry(action={ 'type': action, 'dest': publish_goose_to }, resource='GOOSE', subject=identity) if guard.is_allowed(publish_goose): self.authenticated[mac] = {'address': mac, 'identity': identity} if publish_goose_to in self.authenticated: log('%s permited to subscribe GOOSE %s frames' % (identity, publish_goose_to)) log('Installing flows requested by %s' % identity) add_goose_flow(self.s2, self.authenticated[publish_goose_to]['address'], publish_goose_to, 2, IEDS[identity]['port']) else: self.authenticated[publish_goose_to] = { 'address': mac, 'identity': identity } log('%s (%s) authorized to subscribe %s GOOSE frames' % (identity, mac, publish_goose_to)) body = json.dumps("{'AUTH-OK':" + str(self.authenticated[mac]) + '}') return Response(content_type='application/json', body=body) else: # TODO Fix hostapd to send Failure to supp if NOT-OK log("{'NOT-OK': 'Access not granted'}") return Response(status=400)
def test_find_for_inquiry(self, factory): st, mem, observer = factory() inq = Inquiry(action='get', subject='foo', resource='bar') p1 = Policy('a') p2 = Policy('b') st.add(p1) st.add(p2) mem_found = list(mem.find_for_inquiry(inq)) assert [p1, p2] == mem_found or [p2, p1] == mem_found st_found = list(st.find_for_inquiry(inq)) assert [p1, p2] == st_found or [p2, p1] == st_found assert 2 == observer.count st.find_for_inquiry(inq) st.find_for_inquiry(inq) assert 2 == observer.count
def test_find_for_inquiry_for_populated_cache(self, log_mock): cache_storage = MemoryStorage() back_storage = MemoryStorage() inq = Inquiry(action='get') chk1 = RulesChecker() p1 = Policy(1, description='foo') p2 = Policy(2, description='bar') cache_storage.add(p1) cache_storage.add(p2) ec = EnfoldCache(back_storage, cache=cache_storage, populate=True) # Make first request assert [p1, p2] == list(ec.find_for_inquiry(inquiry=inq, checker=chk1)) assert 0 == log_mock.warning.call_count log_mock.reset_mock() # Make second request assert [p1, p2] == list(ec.find_for_inquiry(inquiry=inq, checker=chk1)) assert 0 == log_mock.warning.call_count
def test_find_for_inquiry_for_non_populated_cache(self, log_mock): cache_storage = MemoryStorage() back_storage = MemoryStorage() inq = Inquiry(action='get') chk1 = RulesChecker() p1 = Policy(1, description='foo') p2 = Policy(2, description='bar') back_storage.add(p1) back_storage.add(p2) ec = EnfoldCache(back_storage, cache=cache_storage, populate=False) # Make first request assert [p1, p2] == list(ec.find_for_inquiry(inquiry=inq, checker=chk1)) log_mock.warning.assert_called_with( '%s cache miss for find_for_inquiry. Trying it from backend storage', 'EnfoldCache') log_mock.reset_mock() # Make second request assert [p1, p2] == list(ec.find_for_inquiry(inquiry=inq, checker=chk1)) assert 1 == log_mock.warning.call_count log_mock.reset_mock()
def authorize_aa_operation(user, context, get_aa_func): ''' This function authorizes an action within a particular context, it checks if the user can perform that action within this SRI module ''' ret = False # deny by default # get storage and guard storage, guard = get_vakt_storage_and_guard() # get authaction authaction = get_aa_func() # forge read resource inquiry inquiry = Inquiry(action=authaction.name, resource=None, subject=user, context={'module': (context.name, )}) ret = guard.is_allowed(inquiry) return ret
import Pyro4 from vakt import Inquiry Pyro4.config.SERIALIZER = 'pickle' gh = Pyro4.Proxy('PYRO:github.guardian@localhost:9999') # regular user tries to access Google repositories assert gh.check(Inquiry(action='get', resource='repos/google/tensorflow', subject='Max')) assert gh.check(Inquiry(action='list', resource='repos/google/tensors-ui', subject='Jimmy')) assert not gh.check(Inquiry(action='list', resource='repos/google/clusterfuzz', subject='Jimmy')) # deletion requests assert not gh.check(Inquiry(action='delete', resource='repos/facebook/react', subject='Max')) assert not gh.check(Inquiry(action='exterminate', resource='repos/facebook/react', subject={'name': 'Max'})) assert gh.check(Inquiry(action='delete', resource='repos/facebook/react', subject='defunkt')) assert gh.check(Inquiry(action='prune', resource='repos/facebook/php', subject={'name': 'defunkt'})) assert gh.check(Inquiry(action='prune', resource='repos/google/', subject={'name': 'defunkt', 'role': 'non-admin'})) assert gh.check(Inquiry(action='delete', resource='repos/facebook/ui', subject={'name': 'Jim', 'role': 'admin'})) # administration panel requests assert not gh.check(Inquiry(action='maintenance', resource={'category': 'administration', 'sub': 'panel'}, subject={'name': 'Jim'}, context={'ip': '175.8.20.1'})) assert not gh.check(Inquiry(action='put-on-fire', resource={'category': 'administration', 'sub': 'switch'}, subject={'name': 'Jim', 'role': 'developer'},
def get_http_req_access(self, req, subject_data, opt_resource=None): """ Transforms an HTTP request (req) and retrieves subject data in a PDP request. Evaluates if the user/subject given has access given its current attributes. Returs result as a boolean. """ if not subject_data: return False subject_data.update( {'admin': self._pgdb.isAdmin(subject_data['iupi'])}) resource_path = req.path.split("/") resource = {} if resource_path[1] in ['rooms', 'types', 'sensors']: resource.update(opt_resource) if opt_resource and 'sensor' in opt_resource: sensor_type = str( self._pgdb.getSensorTypeID(opt_resource['sensor'])) try: sensor_roomid = (self._pgdb.getSensor( opt_resource['sensor']))['room_id'] resource.update({ 'type': sensor_type, 'room': sensor_roomid }) except TypeError: resource.update({'type': sensor_type}) elif resource_path[1] == 'type': # get/modify/delete type if len(resource_path) >= 3: resource.update({resource_path[1]: resource_path[2]}) if len(resource_path) > 3: # it's a sensor of a type if opt_resource: resource.update(opt_resource) sensor_type = str( self._pgdb.getSensorTypeID(opt_resource['sensor'])) resource.update({'type': sensor_type}) # create type elif opt_resource: resource.update(opt_resource) elif resource_path[1] == 'room': # it's a get/modify/delete on a specific room if len(resource_path) >= 3: resource.update({resource_path[1]: resource_path[2]}) # it's a sensor on a room (list get) if len(resource_path) > 3: # get its sensors if resource_path[3] == "sensors": if opt_resource: resource.update(opt_resource) sensor_type = str( self._pgdb.getSensorTypeID(opt_resource['sensor'])) resource.update({'type': sensor_type}) elif resource_path[3] == "types": # it's a type on a room (list get) if opt_resource: resource.update(opt_resource) elif len(resource_path) == 3: if opt_resource: resource.update(opt_resource) sensor_type = str( self._pgdb.getSensorTypeID(opt_resource['sensor'])) resource.update({'type': sensor_type}) elif opt_resource: # it's an update or delete resource.update(opt_resource) elif resource_path[1] == 'sensor': if len(resource_path) < 3: # create sensor if opt_resource: resource.update(opt_resource) else: # get the respective sensor's room and type attributes alongside its id (for any action) sensor_type = str(self._pgdb.getSensorTypeID(resource_path[2])) try: sensor_roomid = (self._pgdb.getSensor( resource_path[2]))['room_id'] resource.update({ resource_path[1]: resource_path[2], 'type': sensor_type, 'room': sensor_roomid }) except TypeError: resource.update({ resource_path[1]: resource_path[2], 'type': sensor_type }) # request a value from a sensor if len(resource_path) > 3 and resource_path[3] == 'measure': resource.update({resource_path[3]: resource_path[4]}) current_date = arrow.utcnow() time = current_date.strftime("%H:%M:%S") day = current_date.strftime("%Y-%m-%dT%H:%M:%SZ") inq = Inquiry( subject=subject_data, action=req.method, resource=resource, #context={'hour': ABAC.daytime_in_s(time), 'date': ABAC.unix_timestamp(day)} context={ 'ip': req.headers['X-Forwarded-For'].split(',')[0].strip(), 'hour': ABAC.daytime_in_s(time), 'date': ABAC.unix_timestamp(day) }) print(inq.to_json()) g = Guard(self._storage, RulesChecker()) res = g.is_allowed(inq) print(res) return res
def test_rulechecker(self): # ensure empty policy set for dbpolicy in self.storage.get_all(): self.storage.delete(dbpolicy.uid) # add policies policy1 = Policy( uuid.uuid4(), actions=[vrules.Eq('read')], resources=[vrules.StartsWith('forum/')], subjects=[{ 'group': vrules.In('can_read', 'can_write', 'can_admin') }], context={'module': vrules.Eq('forum')}, effect=ALLOW_ACCESS, description= 'Grant read-access to the forum section to users with a certain profile' ) policy2 = Policy( uuid.uuid4(), actions=[vrules.Eq('write')], resources=[vrules.StartsWith('forum/')], subjects=[{ 'group': vrules.In('can_write', 'can_admin') }], context={'module': vrules.Eq('forum')}, effect=ALLOW_ACCESS, description= 'Grant write-access to the forum section to users with a certain profile' ) policy3 = Policy( uuid.uuid4(), actions=[vrules.Eq('admin')], resources=[vrules.StartsWith('forum/')], subjects=[{ 'group': vrules.In('can_admin') }], context={'module': vrules.Eq('forum')}, effect=ALLOW_ACCESS, description= 'Grant admin-access to the forum section to users with a certain profile' ) policy4 = Policy( uuid.uuid4(), actions=[vrules.Any()], resources=[vrules.StartsWith('forum/')], subjects=[{ 'group': vrules.NotIn('can_read', 'can_write', 'can_admin') }], context={'module': vrules.Eq('forum')}, effect=DENY_ACCESS, description='Deny access to any user without a group defined') self.storage.add(policy1) self.storage.add(policy2) self.storage.add(policy3) self.storage.add(policy4) # forge successful inquiry for 1st policy inqu1_ok = Inquiry(action='read', resource='forum/users/list', subject={ 'name': 'Jane', 'group': 'can_read' }, context={'module': 'forum'}) assert self.guard.is_allowed( inqu1_ok), "This inquiry should be allowed" inq1_ko = Inquiry(action='read', resource='forum/users/list', subject={ 'name': 'James', 'group': 'new_users' }, context={'module': 'forum'}) assert not self.guard.is_allowed( inq1_ko), "This inquiry should be denied"
def test_crud(self): for dbpolicy in self.storage.get_all(): self.storage.delete(dbpolicy.uid) # create policies self.policy1 = Policy( uuid.uuid4(), actions=[vrules.In('get', 'list', 'read')], resources=[vrules.StartsWith('repos/google/tensor')], subjects=[{ 'name': vrules.Any(), 'role': vrules.Any() }], context={'module': vrules.Eq('Test')}, effect=ALLOW_ACCESS, description= 'Grant read-access for all Google repositories starting with "tensor" to any User' ) self.policy2 = Policy( uuid.uuid4(), actions=[vrules.In('delete', 'prune', 'exterminate')], resources=[vrules.StartsWith('repos/')], subjects=[{ 'name': vrules.Any(), 'role': vrules.Eq('admin') }], context={'module': vrules.Eq('Test')}, effect=ALLOW_ACCESS, description='Grant admin access') ## Create (add) self.storage.add(self.policy1) # try to add it again, it should raise a exception try: self.storage.add(self.policy1) raise Exception( "The DjangoStorage should't store a policy more than once") except PolicyExistsError: pass ## Read # get test_policy = self.storage.get(self.policy1.uid) assert test_policy, "The storage doesn't return a policy from get" assert self.policy1.uid == test_policy.uid, "These policies must be equal" must_be_none = self.storage.get(self.policy2.uid) assert not must_be_none, "This policy shouldn't exists" # get all self.storage.add(self.policy2) test_policies = self.storage.get_all() assert test_policies, "This should return a policies list" assert test_policies[0].uid == self.policy1.uid \ and test_policies[1].uid == self.policy2.uid,\ "The returned list should match" ## Update policy1b = Policy(self.policy1.uid, actions=[vrules.In('get', 'list')], resources=[{ 'category': vrules.Eq('administration'), 'sub': vrules.In('panel', 'switch') }], subjects=[{ 'name': vrules.Any(), 'role': vrules.NotEq('developer') }], effect=ALLOW_ACCESS, context={'module': vrules.Eq('Test')}, description=""" Allow access to administration interface subcategories: 'panel', 'switch' if user is not a developer and came from local IP address. """) self.storage.update(policy1b) test_policy = self.storage.get(self.policy1.uid) assert self.policy1.uid == test_policy.uid and\ test_policy.to_json() == policy1b.to_json(),\ "The test policy values doesn't match with the updated version" ## Delete self.storage.delete(self.policy2.uid) test_policies = self.storage.get_all() test_none = self.storage.get(self.policy2.uid) assert not test_none, "This policy shouldn't be stored as it has been deleted" assert len(test_policies) == 1 and test_policies[0].uid == self.policy1.uid, \ "The returned list should match" ## Find for inquiry # check a matching policy inquiry1 = Inquiry(action='get', resource='repos/google/tensorflow', subject={ 'name': 'Jane', 'role': 'admin' }, context={'module': 'Test'}) matching_policies = self.storage.find_for_inquiry( inquiry1, self.guard.checker) assert matching_policies, "The matching policies list should not be empty" # check it doesn't match and inquiry inquiry2 = Inquiry(action='delete', resource='repos/google/tensorflow', subject={ 'name': 'Max', 'role': 'developer' }, context={'module': 'Test'}) matching_policies = self.storage.find_for_inquiry( inquiry2, self.guard.checker) assert not matching_policies, "The matching policies list should be empty"
def is_allowed(self, ied, action, address, protocol): return self._guard.is_allowed(Inquiry( subject=ied, action=action, context=address, resource=protocol))