From ec239a86c0db944dcb4672efd4e120d4a7fae204 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Fri, 22 Jul 2011 16:30:44 +0300 Subject: Add hbactest command. https://fedorahosted.org/freeipa/ticket/386 HBAC rules control who can access what services on what hosts and from where. You can use HBAC to control which users or groups on a source host can access a service, or group of services, on a target host. Since applying HBAC rules implies use of a production environment, this plugin aims to provide simulation of HBAC rules evaluation without having access to the production environment. Test user coming from source host to a service on a named host against existing enabled rules. ipa hbactest --user= --srchost= --host= --service= [--rules=rules-list] [--nodetail] [--enabled] [--disabled] --user, --srchost, --host, and --service are mandatory, others are optional. If --rules is specified simulate enabling of the specified rules and test the login of the user using only these rules. If --enabled is specified, all enabled HBAC rules will be added to simulation If --disabled is specified, all disabled HBAC rules will be added to simulation If --nodetail is specified, do not return information about rules matched/not matched. If both --rules and --enabled are specified, apply simulation to --rules _and_ all IPA enabled rules. If no --rules specified, simulation is run against all IPA enabled rules. EXAMPLES: 1. Use all enabled HBAC rules in IPA database to simulate: $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh -------------------- Access granted: True -------------------- notmatched: my-second-rule notmatched: my-third-rule notmatched: myrule matched: allow_all 2. Disable detailed summary of how rules were applied: $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh --nodetail -------------------- Access granted: True -------------------- 3. Test explicitly specified HBAC rules: $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh --rules=my-second-rule,myrule --------------------- Access granted: False --------------------- notmatched: my-second-rule notmatched: myrule 4. Use all enabled HBAC rules in IPA database + explicitly specified rules: $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh --rules=my-second-rule,myrule --enabled -------------------- Access granted: True -------------------- notmatched: my-second-rule notmatched: my-third-rule notmatched: myrule matched: allow_all 5. Test all disabled HBAC rules in IPA database: $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh --disabled --------------------- Access granted: False --------------------- notmatched: new-rule 6. Test all disabled HBAC rules in IPA database + explicitly specified rules: $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh --rules=my-second-rule,myrule --disabled --------------------- Access granted: False --------------------- notmatched: my-second-rule notmatched: my-third-rule notmatched: myrule 7. Test all (enabled and disabled) HBAC rules in IPA database: $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh --enabled --disabled -------------------- Access granted: True -------------------- notmatched: my-second-rule notmatched: my-third-rule notmatched: myrule notmatched: new-rule matched: allow_all Only rules existing in IPA database are tested. They may be in enabled or disabled disabled state. Specifying them through --rules option explicitly enables them only in simulation run. Specifying non-existing rules will not grant access and report non-existing rules in output. --- API.txt | 15 ++ VERSION | 2 +- freeipa.spec.in | 5 + ipalib/plugins/hbactest.py | 327 ++++++++++++++++++++++++++++++ tests/test_xmlrpc/test_hbactest_plugin.py | 206 +++++++++++++++++++ 5 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 ipalib/plugins/hbactest.py create mode 100644 tests/test_xmlrpc/test_hbactest_plugin.py diff --git a/API.txt b/API.txt index 42a212b1e..5c4a7fe93 100644 --- a/API.txt +++ b/API.txt @@ -1321,6 +1321,21 @@ option: Str('version?', exclude='webui', flags=['no_option', 'no_output']) output: Output('summary', (, ), 'User-friendly description of action performed') output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Output('value', , "The primary_key value of the entry, e.g. 'jdoe' for a user") +command: hbactest +args: 0,8,5 +option: Str('user', cli_name='user', label=Gettext('User name', domain='ipa', localedir=None), primary_key=True) +option: Str('sourcehost', cli_name='srchost', label=Gettext('Source host', domain='ipa', localedir=None)) +option: Str('targethost', cli_name='host', label=Gettext('Target host', domain='ipa', localedir=None)) +option: Str('service', cli_name='service', label=Gettext('Service', domain='ipa', localedir=None)) +option: List('rules?', cli_name='rules', label=Gettext('Rules to test. If not specified, --enabled is assumed', domain='ipa', localedir=None), multivalue=True) +option: Flag('nodetail?', autofill=True, cli_name='nodetail', default=False, label=Gettext('Hide details which rules are matched, not matched, or invalid', domain='ipa', localedir=None)) +option: Flag('enabled?', autofill=True, cli_name='enabled', default=False, label=Gettext('Include all enabled IPA rules into test [default]', domain='ipa', localedir=None)) +option: Flag('disabled?', autofill=True, cli_name='disabled', default=False, label=Gettext('Include all disabled IPA rules into test', domain='ipa', localedir=None)) +output: Output('summary', (, ), 'User-friendly description of action performed') +output: Output('matched', (, , ), Gettext('Matched rules', domain='ipa', localedir=None)) +output: Output('notmatched', (, , ), Gettext('Not matched rules', domain='ipa', localedir=None)) +output: Output('error', (, , ), Gettext('Non-existent or invalid rules', domain='ipa', localedir=None)) +output: Output('value', , Gettext('Result of simulation', domain='ipa', localedir=None)) command: host_add args: 1,14,3 arg: Str('fqdn', validate_host, attribute=True, cli_name='hostname', label=Gettext('Host name', domain='ipa', localedir=None), multivalue=False, normalizer=, primary_key=True, required=True) diff --git a/VERSION b/VERSION index 98e92c5dd..0abf5962f 100644 --- a/VERSION +++ b/VERSION @@ -79,4 +79,4 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=10 +IPA_API_VERSION_MINOR=11 diff --git a/freeipa.spec.in b/freeipa.spec.in index f51677d81..42137432b 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -59,6 +59,7 @@ BuildRequires: python-kerberos BuildRequires: python-rhsm BuildRequires: pyOpenSSL BuildRequires: pylint +BuildRequires: libipa_hbac-python %description IPA is an integrated solution to provide centrally managed Identity (machine, @@ -201,6 +202,7 @@ Requires: python-netaddr >= 0.7.5-3 %else Requires: python-netaddr %endif +Requires: libipa_hbac-python Obsoletes: ipa-python >= 1.0 @@ -511,6 +513,9 @@ fi %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/default.conf %changelog +* Tue Jul 26 2011 Alexander Bokovoy - 2.0.90-8 +- Add libipa_hbac-python dependency for hbactest plugin + * Wed Jul 20 2011 Rob Crittenden - 2.0.90-7 - Make cyrus-sasl-gssapi requires arch-specific diff --git a/ipalib/plugins/hbactest.py b/ipalib/plugins/hbactest.py new file mode 100644 index 000000000..05fb56932 --- /dev/null +++ b/ipalib/plugins/hbactest.py @@ -0,0 +1,327 @@ +# Authors: +# Alexander Bokovoy +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Simulate use of Host-based access controls + +HBAC rules control who can access what services on what hosts and from where. +You can use HBAC to control which users or groups on a source host can +access a service, or group of services, on a target host. + +Since applying HBAC rules implies use of a production environment, +this plugin aims to provide simulation of HBAC rules evaluation without +having access to the production environment. + + Test user coming from source host to a service on a named host against + existing enabled rules. + + ipa hbactest --user= --srchost= --host= --service= + [--rules=rules-list] [--nodetail] [--enabled] [--disabled] + + --user, --srchost, --host, and --service are mandatory, others are optional. + + If --rules is specified simulate enabling of the specified rules and test + the login of the user using only these rules. + + If --enabled is specified, all enabled HBAC rules will be added to simulation + + If --disabled is specified, all disabled HBAC rules will be added to simulation + + If --nodetail is specified, do not return information about rules matched/not matched. + + If both --rules and --enabled are specified, apply simulation to --rules _and_ + all IPA enabled rules. + + If no --rules specified, simulation is run against all IPA enabled rules. + +EXAMPLES: + + 1. Use all enabled HBAC rules in IPA database to simulate: + $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh + -------------------- + Access granted: True + -------------------- + notmatched: my-second-rule + notmatched: my-third-rule + notmatched: myrule + matched: allow_all + + 2. Disable detailed summary of how rules were applied: + $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh --nodetail + -------------------- + Access granted: True + -------------------- + + 3. Test explicitly specified HBAC rules: + $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh \ + --rules=my-second-rule,myrule + --------------------- + Access granted: False + --------------------- + notmatched: my-second-rule + notmatched: myrule + + 4. Use all enabled HBAC rules in IPA database + explicitly specified rules: + $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh \ + --rules=my-second-rule,myrule --enabled + -------------------- + Access granted: True + -------------------- + notmatched: my-second-rule + notmatched: my-third-rule + notmatched: myrule + matched: allow_all + + 5. Test all disabled HBAC rules in IPA database: + $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh --disabled + --------------------- + Access granted: False + --------------------- + notmatched: new-rule + + 6. Test all disabled HBAC rules in IPA database + explicitly specified rules: + $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh \ + --rules=my-second-rule,myrule --disabled + --------------------- + Access granted: False + --------------------- + notmatched: my-second-rule + notmatched: my-third-rule + notmatched: myrule + + 7. Test all (enabled and disabled) HBAC rules in IPA database: + $ ipa hbactest --user=a1a --srchost=foo --host=bar --service=ssh \ + --enabled --disabled + -------------------- + Access granted: True + -------------------- + notmatched: my-second-rule + notmatched: my-third-rule + notmatched: myrule + notmatched: new-rule + matched: allow_all + +""" + +from ipalib import api, errors, output +from ipalib import Command, List, Str, Flag +from types import NoneType +from ipalib.cli import to_cli +from ipalib import _, ngettext +import pyhbac + +def convert_to_ipa_rule(rule): + # convert a dict with a rule to an pyhbac rule + ipa_rule = pyhbac.HbacRule(rule['cn'][0]) + ipa_rule.enabled = rule['ipaenabledflag'][0] + # Following code attempts to process rule systematically + structure = (('user', 'memberuser', 'user', 'group', ipa_rule.users), + ('host', 'memberhost', 'host', 'hostgroup', ipa_rule.targethosts), + ('sourcehost', 'sourcehost', 'host', 'hostgroup', ipa_rule.srchosts), + ('service', 'memberservice', 'hbacsvc', 'hbacsvcgroup', ipa_rule.services), + ) + for element in structure: + category = '%scategory' % (element[0]) + if category in rule and rule[category][0] == u'all': + # rule applies to all elements + element[4].category = set([pyhbac.HBAC_CATEGORY_ALL]) + else: + # rule is about specific entities + # Check if there are explicitly listed entities + attr_name = '%s_%s' % (element[1], element[2]) + if attr_name in rule: + element[4].names = rule[attr_name] + # Now add groups of entities if they are there + attr_name = '%s_%s' % (element[1], element[3]) + if attr_name in rule: + element[4].groups = rule[attr_name] + return ipa_rule + + +class hbactest(Command): + + has_output = ( + output.summary, + output.Output('matched', (list, tuple, NoneType), _('Matched rules')), + output.Output('notmatched', (list, tuple, NoneType), _('Not matched rules')), + output.Output('error', (list, tuple, NoneType), _('Non-existent or invalid rules')), + output.Output('value', bool, _('Result of simulation'), ['no_display']), + ) + + takes_options = ( + Str('user', + cli_name='user', + label=_('User name'), + primary_key=True, + ), + Str('sourcehost', + cli_name='srchost', + label=_('Source host'), + ), + Str('targethost', + cli_name='host', + label=_('Target host'), + ), + Str('service', + cli_name='service', + label=_('Service'), + ), + List('rules?', + cli_name='rules', + label=_('Rules to test. If not specified, --enabled is assumed'), + ), + Flag('nodetail?', + cli_name='nodetail', + label=_('Hide details which rules are matched, not matched, or invalid'), + ), + Flag('enabled?', + cli_name='enabled', + label=_('Include all enabled IPA rules into test [default]'), + ), + Flag('disabled?', + cli_name='disabled', + label=_('Include all disabled IPA rules into test'), + ), + ) + + def execute(self, *args, **options): + # First receive all needed information: + # 1. HBAC rules (whether enabled or disabled) + # 2. Required options are (user, source host, target host, service) + # 3. Options: rules to test (--rules, --enabled, --disabled), request for detail output + rules = [] + hbacset = self.api.Command.hbacrule_find()['result'] + + # Use all enabled IPA rules by default + all_enabled = True + all_disabled = False + + # We need a local copy of test rules in order find incorrect ones + testrules = {} + if 'rules' in options: + testrules = list(options['rules']) + # When explicit rules are provided, disable assumptions + all_enabled = False + all_disabled = False + + # Check if --disabled is specified, include all disabled IPA rules + if options['disabled']: + all_disabled = True + all_enabled = False + + # Finally, if enabled is specified implicitly, override above decisions + if options['enabled']: + all_enabled = True + + # We have some rules, import them + # --enabled will import all enabled rules (default) + # --disabled will import all disabled rules + # --rules will implicitly add the rules from a rule list + for rule in hbacset: + ipa_rule = convert_to_ipa_rule(rule) + if ipa_rule.name in testrules: + ipa_rule.enabled = True + rules.append(ipa_rule) + testrules.remove(ipa_rule.name) + elif all_enabled and ipa_rule.enabled: + # Option --enabled forces to include all enabled IPA rules into test + rules.append(ipa_rule) + elif all_disabled and not ipa_rule.enabled: + # Option --disabled forces to include all disabled IPA rules into test + ipa_rule.enabled = True + rules.append(ipa_rule) + + # Check if there are unresolved rules left + if len(testrules) > 0: + # Error, unresolved rules are left in --rules + return {'summary' : unicode(_(u'Unresolved rules in --rules')), + 'error': testrules, 'matched': None, 'notmatched': None, + 'value' : False} + + # Rules are converted to pyhbac format, we can test them + request = pyhbac.HbacRequest() + request.user.name = options['user'] + request.service.name = options['service'] + request.srchost.name = options['sourcehost'] + request.targethost.name = options['targethost'] + + matched_rules = [] + notmatched_rules = [] + error_rules = [] + + result = {'matched':None, 'notmatched':None, 'error':None} + if not options['nodetail']: + # Validate runs rules one-by-one and reports failed ones + for ipa_rule in rules: + try: + res = request.evaluate([ipa_rule]) + if res == pyhbac.HBAC_EVAL_ALLOW: + matched_rules.append(ipa_rule.name) + if res == pyhbac.HBAC_EVAL_DENY: + notmatched_rules.append(ipa_rule.name) + except pyhbac.HbacError as (code, rule_name): + if code == pyhbac.HBAC_EVAL_ERROR: + error_rules.append(rule_name) + self.log.info('Native IPA HBAC rule "%s" parsing error: %s' % \ + (rule_name, pyhbac.hbac_result_string(code))) + except (TypeError, IOError) as (info): + self.log.error('Native IPA HBAC module error: %s' % (info)) + + access_granted = len(matched_rules) > 0 + else: + res = request.evaluate(rules) + access_granted = (res == pyhbac.HBAC_EVAL_ALLOW) + + result['summary'] = _('Access granted: %s') % (access_granted) + + + if len(matched_rules) > 0: + result['matched'] = matched_rules + if len(notmatched_rules) > 0: + result['notmatched'] = notmatched_rules + if len(error_rules) > 0: + result['error'] = error_rules + + result['value'] = access_granted + return result + + def output_for_cli(self, textui, output, *args, **options): + """ + Command.output_for_cli() uses --all option to decide whether to print detailed output. + We use --detail to allow that, thus we need to redefine output_for_cli(). + """ + # Note that we don't actually use --detail below to see if details need + # to be printed as our execute() method will return None for corresponding + # entries and None entries will be skipped. + for o in self.output: + outp = self.output[o] + if 'no_display' in outp.flags: + continue + result = output[o] + if isinstance(result, (list, tuple)): + textui.print_attribute(outp.name, result, '%s: %s', 1, True) + elif isinstance(result, (unicode, bool)): + if o == 'summary': + textui.print_summary(result) + else: + textui.print_indented(result) + + # Propagate integer value for result. It will give proper command line result for scripts + return int(not bool(output['value'])) + +api.register(hbactest) diff --git a/tests/test_xmlrpc/test_hbactest_plugin.py b/tests/test_xmlrpc/test_hbactest_plugin.py new file mode 100644 index 000000000..5c5d2aa86 --- /dev/null +++ b/tests/test_xmlrpc/test_hbactest_plugin.py @@ -0,0 +1,206 @@ +# Authors: +# Pavel Zuna +# Alexander Bokovoy +# +# Copyright (C) 2009-2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Test the `ipalib/plugins/hbactest.py` module. +""" + +from xmlrpc_test import XMLRPC_test, assert_attr_equal +from ipalib import api +from ipalib import errors +from types import NoneType + +# Test strategy: +# 1. Create few allow rules: with user categories, with explicit users, with user groups, with groups, with services +# 2. Create users for test +# 3. Run detailed and non-detailed tests for explicitly specified rules, check expected result +# +class test_hbactest(XMLRPC_test): + """ + Test the `hbactest` plugin. + """ + rule_names = [u'testing_rule1234_%d' % (d) for d in [1,2,3,4]] + rule_type = u'allow' + rule_service = u'ssh' + rule_descs = [u'description %d' % (d) for d in [1,2,3,4]] + + test_user = u'hbacrule_test_user' + test_group = u'hbacrule_test_group' + test_host = u'hbacrule._test_host' + test_hostgroup = u'hbacrule_test_hostgroup' + test_sourcehost = u'hbacrule._test_src_host' + test_sourcehostgroup = u'hbacrule_test_src_hostgroup' + test_service = u'ssh' + + def test_0_hbactest_addrules(self): + """ + Prepare data by adding test HBAC rules using `xmlrpc.hbacrule_add'. + """ + + self.failsafe_add(api.Object.user, + self.test_user, givenname=u'first', sn=u'last' + ) + self.failsafe_add(api.Object.group, + self.test_group, description=u'description' + ) + self.failsafe_add(api.Object.host, + self.test_host, force=True + ) + self.failsafe_add(api.Object.hostgroup, + self.test_hostgroup, description=u'description' + ) + self.failsafe_add(api.Object.host, + self.test_sourcehost, force=True + ) + self.failsafe_add(api.Object.hostgroup, + self.test_sourcehostgroup, description=u'desc' + ) + self.failsafe_add(api.Object.hbacsvc, + self.test_service, description=u'desc', force=True + ) + + for i in [0,1,2,3]: + api.Command['hbacrule_add']( + self.rule_names[i], accessruletype=self.rule_type, description=self.rule_descs[i], + ) + + ret = api.Command['hbacrule_add_user']( + self.rule_names[i], user=self.test_user, group=self.test_group + ) + + ret = api.Command['hbacrule_add_host']( + self.rule_names[i], host=self.test_host, hostgroup=self.test_hostgroup + ) + + ret = api.Command['hbacrule_add_sourcehost']( + self.rule_names[i], host=self.test_sourcehost, hostgroup=self.test_sourcehostgroup + ) + + ret = api.Command['hbacrule_add_service']( + self.rule_names[i], hbacsvc=self.test_service + ) + + if i & 1: + ret = api.Command['hbacrule_disable'](self.rule_names[i]) + + def test_a_hbactest_check_rules_detail(self): + """ + Test 'ipa hbactest --rules' (explicit IPA rules, detailed output) + """ + ret = api.Command['hbactest']( + user=self.test_user, + sourcehost=self.test_sourcehost, + targethost=self.test_host, + service=self.test_service, + rules=self.rule_names + ) + assert ret['value'] == True + assert type(ret['error']) == NoneType + for i in [0,1,2,3]: + assert self.rule_names[i] in ret['matched'] + + def test_b_hbactest_check_rules_nodetail(self): + """ + Test 'ipa hbactest --rules --nodetail' (explicit IPA rules, no detailed output) + """ + ret = api.Command['hbactest']( + user=self.test_user, + sourcehost=self.test_sourcehost, + targethost=self.test_host, + service=self.test_service, + rules=self.rule_names, + nodetail=True + ) + assert ret['value'] == True + assert ret['error'] == None + assert ret['matched'] == None + assert ret['notmatched'] == None + + def test_c_hbactest_check_rules_enabled_detail(self): + """ + Test 'ipa hbactest --enabled' (all enabled IPA rules, detailed output) + """ + ret = api.Command['hbactest']( + user=self.test_user, + sourcehost=self.test_sourcehost, + targethost=self.test_host, + service=self.test_service, + enabled=True + ) + # --enabled will try to work with _all_ enabled rules in IPA database + # It means we could have matched something else (unlikely but possible) + # Thus, check that our two enabled rules are in matched, nothing more + for i in [0,2]: + assert self.rule_names[i] in ret['matched'] + + def test_d_hbactest_check_rules_disabled_detail(self): + """ + Test 'ipa hbactest --disabled' (all disabled IPA rules, detailed output) + """ + ret = api.Command['hbactest']( + user=self.test_user, + sourcehost=self.test_sourcehost, + targethost=self.test_host, + service=self.test_service, + disabled=True + ) + # --disabled will try to work with _all_ disabled rules in IPA database + # It means we could have matched something else (unlikely but possible) + # Thus, check that our two disabled rules are in matched, nothing more + for i in [1,3]: + assert self.rule_names[i] in ret['matched'] + + def test_e_hbactest_check_non_existing_rule_detail(self): + """ + Test running 'ipa hbactest' with non-existing rule in --rules + """ + ret = api.Command['hbactest']( + user=self.test_user, + sourcehost=self.test_sourcehost, + targethost=self.test_host, + service=self.test_service, + rules=[u'%s_1x1' % (rule) for rule in self.rule_names], + nodetail=True + ) + + assert ret['value'] == False + assert ret['matched'] == None + assert ret['notmatched'] == None + for rule in self.rule_names: + assert u'%s_1x1' % (rule) in ret['error'] + + def test_f_hbactest_clear_testing_data(self): + """ + Clear data for HBAC test plugin testing. + """ + for i in [0,1,2,3]: + api.Command['hbacrule_remove_host'](self.rule_names[i], host=self.test_host) + api.Command['hbacrule_remove_host'](self.rule_names[i], hostgroup=self.test_hostgroup) + api.Command['hbacrule_remove_sourcehost'](self.rule_names[i], host=self.test_sourcehost) + api.Command['hbacrule_remove_sourcehost'](self.rule_names[i], hostgroup=self.test_sourcehostgroup) + api.Command['hbacrule_del'](self.rule_names[i]) + + api.Command['user_del'](self.test_user) + api.Command['group_del'](self.test_group) + api.Command['host_del'](self.test_host) + api.Command['hostgroup_del'](self.test_hostgroup) + api.Command['host_del'](self.test_sourcehost) + api.Command['hostgroup_del'](self.test_sourcehostgroup) + api.Command['hbacsvc_del'](self.test_service) + -- cgit