diff options
-rw-r--r-- | ipalib/plugins/hbactest.py | 141 | ||||
-rw-r--r-- | ipaserver/dcerpc.py | 56 |
2 files changed, 187 insertions, 10 deletions
diff --git a/ipalib/plugins/hbactest.py b/ipalib/plugins/hbactest.py index 78fac0241..3e2c69eb0 100644 --- a/ipalib/plugins/hbactest.py +++ b/ipalib/plugins/hbactest.py @@ -17,11 +17,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -from ipalib import api, errors, output +from ipalib import api, errors, output, util from ipalib import Command, Str, Flag, Int from types import NoneType from ipalib.cli import to_cli from ipalib import _, ngettext +from ipapython.dn import DN +if api.env.in_server and api.env.context in ['lite', 'server']: + try: + import ipaserver.dcerpc + _dcerpc_bindings_installed = True + except ImportError: + _dcerpc_bindings_installed = False + import pyhbac __doc__ = _(""" @@ -129,6 +137,74 @@ EXAMPLES: notmatched: new-rule matched: allow_all + +HBACTEST AND TRUSTED DOMAINS + +When an external trusted domain is configured in IPA, HBAC rules are also applied +on users accessing IPA resources from the trusted domain. Trusted domain users and +groups (and their SIDs) can be then assigned to external groups which can be +members of POSIX groups in IPA which can be used in HBAC rules and thus allowing +access to resources protected by the HBAC system. + +hbactest plugin is capable of testing access for both local IPA users and users +from the trusted domains, either by a fully qualified user name or by user SID. +Such user names need to have a trusted domain specified as a short name +(DOMAIN\Administrator) or with a user principal name (UPN), Administrator@ad.test. + +Please note that hbactest executed with a trusted domain user as --user parameter +can be only run by members of "trust admins" group. + +EXAMPLES: + + 1. Test if a user from a trusted domain specified by its shortname matches any + rule: + + $ ipa hbactest --user 'DOMAIN\Administrator' --host `hostname` --service sshd + -------------------- + Access granted: True + -------------------- + Matched rules: allow_all + Matched rules: can_login + + 2. Test if a user from a trusted domain specified by its domain name matches + any rule: + + $ ipa hbactest --user 'Administrator@domain.com' --host `hostname` --service sshd + -------------------- + Access granted: True + -------------------- + Matched rules: allow_all + Matched rules: can_login + + 3. Test if a user from a trusted domain specified by its SID matches any rule: + + $ ipa hbactest --user S-1-5-21-3035198329-144811719-1378114514-500 \\ + --host `hostname` --service sshd + -------------------- + Access granted: True + -------------------- + Matched rules: allow_all + Matched rules: can_login + + 4. Test if other user from a trusted domain specified by its SID matches any rule: + + $ ipa hbactest --user S-1-5-21-3035198329-144811719-1378114514-500 \\ + --host `hostname` --service sshd + -------------------- + Access granted: True + -------------------- + Matched rules: allow_all + Matched rules: can_login + + 5. Test if other user from a trusted domain specified by its shortname matches + any rule: + + $ ipa hbactest --user 'DOMAIN\Otheruser' --host `hostname` --service sshd + -------------------- + Access granted: True + -------------------- + Matched rules: allow_all + Not matched rules: can_login """) def convert_to_ipa_rule(rule): @@ -298,15 +374,60 @@ class hbactest(Command): 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 + # 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, ['cn', 'memberOf'], group_container) + except errors.NotFound: + request.user.groups = [] + else: + groups = [] + for dn, entry in entries: + memberof_dns = entry.get('memberof', []) + for memberof_dn in memberof_dns: + if memberof_dn.endswith(group_container): + # this is a group object + 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: diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py index 56d8b0319..6243ebbb9 100644 --- a/ipaserver/dcerpc.py +++ b/ipaserver/dcerpc.py @@ -275,6 +275,62 @@ class DomainValidator(object): raise errors.ValidationError(name=_('trusted domain object'), error= _('Trusted domain did not return a valid SID for the object')) + def get_trusted_domain_user_and_groups(self, object_name): + """ + Returns a tuple with user SID and a list of SIDs of all groups he is + a member of. + + LIMITATIONS: + - only Trusted Admins group members can use this function as it + uses secret for IPA-Trusted domain link + - List of group SIDs does not contain group memberships outside + of the trusted domain + """ + components = normalize_name(object_name) + domain = components.get('domain') + flatname = components.get('flatname') + name = components.get('name') + + is_valid_sid = is_sid_valid(object_name) + if is_valid_sid: + # Find a trusted domain for the SID + domain = self.get_domain_by_sid(object_name) + # Now search a trusted domain for a user with this SID + attrs = ['cn'] + filter = '(&(objectClass=user)(objectSid=%(sid)s))' \ + % dict(sid=object_name) + try: + entries = self.get_trusted_domain_objects(domain=domain, filter=filter, + attrs=attrs, scope=_ldap.SCOPE_SUBTREE) + except errors.NotFound: + raise errors.NotFound(reason=_('trusted domain user not found')) + user_dn = entries[0][0] + elif domain or flatname: + attrs = ['cn'] + filter = '(&(sAMAccountName=%(name)s)(objectClass=user))' \ + % dict(name=name) + try: + entries = self.get_trusted_domain_objects(domain, + flatname, filter, attrs, _ldap.SCOPE_SUBTREE) + except errors.NotFound: + raise errors.NotFound(reason=_('trusted domain user not found')) + user_dn = entries[0][0] + else: + # No domain or realm specified, ambiguous search + raise errors.ValidationError(name=_('trusted domain object'), + error= _('Ambiguous search, user domain was not specified')) + + # Get SIDs of user object and it's groups + # tokenGroups attribute must be read with a scope BASE for a known user + # distinguished name to avoid search error + attrs = ['objectSID', 'tokenGroups'] + filter = "(objectClass=user)" + entries = self.get_trusted_domain_objects(domain, + flatname, filter, attrs, _ldap.SCOPE_BASE, user_dn) + object_sid = self.__sid_to_str(entries[0][1]['objectSid'][0]) + group_sids = [self.__sid_to_str(sid) for sid in entries[0][1]['tokenGroups']] + return (object_sid, group_sids) + def __sid_to_str(self, sid): """ Converts binary SID to string representation |