Esempio n. 1
0
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'})
Esempio n. 2
0
    def get_access(self, struct_inquiry):
        '''Inquires about an access request given an attribute dictionary.'''
        if not struct_inquiry:
            return False

        g = Guard(self._storage, RulesChecker())
        return g.is_allowed(Inquiry.from_json(struct_inquiry))
Esempio n. 3
0
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
Esempio n. 4
0
 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()
Esempio n. 5
0
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
Esempio n. 6
0
    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()
Esempio n. 7
0
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
Esempio n. 8
0
 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
Esempio n. 9
0
    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)
Esempio n. 10
0
 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)
Esempio n. 11
0
 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
Esempio n. 12
0
 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
Esempio n. 13
0
 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()
Esempio n. 14
0
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
Esempio n. 15
0
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'},
Esempio n. 16
0
    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
Esempio n. 17
0
    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"
Esempio n. 18
0
    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"
Esempio n. 19
0
 def is_allowed(self, ied, action, address, protocol):
     return self._guard.is_allowed(Inquiry(
         subject=ied, action=action, context=address, resource=protocol))