class hbacrule(LDAPObject): """ HBAC object. """ container_dn = api.env.container_hbac object_name = _('HBAC rule') object_name_plural = _('HBAC rules') object_class = ['ipaassociation', 'ipahbacrule'] permission_filter_objectclasses = ['ipahbacrule'] default_attributes = [ 'cn', 'ipaenabledflag', 'description', 'usercategory', 'hostcategory', 'servicecategory', 'ipaenabledflag', 'memberuser', 'sourcehost', 'memberhost', 'memberservice', 'externalhost', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], 'sourcehost': ['host', 'hostgroup'], 'memberservice': ['hbacsvc', 'hbacsvcgroup'], } managed_permissions = { 'System: Read HBAC Rules': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'accessruletype', 'accesstime', 'cn', 'description', 'externalhost', 'hostcategory', 'ipaenabledflag', 'ipauniqueid', 'memberhost', 'memberservice', 'memberuser', 'servicecategory', 'sourcehost', 'sourcehostcategory', 'usercategory', 'objectclass', 'member', }, }, 'System: Add HBAC Rule': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Add HBAC rule";allow (add) groupdn = "ldap:///cn=Add HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, 'System: Delete HBAC Rule': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Delete HBAC rule";allow (delete) groupdn = "ldap:///cn=Delete HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, 'System: Manage HBAC Rule Membership': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'externalhost', 'memberhost', 'memberservice', 'memberuser'}, 'replaces': [ '(targetattr = "memberuser || externalhost || memberservice || memberhost")(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Manage HBAC rule membership";allow (write) groupdn = "ldap:///cn=Manage HBAC rule membership,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, 'System: Modify HBAC Rule': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'accessruletype', 'accesstime', 'cn', 'description', 'hostcategory', 'ipaenabledflag', 'servicecategory', 'sourcehost', 'sourcehostcategory', 'usercategory' }, 'replaces': [ '(targetattr = "servicecategory || sourcehostcategory || cn || description || ipaenabledflag || accesstime || usercategory || hostcategory || accessruletype || sourcehost")(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Modify HBAC rule";allow (write) groupdn = "ldap:///cn=Modify HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, } label = _('HBAC Rules') label_singular = _('HBAC Rule') takes_params = ( Str( 'cn', cli_name='name', label=_('Rule name'), primary_key=True, ), StrEnum( 'accessruletype', validate_type, cli_name='type', doc=_('Rule type (allow)'), label=_('Rule type'), values=(u'allow', u'deny'), default=u'allow', autofill=True, exclude='webui', flags=['no_option', 'no_output'], ), # FIXME: {user,host,service}categories should expand in the future StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), DeprecatedParam('sourcehostcategory?'), StrEnum( 'servicecategory?', cli_name='servicecat', label=_('Service category'), doc=_('Service category the rule applies to'), values=(u'all', ), ), # AccessTime('accesstime?', # cli_name='time', # label=_('Access time'), # ), Str( 'description?', cli_name='desc', label=_('Description'), ), Bool( 'ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), Str( 'memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), DeprecatedParam('sourcehost_host?'), DeprecatedParam('sourcehost_hostgroup?'), Str( 'memberservice_hbacsvc?', label=_('Services'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberservice_hbacsvcgroup?', label=_('Service Groups'), flags=['no_create', 'no_update', 'no_search'], ), external_host_param, )
class idrange_mod(LDAPUpdate): __doc__ = _("""Modify ID range. {0} """.format(ID_RANGE_VS_DNA_WARNING)) msg_summary = _('Modified ID range "%(value)s"') takes_options = LDAPUpdate.takes_options + ( DeprecatedParam('ipanttrusteddomainsid?'), DeprecatedParam('ipanttrusteddomainname?'), ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) attrs_list.append('objectclass') try: old_attrs = ldap.get_entry(dn, ['*']) except errors.NotFound: self.obj.handle_not_found(*keys) if old_attrs['iparangetype'][0] == 'ipa-local': raise errors.ExecutionError( message=_('This command can not be used to change ID ' 'allocation for local IPA domain. Run ' '`ipa help idrange` for more information')) is_set = lambda x: (x in entry_attrs) and (entry_attrs[x] is not None) in_updated_attrs = lambda x:\ (x in entry_attrs and entry_attrs[x] is not None) or\ (x not in entry_attrs and x in old_attrs and old_attrs[x] is not None) # This needs to stay in options since there is no # ipanttrusteddomainname attribute in LDAP if 'ipanttrusteddomainname' in options: if is_set('ipanttrusteddomainsid'): raise errors.ValidationError( name='ID Range setup', error=_('Options dom-sid and dom-name ' 'cannot be used together')) sid = self.obj.get_trusted_domain_sid_from_name( options['ipanttrusteddomainname']) # we translate the name into sid so further validation can rely # on ipanttrusteddomainsid attribute only if sid is not None: entry_attrs['ipanttrusteddomainsid'] = sid else: raise errors.ValidationError( name='ID Range setup', error=_('SID for the specified trusted domain name could ' 'not be found. Please specify the SID directly ' 'using dom-sid option.')) if in_updated_attrs('ipanttrusteddomainsid'): if in_updated_attrs('ipasecondarybaserid'): raise errors.ValidationError( name='ID Range setup', error=_('Options dom-sid and secondary-rid-base cannot ' 'be used together')) range_type = old_attrs['iparangetype'][0] if range_type == u'ipa-ad-trust': if not in_updated_attrs('ipabaserid'): raise errors.ValidationError( name='ID Range setup', error=_('Options dom-sid and rid-base must ' 'be used together')) elif (range_type == u'ipa-ad-trust-posix' and 'ipabaserid' in entry_attrs): if entry_attrs['ipabaserid'] is None: entry_attrs['ipabaserid'] = 0 elif entry_attrs['ipabaserid'] != 0: raise errors.ValidationError( name='ID Range setup', error=_('Option rid-base must not be used when IPA ' 'range type is ipa-ad-trust-posix')) if is_set('ipanttrusteddomainsid'): # Validate SID as the one of trusted domains # perform this check only if the attribute was changed self.obj.validate_trusted_domain_sid( entry_attrs['ipanttrusteddomainsid']) # Add trusted AD domain range object class, if it wasn't there if not 'ipatrustedaddomainrange' in old_attrs['objectclass']: entry_attrs['objectclass'].append('ipatrustedaddomainrange') else: # secondary base rid must be set if and only if base rid is set if in_updated_attrs('ipasecondarybaserid') !=\ in_updated_attrs('ipabaserid'): raise errors.ValidationError( name='ID Range setup', error=_('Options secondary-rid-base and rid-base must ' 'be used together')) # ensure that primary and secondary rid ranges do not overlap if all( in_updated_attrs(base) for base in ('ipabaserid', 'ipasecondarybaserid')): # make sure we are working with updated attributes rid_range_attributes = ('ipabaserid', 'ipasecondarybaserid', 'ipaidrangesize') updated_values = dict() for attr in rid_range_attributes: if is_set(attr): updated_values[attr] = entry_attrs[attr] else: updated_values[attr] = int(old_attrs[attr][0]) if self.obj.are_rid_ranges_overlapping( updated_values['ipabaserid'], updated_values['ipasecondarybaserid'], updated_values['ipaidrangesize']): raise errors.ValidationError( name='ID Range setup', error=_("Primary RID range and secondary RID range" " cannot overlap")) # check whether ids are in modified range old_base_id = int(old_attrs.get('ipabaseid', [0])[0]) old_range_size = int(old_attrs.get('ipaidrangesize', [0])[0]) new_base_id = entry_attrs.get('ipabaseid') if new_base_id is not None: new_base_id = int(new_base_id) new_range_size = entry_attrs.get('ipaidrangesize') if new_range_size is not None: new_range_size = int(new_range_size) self.obj.check_ids_in_modified_range(old_base_id, old_range_size, new_base_id, new_range_size) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): assert isinstance(dn, DN) self.obj.handle_ipabaserid(entry_attrs, options) self.obj.handle_iparangetype(entry_attrs, options) return dn
class hbacrule(LDAPObject): """ HBAC object. """ container_dn = api.env.container_hbac object_name = _('HBAC rule') object_name_plural = _('HBAC rules') object_class = ['ipaassociation', 'ipahbacrule'] default_attributes = [ 'cn', 'ipaenabledflag', 'description', 'usercategory', 'hostcategory', 'sourcehostcategory', 'servicecategory', 'ipaenabledflag', 'memberuser', 'sourcehost', 'memberhost', 'memberservice', 'memberhostgroup', 'externalhost', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], 'sourcehost': ['host', 'hostgroup'], 'memberservice': ['hbacsvc', 'hbacsvcgroup'], } label = _('HBAC Rules') label_singular = _('HBAC Rule') takes_params = ( Str( 'cn', cli_name='name', label=_('Rule name'), primary_key=True, ), StrEnum( 'accessruletype', validate_type, cli_name='type', doc=_('Rule type (allow)'), label=_('Rule type'), values=(u'allow', u'deny'), default=u'allow', autofill=True, exclude='webui', flags=['no_option', 'no_output'], ), # FIXME: {user,host,service}categories should expand in the future StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), DeprecatedParam('sourcehostcategory?'), StrEnum( 'servicecategory?', cli_name='servicecat', label=_('Service category'), doc=_('Service category the rule applies to'), values=(u'all', ), ), # AccessTime('accesstime?', # cli_name='time', # label=_('Access time'), # ), Str( 'description?', cli_name='desc', label=_('Description'), ), Bool( 'ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), Str( 'memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), DeprecatedParam('sourcehost_host?'), DeprecatedParam('sourcehost_hostgroup?'), Str( 'memberservice_hbacsvc?', label=_('Services'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberservice_hbacsvcgroup?', label=_('Service Groups'), flags=['no_create', 'no_update', 'no_search'], ), external_host_param, )
class stageuser_add(baseuser_add): __doc__ = _('Add a new stage user.') msg_summary = _('Added stage user "%(value)s"') has_output_params = baseuser_add.has_output_params + stageuser_output_params takes_options = LDAPCreate.takes_options + (DeprecatedParam( 'from_delete?', doc=_('Create Stage user in from a delete user'), cli_name='from_delete', default=False, ), ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) # then givenname and sn are required attributes if 'givenname' not in entry_attrs: raise errors.RequirementError(name='givenname', error=_('givenname is required')) if 'sn' not in entry_attrs: raise errors.RequirementError(name='sn', error=_('sn is required')) # we don't want an user private group to be created for this user # add NO_UPG_MAGIC description attribute to let the DS plugin know entry_attrs.setdefault('description', []) entry_attrs['description'].append(NO_UPG_MAGIC) # uidNumber/gidNumber entry_attrs.setdefault('uidnumber', baseldap.DNA_MAGIC) entry_attrs.setdefault('gidnumber', baseldap.DNA_MAGIC) if not client_has_capability(options['version'], 'optional_uid_params'): # https://fedorahosted.org/freeipa/ticket/2886 # Old clients say 999 (OLD_DNA_MAGIC) when they really mean # "assign a value dynamically". OLD_DNA_MAGIC = 999 if entry_attrs.get('uidnumber') == OLD_DNA_MAGIC: entry_attrs['uidnumber'] = baseldap.DNA_MAGIC if entry_attrs.get('gidnumber') == OLD_DNA_MAGIC: entry_attrs['gidnumber'] = baseldap.DNA_MAGIC # Check the lenght of the RDN (uid) value config = ldap.get_ipa_config() if 'ipamaxusernamelength' in config: if len(keys[-1]) > int(config.get('ipamaxusernamelength')[0]): raise errors.ValidationError( name=self.obj.primary_key.cli_name, error=_('can be at most %(len)d characters') % dict(len=int(config.get('ipamaxusernamelength')[0]))) default_shell = config.get('ipadefaultloginshell', [paths.SH])[0] entry_attrs.setdefault('loginshell', default_shell) # hack so we can request separate first and last name in CLI full_name = '%s %s' % (entry_attrs['givenname'], entry_attrs['sn']) entry_attrs.setdefault('cn', full_name) # Homedirectory # (order is : option, placeholder (TBD), CLI default value (here in config)) if 'homedirectory' not in entry_attrs: # get home's root directory from config homes_root = config.get('ipahomesrootdir', [paths.HOME_DIR])[0] # build user's home directory based on his uid entry_attrs['homedirectory'] = posixpath.join(homes_root, keys[-1]) # Kerberos principal entry_attrs.setdefault('krbprincipalname', '%s@%s' % (entry_attrs['uid'], api.env.realm)) # If requested, generate a userpassword if 'userpassword' not in entry_attrs and options.get('random'): entry_attrs['userpassword'] = ipa_generate_password( baseuser_pwdchars) # save the password so it can be displayed in post_callback setattr(context, 'randompassword', entry_attrs['userpassword']) # Check the email or create it if 'mail' in entry_attrs: entry_attrs['mail'] = self.obj.normalize_and_validate_email( entry_attrs['mail'], config) else: # No e-mail passed in. If we have a default e-mail domain set # then we'll add it automatically. defaultdomain = config.get('ipadefaultemaildomain', [None])[0] if defaultdomain: entry_attrs['mail'] = self.obj.normalize_and_validate_email( keys[-1], config) # If the manager is defined, check it is a ACTIVE user to validate it if 'manager' in entry_attrs: entry_attrs['manager'] = self.obj.normalize_manager( entry_attrs['manager'], self.obj.active_container_dn) if ('objectclass' in entry_attrs and 'userclass' in entry_attrs and 'ipauser' not in entry_attrs['objectclass']): entry_attrs['objectclass'].append('ipauser') if 'ipatokenradiusconfiglink' in entry_attrs: cl = entry_attrs['ipatokenradiusconfiglink'] if cl: if 'objectclass' not in entry_attrs: _entry = ldap.get_entry(dn, ['objectclass']) entry_attrs['objectclass'] = _entry['objectclass'] if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']: entry_attrs['objectclass'].append( 'ipatokenradiusproxyuser') answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl) entry_attrs['ipatokenradiusconfiglink'] = answer return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): assert isinstance(dn, DN) config = ldap.get_ipa_config() # Fetch the entry again to update memberof, mep data, etc updated # at the end of the transaction. newentry = ldap.get_entry(dn, ['*']) entry_attrs.update(newentry) if options.get('random', False): try: entry_attrs['randompassword'] = unicode( getattr(context, 'randompassword')) except AttributeError: # if both randompassword and userpassword options were used pass self.post_common_callback(ldap, dn, entry_attrs, **options) return dn
class hbactest(Command): __doc__ = _('Simulate use of Host-based access controls') has_output = ( output.summary, output.Output('warning', (list, tuple, NoneType), _('Warning')), 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, ), DeprecatedParam('sourcehost?'), Str('targethost', cli_name='host', label=_('Target host'), ), Str('service', cli_name='service', label=_('Service'), ), Str('rules*', cli_name='rules', label=_('Rules to test. If not specified, --enabled is assumed'), csv=True, ), 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'), ), Int('sizelimit?', label=_('Size Limit'), doc=_('Maximum number of rules to process when no --rules is specified'), flags=['no_display'], minvalue=0, autofill=False, ), ) def canonicalize(self, host): """ Canonicalize the host name -- add default IPA domain if that is missing """ if host.find('.') == -1: return u'%s.%s' % (host, self.env.domain) return host def execute(self, *args, **options): # First receive all needed information: # 1. HBAC rules (whether enabled or disabled) # 2. Required options are (user, target host, service) # 3. Options: rules to test (--rules, --enabled, --disabled), request for detail output rules = [] # 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 sizelimit = None if 'sizelimit' in options: sizelimit = int(options['sizelimit']) # 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 hbacset = [] if len(testrules) == 0: hbacset = self.api.Command.hbacrule_find(sizelimit=sizelimit)['result'] else: for rule in testrules: try: hbacset.append(self.api.Command.hbacrule_show(rule)['result']) except: pass # 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, 'warning' : None, 'value' : False} # Rules are converted to pyhbac format, build request and then test it request = pyhbac.HbacRequest() if options['user'] != u'all': try: request.user.name = options['user'] search_result = self.api.Command.user_show(request.user.name)['result'] groups = search_result['memberof_group'] if 'memberofindirect_group' in search_result: groups += search_result['memberofindirect_group'] request.user.groups = sorted(set(groups)) except: pass if options['service'] != u'all': try: request.service.name = options['service'] service_result = self.api.Command.hbacsvc_show(request.service.name)['result'] if 'memberof_hbacsvcgroup' in service_result: request.service.groups = service_result['memberof_hbacsvcgroup'] except: pass if options['targethost'] != u'all': try: request.targethost.name = self.canonicalize(options['targethost']) tgthost_result = self.api.Command.host_show(request.targethost.name)['result'] groups = tgthost_result['memberof_hostgroup'] if 'memberofindirect_hostgroup' in tgthost_result: groups += tgthost_result['memberofindirect_hostgroup'] request.targethost.groups = sorted(set(groups)) except: pass matched_rules = [] notmatched_rules = [] error_rules = [] warning_rules = [] result = {'warning':None, '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
class hbactest(Command): __doc__ = _('Simulate use of Host-based access controls') has_output = ( output.summary, output.Output('warning', (list, tuple, NoneType), _('Warning')), 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, ), DeprecatedParam('sourcehost?'), Str( 'targethost', cli_name='host', label=_('Target host'), ), Str( 'service', cli_name='service', label=_('Service'), ), Str( 'rules*', cli_name='rules', label=_('Rules to test. If not specified, --enabled is assumed'), csv=True, ), 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'), ), Int( 'sizelimit?', label=_('Size Limit'), doc= _('Maximum number of rules to process when no --rules is specified' ), flags=['no_display'], minvalue=0, autofill=False, ), ) def canonicalize(self, host): """ Canonicalize the host name -- add default IPA domain if that is missing """ if host.find('.') == -1: return u'%s.%s' % (host, self.env.domain) return host def execute(self, *args, **options): # First receive all needed information: # 1. HBAC rules (whether enabled or disabled) # 2. Required options are (user, target host, service) # 3. Options: rules to test (--rules, --enabled, --disabled), request for detail output rules = [] # 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 sizelimit = None if 'sizelimit' in options: sizelimit = int(options['sizelimit']) # 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 hbacset = [] if len(testrules) == 0: hbacset = self.api.Command.hbacrule_find( sizelimit=sizelimit)['result'] else: for rule in testrules: try: hbacset.append( self.api.Command.hbacrule_show(rule)['result']) except: pass # 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, 'warning': None, 'value': False } # Rules are converted to pyhbac format, build request and then test it request = pyhbac.HbacRequest() if options['user'] != u'all': # check first if this is not a trusted domain user if _dcerpc_bindings_installed: is_valid_sid = ipaserver.dcerpc.is_sid_valid(options['user']) else: is_valid_sid = False components = util.normalize_name(options['user']) if is_valid_sid or 'domain' in components or 'flatname' in components: # this is a trusted domain user if not _dcerpc_bindings_installed: raise errors.NotFound(reason=_( 'Cannot perform external member validation without ' 'Samba 4 support installed. Make sure you have installed ' 'server-trust-ad sub-package of IPA on the server')) domain_validator = ipaserver.dcerpc.DomainValidator(self.api) if not domain_validator.is_configured(): raise errors.NotFound(reason=_( 'Cannot search in trusted domains without own domain configured. ' 'Make sure you have run ipa-adtrust-install on the IPA server first' )) user_sid, group_sids = domain_validator.get_trusted_domain_user_and_groups( options['user']) request.user.name = user_sid # Now search for all external groups that have this user or # any of its groups in its external members. Found entires # memberOf links will be then used to gather all groups where # this group is assigned, including the nested ones filter_sids = "(&(objectclass=ipaexternalgroup)(|(ipaExternalMember=%s)))" \ % ")(ipaExternalMember=".join(group_sids + [user_sid]) ldap = self.api.Backend.ldap2 group_container = DN(api.env.container_group, api.env.basedn) try: entries, truncated = ldap.find_entries( filter_sids, ['memberof'], group_container) except errors.NotFound: request.user.groups = [] else: groups = [] for entry in entries: memberof_dns = entry.get('memberof', []) for memberof_dn in memberof_dns: if memberof_dn.endswith(group_container): groups.append(memberof_dn[0][0].value) request.user.groups = sorted(set(groups)) else: # try searching for a local user try: request.user.name = options['user'] search_result = self.api.Command.user_show( request.user.name)['result'] groups = search_result['memberof_group'] if 'memberofindirect_group' in search_result: groups += search_result['memberofindirect_group'] request.user.groups = sorted(set(groups)) except: pass if options['service'] != u'all': try: request.service.name = options['service'] service_result = self.api.Command.hbacsvc_show( request.service.name)['result'] if 'memberof_hbacsvcgroup' in service_result: request.service.groups = service_result[ 'memberof_hbacsvcgroup'] except: pass if options['targethost'] != u'all': try: request.targethost.name = self.canonicalize( options['targethost']) tgthost_result = self.api.Command.host_show( request.targethost.name)['result'] groups = tgthost_result['memberof_hostgroup'] if 'memberofindirect_hostgroup' in tgthost_result: groups += tgthost_result['memberofindirect_hostgroup'] request.targethost.groups = sorted(set(groups)) except: pass matched_rules = [] notmatched_rules = [] error_rules = [] warning_rules = [] result = { 'warning': None, '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