Пример #1
0
 def post_callback(self, ldap, dn, entry, *keys, **options):
     default_entry = None
     rights = None
     for attrname in self.obj.default_attributes:
         if attrname not in entry:
             if keys[-1] is not None:
                 # User entry doesn't override the attribute.
                 # Check if this is caused by insufficient read rights
                 if rights is None:
                     rights = baseldap.get_effective_rights(
                         ldap, dn, self.obj.default_attributes)
                 if 'r' not in rights.get(attrname.lower(), ''):
                     raise errors.ACIError(
                         info=_('Ticket policy for %s could not be read') %
                             keys[-1])
                 # Fallback to the default
                 if default_entry is None:
                     try:
                         default_dn = self.obj.get_dn(None)
                         default_entry = ldap.get_entry(default_dn)
                     except errors.NotFound:
                         default_entry = {}
                 if attrname in default_entry:
                     entry[attrname] = default_entry[attrname]
         if attrname not in entry:
             raise errors.ACIError(
                 info=_('Default ticket policy could not be read'))
     return dn
Пример #2
0
    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
        assert isinstance(dn, DN)
        def check_validity(runas):
            v = unicode(runas)
            if v.upper() == u'ALL':
                return False
            return True

        try:
            (_dn, _entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes)
        except errors.NotFound:
            self.obj.handle_not_found(*keys)
        if is_all(_entry_attrs, 'ipasudorunasusercategory') or \
          is_all(_entry_attrs, 'ipasudorunasgroupcategory'):
            raise errors.MutuallyExclusiveError(reason=_("users cannot be added when runAs user or runAs group category='all'"))

        if 'user' in options:
            for name in options['user']:
                if not check_validity(name):
                    raise errors.ValidationError(name='runas-user',
                          error=unicode(_("RunAsUser does not accept '%(name)s' as a user name")) %
                          dict(name=name))
        if 'group' in options:
            for name in options['group']:
                if not check_validity(name):
                    raise errors.ValidationError(name='runas-user',
                          error=unicode(_("RunAsUser does not accept '%(name)s' as a group name")) %
                          dict(name=name))

        return add_external_pre_callback('user', ldap, dn, keys, options)
Пример #3
0
def validate_selinuxuser(ugettext, user):
    """
    An SELinux user has 3 components: user:MLS:MCS. user and MLS are required.
    user traditionally ends with _u but this is not mandatory.
      The regex is ^[a-zA-Z][a-zA-Z_]*

    The MLS part can only be:
      Level: s[0-15](-s[0-15])

    Then MCS could be c[0-1023].c[0-1023] and/or c[0-1023]-c[0-c0123]
    Meaning
    s0 s0-s1 s0-s15:c0.c1023 s0-s1:c0,c2,c15.c26 s0-s0:c0.c1023

    Returns a message on invalid, returns nothing on valid.
    """
    regex_name = re.compile(r'^[a-zA-Z][a-zA-Z_]*$')
    regex_mls = re.compile(r'^s[0-9][1-5]{0,1}(-s[0-9][1-5]{0,1}){0,1}$')
    regex_mcs = re.compile(r'^c(\d+)([.,-]c(\d+))*?$')

    # If we add in ::: we don't have to check to see if some values are
    # empty
    (name, mls, mcs, ignore) = (user + ':::').split(':', 3)

    if not regex_name.match(name):
        return _('Invalid SELinux user name, only a-Z and _ are allowed')
    if not mls or not regex_mls.match(mls):
        return _('Invalid MLS value, must match s[0-15](-s[0-15])')
    m = regex_mcs.match(mcs)
    if mcs and (not m or (m.group(3) and (int(m.group(3)) > 1023))):
        return _('Invalid MCS value, must match c[0-1023].c[0-1023] '
                 'and/or c[0-1023]-c[0-c0123]')

    return None
Пример #4
0
    def validate(self, **kw):
        """
        Validation rules:
        - at least one of 'type', 'users', 'hosts' is required
        - 'users' and 'hosts' cannot be combined together
        - if 'users' and 'type' are specified, 'type' must be 'group'
        - if 'hosts' and 'type' are specified, 'type' must be 'hostgroup'
        """
        super(automember_rebuild, self).validate(**kw)
        users, hosts, gtype = kw.get('users'), kw.get('hosts'), kw.get('type')

        if not (gtype or users or hosts):
            raise errors.MutuallyExclusiveError(
                reason=_('at least one of options: type, users, hosts must be '
                         'specified')
            )

        if users and hosts:
            raise errors.MutuallyExclusiveError(
                reason=_("users and hosts cannot both be set")
            )
        if gtype == 'group' and hosts:
            raise errors.MutuallyExclusiveError(
                reason=_("hosts cannot be set when type is 'group'")
            )
        if gtype == 'hostgroup' and users:
            raise errors.MutuallyExclusiveError(
                reason=_("users cannot be set when type is 'hostgroup'")
            )
Пример #5
0
    def execute(self, *keys, **options):
        if not _murmur_installed and 'base_id' not in options:
            raise errors.ValidationError(name=_('missing base_id'),
                error=_('pysss_murmur is not available on the server ' \
                        'and no base-id is given.'))

        if 'trust_type' in options:
            if options['trust_type'] == u'ad':
                result = self.execute_ad(*keys, **options)
            else:
                raise errors.ValidationError(name=_('trust type'), error=_('only "ad" is supported'))
        else:
            raise errors.RequirementError(name=_('trust type'))

        self.add_range(*keys, **options)

        trust_filter = "cn=%s" % result['value']
        ldap = self.obj.backend
        (trusts, truncated) = ldap.find_entries(
                         base_dn = DN(api.env.container_trusts, api.env.basedn),
                         filter = trust_filter)

        result['result'] = trusts[0][1]
        result['result']['trusttype'] = [trust_type_string(result['result']['ipanttrusttype'][0])]
        result['result']['trustdirection'] = [trust_direction_string(result['result']['ipanttrustdirection'][0])]
        result['result']['truststatus'] = [trust_status_string(result['verified'])]
        del result['verified']

        return result
Пример #6
0
def decrypt(data, symmetric_key=None, private_key=None):
    """
    Decrypts data with symmetric key or public key.
    """
    if symmetric_key:
        try:
            fernet = Fernet(symmetric_key)
            return fernet.decrypt(data)
        except InvalidToken:
            raise errors.AuthenticationError(
                message=_('Invalid credentials'))

    elif private_key:
        try:
            private_key_obj = load_pem_private_key(
                data=private_key,
                password=None,
                backend=default_backend()
            )
            return private_key_obj.decrypt(
                data,
                padding.OAEP(
                    mgf=padding.MGF1(algorithm=hashes.SHA1()),
                    algorithm=hashes.SHA1(),
                    label=None
                )
            )
        except ValueError:
            raise errors.AuthenticationError(
                message=_('Invalid credentials'))
Пример #7
0
    def execute(self, *args, **options):
        """
        Checks all the IPA masters for supported domain level ranges.

        If the desired domain level is within the supported range of all
        masters, it will be raised.

        Domain level cannot be lowered.
        """

        ldap = self.api.Backend.ldap2

        current_entry = ldap.get_entry(get_domainlevel_dn(self.api))
        current_value = int(current_entry.single_value['ipadomainlevel'])
        desired_value = int(args[0])

        # Domain level cannot be lowered
        if int(desired_value) < int(current_value):
            message = _("Domain Level cannot be lowered.")
            raise errors.InvalidDomainLevelError(message)

        # Check if every master supports the desired level
        for master in get_master_entries(ldap, self.api):
            supported = get_domainlevel_range(master)

            if supported.min > desired_value or supported.max < desired_value:
                message = _("Domain Level cannot be raised to {0}, server {1} "
                            "does not support it."
                            .format(desired_value, master['cn'][0]))
                raise errors.InvalidDomainLevelError(message)

        current_entry.single_value['ipaDomainLevel'] = desired_value
        ldap.update_entry(current_entry)

        return {'result': int(current_entry.single_value['ipaDomainLevel'])}
Пример #8
0
def resolve_object_to_anchor(ldap, obj_type, obj, fallback_to_ldap):
    """
    Resolves the user/group name to the anchor uuid:
        - first it tries to find the object as user or group in IPA (depending
          on the passed obj_type)
        - if the IPA lookup failed, lookup object SID in the trusted domains

    Takes options:
        ldap - the backend
        obj_type - either 'user' or 'group'
        obj - the name of the object, e.g 'admin' or 'testuser'
    """

    try:
        entry = ldap.get_entry(api.Object[obj_type].get_dn(obj),
                               attrs_list=['ipaUniqueID', 'objectClass'])

        # First we check this is a valid object to override
        # - for groups, it must have ipaUserGroup objectclass
        # - for users, it must have posixAccount objectclass

        required_objectclass = {
            'user': '******',
            'group': 'ipausergroup',
        }[obj_type]

        if required_objectclass not in entry['objectclass']:
            raise errors.ValidationError(
                    name=_('IPA object'),
                    error=_('system IPA objects (e.g system groups, user '
                            'private groups) cannot be overriden')
                )

        # The domain prefix, this will need to be reworked once we
        # introduce IPA-IPA trusts
        domain = api.env.domain
        uuid = entry.single_value['ipaUniqueID']

        return "%s%s:%s" % (IPA_ANCHOR_PREFIX, domain, uuid)
    except errors.NotFound:
        pass

    # If not successfull, try looking up the object in the trusted domain
    try:
        if _dcerpc_bindings_installed:
            domain_validator = ipaserver.dcerpc.DomainValidator(api)
            if domain_validator.is_configured():
                sid = domain_validator.get_trusted_domain_object_sid(obj,
                        fallback_to_ldap=fallback_to_ldap)

                # There is no domain prefix since SID contains information
                # about the domain
                return SID_ANCHOR_PREFIX + sid
    except errors.ValidationError:
        # Domain validator raises Validation Error if object name does not
        # contain domain part (either NETBIOS\ prefix or @domain.name suffix)
        pass

    # No acceptable object was found
    api.Object[obj_type].handle_not_found(obj)
Пример #9
0
    def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args,
                     **options):
        assert isinstance(base_dn, DN)

        if not self.api.Command.kra_is_enabled()['result']:
            raise errors.InvocationError(
                format=_('KRA service is not enabled'))

        if options.get('users') or options.get('services'):
            mutex = ['service', 'services', 'shared', 'username', 'users']
            count = sum(bool(options.get(option)) for option in mutex)
            if count > 1:
                raise errors.MutuallyExclusiveError(
                    reason=_('Service(s), shared, and user(s) options ' +
                             'cannot be specified simultaneously'))

            scope = ldap.SCOPE_SUBTREE
            container_dn = DN(self.obj.container_dn,
                              self.api.env.basedn)

            if options.get('services'):
                base_dn = DN(('cn', 'services'), container_dn)
            else:
                base_dn = DN(('cn', 'users'), container_dn)
        else:
            base_dn = self.obj.get_dn(None, **options)

        return filter, base_dn, scope
Пример #10
0
 def pre_callback(self, ldap, dn, *keys, **options):
     if keys[0] == 'hosts_services_caIPAserviceCert':
         raise errors.ProtectedEntryError(
             label=_("CA ACL"),
             key=keys[0],
             reason=_("default CA ACL can be only disabled"))
     return dn
Пример #11
0
    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
        assert isinstance(dn, DN)
        try:
            entry_attrs = ldap.get_entry(dn, attrs_list)
            dn = entry_attrs.dn
        except errors.NotFound:
            self.obj.handle_not_found(*keys)

        if is_all(options, 'ipacacategory') and 'ipamemberca' in entry_attrs:
            raise errors.MutuallyExclusiveError(reason=_(
                "CA category cannot be set to 'all' "
                "while there are allowed CAs"))
        if (is_all(options, 'ipacertprofilecategory')
                and 'ipamembercertprofile' in entry_attrs):
            raise errors.MutuallyExclusiveError(reason=_(
                "profile category cannot be set to 'all' "
                "while there are allowed profiles"))
        if is_all(options, 'usercategory') and 'memberuser' in entry_attrs:
            raise errors.MutuallyExclusiveError(reason=_(
                "user category cannot be set to 'all' "
                "while there are allowed users"))
        if is_all(options, 'hostcategory') and 'memberhost' in entry_attrs:
            raise errors.MutuallyExclusiveError(reason=_(
                "host category cannot be set to 'all' "
                "while there are allowed hosts"))
        if is_all(options, 'servicecategory') and 'memberservice' in entry_attrs:
            raise errors.MutuallyExclusiveError(reason=_(
                "service category cannot be set to 'all' "
                "while there are allowed services"))
        return dn
Пример #12
0
    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
        assert isinstance(dn, DN)
        if 'sudoorder' in options:
            new_order = options.get('sudoorder')
            old_entry = self.api.Command.sudorule_show(keys[-1])['result']
            if 'sudoorder' in old_entry:
                old_order = int(old_entry['sudoorder'][0])
                if old_order != new_order:
                    self.obj.check_order_uniqueness(*keys, **options)
            else:
                self.obj.check_order_uniqueness(*keys, **options)
        try:
            (_dn, _entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes)
        except errors.NotFound:
            self.obj.handle_not_found(*keys)

        if is_all(options, 'usercategory') and 'memberuser' in _entry_attrs:
            raise errors.MutuallyExclusiveError(reason=_("user category cannot be set to 'all' while there are allowed users"))
        if is_all(options, 'hostcategory') and 'memberhost' in _entry_attrs:
            raise errors.MutuallyExclusiveError(reason=_("host category cannot be set to 'all' while there are allowed hosts"))
        if is_all(options, 'cmdcategory') and ('memberallowcmd' or
            'memberdenywcmd') in _entry_attrs:
            raise errors.MutuallyExclusiveError(reason=_("command category cannot be set to 'all' while there are allow or deny commands"))
        if is_all(options, 'ipasudorunasusercategory') and 'ipasudorunas' in _entry_attrs:
            raise errors.MutuallyExclusiveError(reason=_("user runAs category cannot be set to 'all' while there are users"))
        if is_all(options, 'ipasudorunasgroupcategory') and 'ipasudorunasgroup' in _entry_attrs:
            raise errors.MutuallyExclusiveError(reason=_("group runAs category cannot be set to 'all' while there are groups"))

        return dn
Пример #13
0
    def pre_callback(self, ldap, dn, *keys, **options):
        if keys[0] in PROTECTED_HOSTGROUPS:
            raise errors.ProtectedEntryError(label=_(u'hostgroup'),
                                             key=keys[0],
                                             reason=_(u'privileged hostgroup'))

        return dn
Пример #14
0
 def _check_hide_server(self, fqdn):
     result = self.api.Command.config_show()['result']
     err = []
     # single value entries
     if result.get("ca_renewal_master_server") == fqdn:
         err.append(_("Cannot hide CA renewal master."))
     if result.get("dnssec_key_master_server") == fqdn:
         err.append(_("Cannot hide DNSSec key master."))
     # multi value entries, only fail if we are the last one
     checks = [
         ("ca_server_server", "CA"),
         ("dns_server_server", "DNS"),
         ("ipa_master_server", "IPA"),
         ("kra_server_server", "KRA"),
     ]
     for key, name in checks:
         values = result.get(key, [])
         if values == [fqdn]:  # fqdn is the only entry
             err.append(
                 _("Cannot hide last enabled %(name)s server.") % {
                     'name': name
                 }
             )
     if err:
         raise errors.ValidationError(
             name=fqdn,
             error=' '.join(str(e) for e in err)
         )
Пример #15
0
    def validate_nodes(self, ldap, dn, entry_attrs):
        leftnode = entry_attrs.get("iparepltoposegmentleftnode")
        rightnode = entry_attrs.get("iparepltoposegmentrightnode")

        if not leftnode and not rightnode:
            return  # nothing to check

        # check if nodes are IPA servers
        masters = self.api.Command.server_find("", sizelimit=0)["result"]
        m_hostnames = [master["cn"][0].lower() for master in masters]

        if leftnode and leftnode not in m_hostnames:
            raise errors.ValidationError(
                name="leftnode", error=_("left node is not a topology node: %(leftnode)s") % dict(leftnode=leftnode)
            )

        if rightnode and rightnode not in m_hostnames:
            raise errors.ValidationError(
                name="rightnode",
                error=_("right node is not a topology node: %(rightnode)s") % dict(rightnode=rightnode),
            )

        # prevent creation of reflexive relation
        key = "leftnode"
        if not leftnode or not rightnode:  # get missing end
            _entry_attrs = ldap.get_entry(dn, ["*"])
            if not leftnode:
                key = "rightnode"
                leftnode = _entry_attrs["iparepltoposegmentleftnode"][0]
            else:
                rightnode = _entry_attrs["iparepltoposegmentrightnode"][0]

        if leftnode == rightnode:
            raise errors.ValidationError(name=key, error=_("left node and right node must not be the same"))
Пример #16
0
    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
        assert isinstance(dn, DN)

        if 'ipanttrusteddomainsid' in options:
            if 'ipasecondarybaserid' in options:
                raise errors.ValidationError(name='ID Range setup',
                    error=_('Options dom_sid and secondary_rid_base cannot ' \
                            'be used together'))

            if 'ipabaserid' not in options:
                raise errors.ValidationError(name='ID Range setup',
                    error=_('Options dom_sid and rid_base must ' \
                            'be used together'))

            # Validate SID as the one of trusted domains
            self.obj.validate_trusted_domain_sid(options['ipanttrusteddomainsid'])
            # Finally, add trusted AD domain range object class
            entry_attrs['objectclass'].append('ipatrustedaddomainrange')
        else:
            if (('ipasecondarybaserid' in options) != ('ipabaserid' in options)):
                raise errors.ValidationError(name='ID Range setup',
                    error=_('Options secondary_rid_base and rid_base must ' \
                            'be used together'))

            entry_attrs['objectclass'].append('ipadomainidrange')

        return dn
Пример #17
0
    def execute(self, *keys, **options):
        dn = self.obj.get_dn(*keys, **options)
        validate_domain_level(self.api)

        entry = self.obj.backend.get_entry(dn, ["nsds5beginreplicarefresh;left", "nsds5beginreplicarefresh;right"])

        left = options.get("left")
        right = options.get("right")
        stop = options.get("stop")

        if not left and not right:
            raise errors.OptionError(_("left or right node has to be specified"))

        if left and right:
            raise errors.OptionError(_("only one node can be specified"))

        action = u"start"
        msg = _('Replication refresh for segment: "%(pkey)s" requested.')
        if stop:
            action = u"stop"
            msg = _('Stopping of replication refresh for segment: "' '%(pkey)s" requested.')

        # left and right are swapped because internally it's a push not
        # pull operation
        if right:
            entry["nsds5beginreplicarefresh;left"] = [action]
        if left:
            entry["nsds5beginreplicarefresh;right"] = [action]

        self.obj.backend.update_entry(entry)

        msg = msg % {"pkey": keys[-1]}
        return dict(result=True, value=msg)
Пример #18
0
 def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
     assert isinstance(dn, DN)
     result = (completed, dn)
     if 'ipaexternalmember' in options:
         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 perform join operation without own domain configured. '
                                   'Make sure you have run ipa-adtrust-install on the IPA server first'))
         sids = []
         failed_sids = []
         for sid in options['ipaexternalmember']:
             if domain_validator.is_trusted_sid_valid(sid):
                 sids.append(sid)
             else:
                 actual_sid = domain_validator.get_sid_trusted_domain_object(sid)
                 if isinstance(actual_sid, unicode):
                     sids.append(actual_sid)
                 else:
                     failed_sids.append((sid, 'Not a trusted domain SID'))
         if len(sids) == 0:
             raise errors.ValidationError(name=_('external member'),
                                          error=_('values are not recognized as valid SIDs from trusted domain'))
         restore = []
         if 'member' in failed and 'group' in failed['member']:
             restore = failed['member']['group']
         failed['member']['group'] = list((id,id) for id in sids)
         result = add_external_post_callback('member', 'group', 'ipaexternalmember',
                                             ldap, completed, failed, dn, entry_attrs,
                                             keys, options, external_callback_normalize=False)
         failed['member']['group'] = restore + failed_sids
     return result
Пример #19
0
def split_any_principal(principal):
    service = hostname = realm = None

    # Break down the principal into its component parts, which may or
    # may not include the realm.
    sp = principal.split('/')
    name_and_realm = None
    if len(sp) > 2:
        raise errors.MalformedServicePrincipal(reason=_('unable to determine service'))
    elif len(sp) == 2:
        service = sp[0]
        if len(service) == 0:
            raise errors.MalformedServicePrincipal(reason=_('blank service'))
        name_and_realm = sp[1]
    else:
        name_and_realm = sp[0]

    sr = name_and_realm.split('@')
    if len(sr) > 2:
        raise errors.MalformedServicePrincipal(
            reason=_('unable to determine realm'))

    hostname = sr[0].lower()
    if len(sr) == 2:
        realm = sr[1].upper()
        # At some point we'll support multiple realms
        if realm != api.env.realm:
            raise errors.RealmMismatch()
    else:
        realm = api.env.realm

    # Note that realm may be None.
    return service, hostname, realm
Пример #20
0
    def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options):
        ca_enabled_check()
        if not ldap.can_add(dn[1:]):
            raise errors.ACIError(
                info=_("Insufficient 'add' privilege for entry '%s'.") % dn)

        # check for name collision before creating CA in Dogtag
        try:
            api.Object.ca.get_dn_if_exists(keys[-1])
            self.obj.handle_duplicate_entry(*keys)
        except errors.NotFound:
            pass

        # check for subject collision before creating CA in Dogtag
        result = api.Command.ca_find(ipacasubjectdn=options['ipacasubjectdn'])
        if result['count'] > 0:
            raise errors.DuplicateEntry(message=_(
                "Subject DN is already used by CA '%s'"
                ) % result['result'][0]['cn'][0])

        # Create the CA in Dogtag.
        with self.api.Backend.ra_lightweight_ca as ca_api:
            resp = ca_api.create_ca(options['ipacasubjectdn'])
        entry['ipacaid'] = [resp['id']]
        entry['ipacaissuerdn'] = [resp['issuerDN']]

        # In the event that the issued certificate's subject DN
        # differs from what was requested, record the actual DN.
        #
        entry['ipacasubjectdn'] = [resp['dn']]
        return dn
Пример #21
0
    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
        assert isinstance(dn, DN)
        # Allow an existing OTP to be reset but don't allow a OTP to be
        # added to an enrolled host.
        if options.get('userpassword') or options.get('random'):
            entry = {}
            self.obj.get_password_attributes(ldap, dn, entry)
            if not entry['has_password'] and entry['has_keytab']:
                raise errors.ValidationError(name='password', error=_('Password cannot be set on enrolled host.'))

        # Once a principal name is set it cannot be changed
        if 'cn' in entry_attrs:
            raise errors.ACIError(info=_('cn is immutable'))
        if 'locality' in entry_attrs:
            entry_attrs['l'] = entry_attrs['locality']
        if 'krbprincipalname' in entry_attrs:
            (dn, entry_attrs_old) = ldap.get_entry(
                dn, ['objectclass', 'krbprincipalname']
            )
            if 'krbprincipalname' in entry_attrs_old:
                msg = 'Principal name already set, it is unchangeable.'
                raise errors.ACIError(info=msg)
            obj_classes = entry_attrs_old['objectclass']
            if 'krbprincipalaux' not in obj_classes:
                obj_classes.append('krbprincipalaux')
                entry_attrs['objectclass'] = obj_classes
        cert = x509.normalize_certificate(entry_attrs.get('usercertificate'))
        if cert:
            if self.api.env.enable_ra:
                x509.verify_cert_subject(ldap, keys[-1], cert)
                (dn, entry_attrs_old) = ldap.get_entry(dn, ['usercertificate'])
                oldcert = entry_attrs_old.single_value.get('usercertificate')
                if oldcert:
                    oldcert = x509.normalize_certificate(oldcert)
                    try:
                        serial = x509.get_serial_number(oldcert, x509.DER)
                        serial = unicode(serial)
                        try:
                            result = api.Command['cert_show'](serial)['result']
                            if 'revocation_reason' not in result:
                                try:
                                    api.Command['cert_revoke'](
                                        serial, revocation_reason=4)
                                except errors.NotImplementedError:
                                    # some CA's might not implement revoke
                                    pass
                        except errors.NotImplementedError:
                            # some CA's might not implement revoke
                            pass
                    except NSPRError, nsprerr:
                        if nsprerr.errno == -8183:
                            # If we can't decode the cert them proceed with
                            # modifying the host.
                            self.log.info("Problem decoding certificate %s" %
                                          nsprerr.args[1])
                        else:
                            raise nsprerr

            entry_attrs['usercertificate'] = cert
Пример #22
0
    def execute(self, *keys, **options):
        """
        This requires updating both the user and the group. We first need to
        verify that both the user and group can be updated, then we go
        about our work. We don't want a situation where only the user or
        group can be modified and we're left in a bad state.
        """
        ldap = self.obj.backend

        group_dn = self.obj.get_dn(*keys, **options)
        user_dn = self.api.Object["user"].get_dn(*keys)

        try:
            user_attrs = ldap.get_entry(user_dn)
        except errors.NotFound:
            self.obj.handle_not_found(*keys)
        is_managed = self.obj.has_objectclass(user_attrs["objectclass"], "mepmanagedentry")
        if (
            not ldap.can_write(user_dn, "objectclass")
            or not (ldap.can_write(user_dn, "mepManagedEntry"))
            and is_managed
        ):
            raise errors.ACIError(info=_("not allowed to modify user entries"))

        group_attrs = ldap.get_entry(group_dn)
        is_managed = self.obj.has_objectclass(group_attrs["objectclass"], "mepmanagedby")
        if not ldap.can_write(group_dn, "objectclass") or not (ldap.can_write(group_dn, "mepManagedBy")) and is_managed:
            raise errors.ACIError(info=_("not allowed to modify group entries"))

        objectclasses = user_attrs["objectclass"]
        try:
            i = objectclasses.index("mepOriginEntry")
            del objectclasses[i]
            user_attrs["mepManagedEntry"] = None
            ldap.update_entry(user_attrs)
        except ValueError:
            # Somehow the user isn't managed, let it pass for now. We'll
            # let the group throw "Not managed".
            pass

        group_attrs = ldap.get_entry(group_dn)
        objectclasses = group_attrs["objectclass"]
        try:
            i = objectclasses.index("mepManagedEntry")
        except ValueError:
            # this should never happen
            raise errors.NotFound(reason=_("Not a managed group"))
        del objectclasses[i]

        # Make sure the resulting group has the default group objectclasses
        config = ldap.get_ipa_config()
        def_objectclass = config.get(self.obj.object_class_config, objectclasses)
        objectclasses = list(set(def_objectclass + objectclasses))

        group_attrs["mepManagedBy"] = None
        group_attrs["objectclass"] = objectclasses
        ldap.update_entry(group_attrs)

        return dict(result=True, value=pkey_to_value(keys[0], options))
Пример #23
0
 def interactive_prompt_callback(self, kw):
     # show informative message on client side
     # server cannot send messages asynchronous
     if kw.get('idnsforwarders', False):
         self.Backend.textui.print_plain(
             _("Server will check DNS forwarder(s)."))
         self.Backend.textui.print_plain(
             _("This may take some time, please wait ..."))
Пример #24
0
    def execute(self, cn, **options):
        if cn == IPA_CA_CN:
            raise errors.ProtectedEntryError(
                label=_("CA"),
                key=cn,
                reason=_("IPA CA cannot be disabled"))

        return super(ca_disable, self).execute(cn, **options)
Пример #25
0
    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
        if 'rename' in options:
            raise errors.ValidationError(
                name=_('ID override'),
                error=_('ID overrides cannot be renamed')
                )

        self.obj.prohibit_ipa_users_in_default_view(dn, entry_attrs)
        return dn
Пример #26
0
 def pre_callback(self, ldap, dn, *keys, **options):
     assert isinstance(dn, DN)
     if keys[0] in PROTECTED_CONSTRAINT_TARGETS:
         raise errors.ProtectedEntryError(
             label=_(u'service delegation target'),
             key=keys[0],
             reason=_(u'privileged service delegation target')
         )
     return dn
Пример #27
0
 def handle_iparangetype(self, entry_attrs, options, keep_objectclass=False):
     if not options.get('pkey_only', False):
         if 'ipatrustedaddomainrange' in entry_attrs.get('objectclass', []):
             entry_attrs['iparangetype'] = [unicode(_('Active Directory domain range'))]
         else:
             entry_attrs['iparangetype'] = [unicode(_(u'local domain range'))]
     if not keep_objectclass:
         if not options.get('all', False) or options.get('pkey_only', False):
             entry_attrs.pop('objectclass', None)
Пример #28
0
    def prohibit_ipa_users_in_default_view(self, dn, entry_attrs):
        # Check if parent object is Default Trust View, if so, prohibit
        # adding overrides for IPA objects

        if dn[1].value.lower() == DEFAULT_TRUST_VIEW_NAME:
            if dn[0].value.startswith(IPA_ANCHOR_PREFIX):
                raise errors.ValidationError(
                    name=_('ID View'),
                    error=_('Default Trust View cannot contain IPA users')
                    )
Пример #29
0
    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)
Пример #30
0
 def reject_system(self, entry):
     """Raise if permission entry has unknown flags, or is a SYSTEM perm"""
     flags = entry.get('ipapermissiontype', [])
     for flag in flags:
         if flag not in KNOWN_FLAGS:
             raise errors.ACIError(
                 info=_('Permission with unknown flag %s may not be '
                         'modified or removed') % flag)
     if list(flags) == [u'SYSTEM']:
         raise errors.ACIError(
             info=_('A SYSTEM permission may not be modified or removed'))
Пример #31
0
class idoverridegroup_add(baseidoverride_add):
    __doc__ = _('Add a new Group ID override.')
    msg_summary = _('Added Group ID override "%(value)s"')
Пример #32
0
def validate_ldapuri(ugettext, ldapuri):
    m = re.match('^ldaps?://[-\w\.]+(:\d+)?$', ldapuri)
    if not m:
        err_msg = _('Invalid LDAP URI.')
        raise errors.ValidationError(name='ldap_uri', error=err_msg)
Пример #33
0
class migrate_ds(Command):
    __doc__ = _('Migrate users and groups from DS to IPA.')

    migrate_objects = {
        # OBJECT_NAME: (search_filter, pre_callback, post_callback)
        #
        # OBJECT_NAME - is the name of an LDAPObject subclass
        # search_filter - is the filter to retrieve objects from DS
        # pre_callback - is called for each object just after it was
        #                retrieved from DS and before being added to IPA
        # post_callback - is called for each object after it was added to IPA
        # exc_callback - is called when adding entry to IPA raises an exception
        #
        # {pre, post}_callback parameters:
        #  ldap - ldap2 instance connected to IPA
        #  pkey - primary key value of the object (uid for users, etc.)
        #  dn - dn of the object as it (will be/is) stored in IPA
        #  entry_attrs - attributes of the object
        #  failed - a list of so-far failed objects
        #  config - IPA config entry attributes
        #  ctx - object context, used to pass data between callbacks
        #
        # If pre_callback return value evaluates to False, migration
        # of the current object is aborted.
        'user': {
            'filter_template': '(&(|%s)(uid=*))',
            'oc_option': 'userobjectclass',
            'oc_blacklist_option': 'userignoreobjectclass',
            'attr_blacklist_option': 'userignoreattribute',
            'pre_callback': _pre_migrate_user,
            'post_callback': _post_migrate_user,
            'exc_callback': None
        },
        'group': {
            'filter_template': '(&(|%s)(cn=*))',
            'oc_option': 'groupobjectclass',
            'oc_blacklist_option': 'groupignoreobjectclass',
            'attr_blacklist_option': 'groupignoreattribute',
            'pre_callback': _pre_migrate_group,
            'post_callback': None,
            'exc_callback': _group_exc_callback,
        },
    }
    migrate_order = ('user', 'group')

    takes_args = (
        Str(
            'ldapuri',
            validate_ldapuri,
            cli_name='ldap_uri',
            label=_('LDAP URI'),
            doc=_('LDAP URI of DS server to migrate from'),
        ),
        Password(
            'bindpw',
            cli_name='password',
            label=_('Password'),
            confirm=False,
            doc=_('bind password'),
        ),
    )

    takes_options = (
        DNParam('binddn?',
            cli_name='bind_dn',
            label=_('Bind DN'),
            default=DN(('cn', 'directory manager')),
            autofill=True,
        ),
        DNParam('usercontainer',
            cli_name='user_container',
            label=_('User container'),
            doc=_('DN of container for users in DS relative to base DN'),
            default=DN(('ou', 'people')),
            autofill=True,
        ),
        DNParam('groupcontainer',
            cli_name='group_container',
            label=_('Group container'),
            doc=_('DN of container for groups in DS relative to base DN'),
            default=DN(('ou', 'groups')),
            autofill=True,
        ),
        Str('userobjectclass+',
            cli_name='user_objectclass',
            label=_('User object class'),
            doc=_('Objectclasses used to search for user entries in DS'),
            default=(u'person',),
            autofill=True,
        ),
        Str('groupobjectclass+',
            cli_name='group_objectclass',
            label=_('Group object class'),
            doc=_('Objectclasses used to search for group entries in DS'),
            default=(u'groupOfUniqueNames', u'groupOfNames'),
            autofill=True,
        ),
        Str('userignoreobjectclass*',
            cli_name='user_ignore_objectclass',
            label=_('Ignore user object class'),
            doc=_('Objectclasses to be ignored for user entries in DS'),
            default=tuple(),
            autofill=True,
        ),
        Str('userignoreattribute*',
            cli_name='user_ignore_attribute',
            label=_('Ignore user attribute'),
            doc=_('Attributes to be ignored for user entries in DS'),
            default=tuple(),
            autofill=True,
        ),
        Str('groupignoreobjectclass*',
            cli_name='group_ignore_objectclass',
            label=_('Ignore group object class'),
            doc=_('Objectclasses to be ignored for group entries in DS'),
            default=tuple(),
            autofill=True,
        ),
        Str('groupignoreattribute*',
            cli_name='group_ignore_attribute',
            label=_('Ignore group attribute'),
            doc=_('Attributes to be ignored for group entries in DS'),
            default=tuple(),
            autofill=True,
        ),
        Flag('groupoverwritegid',
            cli_name='group_overwrite_gid',
            label=_('Overwrite GID'),
            doc=_('When migrating a group already existing in IPA domain overwrite the '\
                  'group GID and report as success'),
        ),
        StrEnum('schema?',
            cli_name='schema',
            label=_('LDAP schema'),
            doc=_('The schema used on the LDAP server. Supported values are RFC2307 and RFC2307bis. The default is RFC2307bis'),
            values=_supported_schemas,
            default=_supported_schemas[0],
            autofill=True,
        ),
        Flag('continue?',
            label=_('Continue'),
            doc=_('Continuous operation mode. Errors are reported but the process continues'),
            default=False,
        ),
        DNParam('basedn?',
            cli_name='base_dn',
            label=_('Base DN'),
            doc=_('Base DN on remote LDAP server'),
        ),
        Flag('compat?',
            cli_name='with_compat',
            label=_('Ignore compat plugin'),
            doc=_('Allows migration despite the usage of compat plugin'),
            default=False,
        ),
        Str('cacertfile?',
            cli_name='ca_cert_file',
            label=_('CA certificate'),
            doc=_('Load CA certificate of LDAP server from FILE'),
            default=None,
            noextrawhitespace=False,
        ),
        Bool('use_def_group?',
            cli_name='use_default_group',
            label=_('Add to default group'),
            doc=_('Add migrated users without a group to a default group '
                  '(default: true)'),
            default=True,
            autofill=True,
        ),
        StrEnum('scope',
            cli_name='scope',
            label=_('Search scope'),
            doc=_('LDAP search scope for users and groups: base, onelevel, or '
                  'subtree. Defaults to onelevel'),
            values=tuple(_supported_scopes.keys()),
            default=_default_scope,
            autofill=True,
        ),
    )

    has_output = (
        output.Output(
            'result',
            type=dict,
            doc=_('Lists of objects migrated; categorized by type.'),
        ),
        output.Output(
            'failed',
            type=dict,
            doc=
            _('Lists of objects that could not be migrated; categorized by type.'
              ),
        ),
        output.Output(
            'enabled',
            type=bool,
            doc=_('False if migration mode was disabled.'),
        ),
        output.Output(
            'compat',
            type=bool,
            doc=
            _('False if migration fails because the compatibility plug-in is enabled.'
              ),
        ),
    )

    exclude_doc = _('%s to exclude from migration')

    truncated_err_msg = _('''\
search results for objects to be migrated
have been truncated by the server;
migration process might be incomplete\n''')

    def get_options(self):
        """
        Call get_options of the baseclass and add "exclude" options
        for each type of object being migrated.
        """
        for option in super(migrate_ds, self).get_options():
            yield option
        for ldap_obj_name in self.migrate_objects:
            ldap_obj = self.api.Object[ldap_obj_name]
            name = 'exclude_%ss' % to_cli(ldap_obj_name)
            doc = self.exclude_doc % ldap_obj.object_name_plural
            yield Str('%s*' % name,
                      cli_name=name,
                      doc=doc,
                      default=tuple(),
                      autofill=True)

    def normalize_options(self, options):
        """
        Convert all "exclude" option values to lower-case.

        Also, empty List parameters are converted to None, but the migration
        plugin doesn't like that - convert back to empty lists.
        """
        names = [
            'userobjectclass', 'groupobjectclass', 'userignoreobjectclass',
            'userignoreattribute', 'groupignoreobjectclass',
            'groupignoreattribute'
        ]
        names.extend('exclude_%ss' % to_cli(n) for n in self.migrate_objects)
        for name in names:
            if options[name]:
                options[name] = tuple(v.lower() for v in options[name])
            else:
                options[name] = tuple()

    def _get_search_bases(self, options, ds_base_dn, migrate_order):
        search_bases = dict()
        for ldap_obj_name in migrate_order:
            container = options.get('%scontainer' % to_cli(ldap_obj_name))
            if container:
                # Don't append base dn if user already appended it in the container dn
                if container.endswith(ds_base_dn):
                    search_base = container
                else:
                    search_base = DN(container, ds_base_dn)
            else:
                search_base = ds_base_dn
            search_bases[ldap_obj_name] = search_base
        return search_bases

    def migrate(self, ldap, config, ds_ldap, ds_base_dn, options):
        """
        Migrate objects from DS to LDAP.
        """
        assert isinstance(ds_base_dn, DN)
        migrated = {}  # {'OBJ': ['PKEY1', 'PKEY2', ...], ...}
        failed = {}  # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...}
        search_bases = self._get_search_bases(options, ds_base_dn,
                                              self.migrate_order)
        migration_start = datetime.datetime.now()

        scope = _supported_scopes[options.get('scope')]

        for ldap_obj_name in self.migrate_order:
            ldap_obj = self.api.Object[ldap_obj_name]

            template = self.migrate_objects[ldap_obj_name]['filter_template']
            oc_list = options[to_cli(
                self.migrate_objects[ldap_obj_name]['oc_option'])]
            search_filter = construct_filter(template, oc_list)

            exclude = options['exclude_%ss' % to_cli(ldap_obj_name)]
            context = dict(ds_ldap=ds_ldap)

            migrated[ldap_obj_name] = []
            failed[ldap_obj_name] = {}

            try:
                entries, truncated = ds_ldap.find_entries(
                    search_filter, ['*'],
                    search_bases[ldap_obj_name],
                    scope,
                    time_limit=0,
                    size_limit=-1)
            except errors.NotFound:
                if not options.get('continue', False):
                    raise errors.NotFound(
                        reason=
                        _('%(container)s LDAP search did not return any result '
                          '(search base: %(search_base)s, '
                          'objectclass: %(objectclass)s)') % {
                              'container': ldap_obj_name,
                              'search_base': search_bases[ldap_obj_name],
                              'objectclass': ', '.join(oc_list)
                          })
                else:
                    truncated = False
                    entries = []
            if truncated:
                self.log.error('%s: %s' %
                               (ldap_obj.name, self.truncated_err_msg))

            blacklists = {}
            for blacklist in ('oc_blacklist', 'attr_blacklist'):
                blacklist_option = self.migrate_objects[ldap_obj_name][
                    blacklist + '_option']
                if blacklist_option is not None:
                    blacklists[blacklist] = options.get(
                        blacklist_option, tuple())
                else:
                    blacklists[blacklist] = tuple()

            # get default primary group for new users
            if 'def_group_dn' not in context and options.get('use_def_group'):
                def_group = config.get('ipadefaultprimarygroup')
                context['def_group_dn'] = api.Object.group.get_dn(def_group)
                try:
                    ldap.get_entry(context['def_group_dn'],
                                   ['gidnumber', 'cn'])
                except errors.NotFound:
                    error_msg = _('Default group for new users not found')
                    raise errors.NotFound(reason=error_msg)

            context['has_upg'] = ldap.has_upg()

            valid_gids = set()
            invalid_gids = set()
            migrate_cnt = 0
            context['migrate_cnt'] = 0
            for entry_attrs in entries:
                context['migrate_cnt'] = migrate_cnt
                s = datetime.datetime.now()

                ava = entry_attrs.dn[0][0]
                if ava.attr == ldap_obj.primary_key.name:
                    # In case if pkey attribute is in the migrated object DN
                    # and the original LDAP is multivalued, make sure that
                    # we pick the correct value (the unique one stored in DN)
                    pkey = ava.value.lower()
                else:
                    pkey = entry_attrs[ldap_obj.primary_key.name][0].lower()

                if pkey in exclude:
                    continue

                entry_attrs.dn = ldap_obj.get_dn(pkey)
                entry_attrs['objectclass'] = list(
                    set(
                        config.get(ldap_obj.object_class_config,
                                   ldap_obj.object_class) +
                        [o.lower() for o in entry_attrs['objectclass']]))
                entry_attrs[ldap_obj.primary_key.name][0] = entry_attrs[
                    ldap_obj.primary_key.name][0].lower()

                callback = self.migrate_objects[ldap_obj_name]['pre_callback']
                if callable(callback):
                    try:
                        entry_attrs.dn = callback(ldap,
                                                  pkey,
                                                  entry_attrs.dn,
                                                  entry_attrs,
                                                  failed[ldap_obj_name],
                                                  config,
                                                  context,
                                                  schema=options['schema'],
                                                  search_bases=search_bases,
                                                  valid_gids=valid_gids,
                                                  invalid_gids=invalid_gids,
                                                  **blacklists)
                        if not entry_attrs.dn:
                            continue
                    except errors.NotFound as e:
                        failed[ldap_obj_name][pkey] = unicode(e.reason)
                        continue

                try:
                    ldap.add_entry(entry_attrs)
                except errors.ExecutionError as e:
                    callback = self.migrate_objects[ldap_obj_name][
                        'exc_callback']
                    if callable(callback):
                        try:
                            callback(ldap, entry_attrs.dn, entry_attrs, e,
                                     options)
                        except errors.ExecutionError as e:
                            failed[ldap_obj_name][pkey] = unicode(e)
                            continue
                    else:
                        failed[ldap_obj_name][pkey] = unicode(e)
                        continue

                migrated[ldap_obj_name].append(pkey)

                callback = self.migrate_objects[ldap_obj_name]['post_callback']
                if callable(callback):
                    callback(ldap, pkey, entry_attrs.dn, entry_attrs,
                             failed[ldap_obj_name], config, context)
                e = datetime.datetime.now()
                d = e - s
                total_dur = e - migration_start
                migrate_cnt += 1
                if migrate_cnt > 0 and migrate_cnt % 100 == 0:
                    api.log.info("%d %ss migrated. %s elapsed." %
                                 (migrate_cnt, ldap_obj_name, total_dur))
                api.log.debug("%d %ss migrated, duration: %s (total %s)" %
                              (migrate_cnt, ldap_obj_name, d, total_dur))

        if 'def_group_dn' in context:
            _update_default_group(ldap, context, True)

        return (migrated, failed)

    def execute(self, ldapuri, bindpw, **options):
        ldap = self.api.Backend.ldap2
        self.normalize_options(options)
        config = ldap.get_ipa_config()

        ds_base_dn = options.get('basedn')
        if ds_base_dn is not None:
            assert isinstance(ds_base_dn, DN)

        # check if migration mode is enabled
        if config.get('ipamigrationenabled', ('FALSE', ))[0] == 'FALSE':
            return dict(result={}, failed={}, enabled=False, compat=True)

        # connect to DS
        cacert = None
        if options.get('cacertfile') is not None:
            # store CA cert into file
            tmp_ca_cert_f = write_tmp_file(options['cacertfile'])
            cacert = tmp_ca_cert_f.name

            # start TLS connection
            ds_ldap = LDAPClient(ldapuri, cacert=cacert)
            ds_ldap.simple_bind(options['binddn'], bindpw)

            tmp_ca_cert_f.close()
        else:
            ds_ldap = LDAPClient(ldapuri, cacert=cacert)
            ds_ldap.simple_bind(options['binddn'], bindpw)

        # check whether the compat plugin is enabled
        if not options.get('compat'):
            try:
                ldap.get_entry(DN(('cn', 'compat'), (api.env.basedn)))
                return dict(result={}, failed={}, enabled=True, compat=False)
            except errors.NotFound:
                pass

        if not ds_base_dn:
            # retrieve base DN from remote LDAP server
            entries, _truncated = ds_ldap.find_entries(
                '',
                ['namingcontexts', 'defaultnamingcontext'],
                DN(''),
                ds_ldap.SCOPE_BASE,
                size_limit=-1,
                time_limit=0,
            )
            if 'defaultnamingcontext' in entries[0]:
                ds_base_dn = DN(entries[0]['defaultnamingcontext'][0])
                assert isinstance(ds_base_dn, DN)
            else:
                try:
                    ds_base_dn = DN(entries[0]['namingcontexts'][0])
                    assert isinstance(ds_base_dn, DN)
                except (IndexError, KeyError) as e:
                    raise Exception(str(e))

        # migrate!
        (migrated, failed) = self.migrate(ldap, config, ds_ldap, ds_base_dn,
                                          options)

        return dict(result=migrated, failed=failed, enabled=True, compat=True)
Пример #34
0
class selinuxusermap_remove_user(LDAPRemoveMember):
    __doc__ = _('Remove users and groups from an SELinux User Map rule.')

    member_attributes = ['memberuser']
    member_count_out = ('%i object removed.', '%i objects removed.')
Пример #35
0
    LDAPRemoveMember)
from ipalib import _, ngettext
from ipalib import output
from ipapython.dn import DN

__doc__ = _("""
MacOS configuration management for FreeIPA

Define and distribute profile configuration for MacOS clients

EXAMPLES:

 Enable MacOS support in FreeIPA. Defines default configuration settings
 that allow MacOS clients to be configured to use all existing FreeIPA masters 
 for identity information and Kerberos authentication:

   ipa macos-enable

 Show current configuration, optionally save JSON property list in a file:

   ipa macos-show [--out=profile.json]

 Import new configuration from a JSON property list format:

   ipa macos-import --data=profile.json

""")

register = Registry()

# MacOS clients actually search over all LDAP naming contexts with LDAP
# filter "(&(objectClass=organizationalUnit)(ou=macosodconfig))" and request a
Пример #36
0
__doc__ = _("""
Migration to IPA

Migrate users and groups from an LDAP server to IPA.

This performs an LDAP query against the remote server searching for
users and groups in a container. In order to migrate passwords you need
to bind as a user that can read the userPassword attribute on the remote
server. This is generally restricted to high-level admins such as
cn=Directory Manager in 389-ds (this is the default bind user).

The default user container is ou=People.

The default group container is ou=Groups.

Users and groups that already exist on the IPA server are skipped.

Two LDAP schemas define how group members are stored: RFC2307 and
RFC2307bis. RFC2307bis uses member and uniquemember to specify group
members, RFC2307 uses memberUid. The default schema is RFC2307bis.

The schema compat feature allows IPA to reformat data for systems that
do not support RFC2307bis. It is recommended that this feature is disabled
during migration to reduce system overhead. It can be re-enabled after
migration. To migrate with it enabled use the "--with-compat" option.

Migrated users do not have Kerberos credentials, they have only their
LDAP password. To complete the migration process, users need to go
to http://ipa.example.com/ipa/migration and authenticate using their
LDAP password in order to generate their Kerberos credentials.

Migration is disabled by default. Use the command ipa config-mod to
enable it:

 ipa config-mod --enable-migration=TRUE

If a base DN is not provided with --basedn then IPA will use either
the value of defaultNamingContext if it is set or the first value
in namingContexts set in the root of the remote LDAP server.

Users are added as members to the default user group. This can be a
time-intensive task so during migration this is done in a batch
mode for every 100 users. As a result there will be a window in which
users will be added to IPA but will not be members of the default
user group.

EXAMPLES:

 The simplest migration, accepting all defaults:
   ipa migrate-ds ldap://ds.example.com:389

 Specify the user and group container. This can be used to migrate user
 and group data from an IPA v1 server:
   ipa migrate-ds --user-container='cn=users,cn=accounts' \\
       --group-container='cn=groups,cn=accounts' \\
       ldap://ds.example.com:389

 Since IPA v2 server already contain predefined groups that may collide with
 groups in migrated (IPA v1) server (for example admins, ipausers), users
 having colliding group as their primary group may happen to belong to
 an unknown group on new IPA v2 server.
 Use --group-overwrite-gid option to overwrite GID of already existing groups
 to prevent this issue:
    ipa migrate-ds --group-overwrite-gid \\
        --user-container='cn=users,cn=accounts' \\
        --group-container='cn=groups,cn=accounts' \\
        ldap://ds.example.com:389

 Migrated users or groups may have object class and accompanied attributes
 unknown to the IPA v2 server. These object classes and attributes may be
 left out of the migration process:
    ipa migrate-ds --user-container='cn=users,cn=accounts' \\
       --group-container='cn=groups,cn=accounts' \\
       --user-ignore-objectclass=radiusprofile \\
       --user-ignore-attribute=radiusgroupname \\
       ldap://ds.example.com:389

LOGGING

Migration will log warnings and errors to the Apache error log. This
file should be evaluated post-migration to correct or investigate any
issues that were discovered.

For every 100 users migrated an info-level message will be displayed to
give the current progress and duration to make it possible to track
the progress of migration.

If the log level is debug, either by setting debug = True in
/etc/ipa/default.conf or /etc/ipa/server.conf, then an entry will be printed
for each user added plus a summary when the default user group is
updated.
""")
Пример #37
0
class idview(LDAPObject):
    """
    ID View object.
    """

    container_dn = api.env.container_views
    object_name = _('ID View')
    object_name_plural = _('ID Views')
    object_class = ['ipaIDView', 'top']
    possible_objectclasses = ['ipaNameResolutionData']
    default_attributes = ['cn', 'description', 'ipadomainresolutionorder']
    allow_rename = True

    label = _('ID Views')
    label_singular = _('ID View')

    takes_params = (
        Str(
            'cn',
            cli_name='name',
            label=_('ID View Name'),
            primary_key=True,
            normalizer=normalize_idview_name,
        ), Str(
            'description?',
            cli_name='desc',
            label=_('Description'),
        ),
        Str(
            'useroverrides',
            label=_('User object overrides'),
            flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
        ),
        Str(
            'groupoverrides',
            label=_('Group object overrides'),
            flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
        ),
        Str(
            'appliedtohosts',
            label=_('Hosts the view applies to'),
            flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
        ),
        Str('ipadomainresolutionorder?',
            cli_name='domain_resolution_order',
            label=_('Domain resolution order'),
            doc=_('colon-separated list of domains used for short name'
                  ' qualification'),
            flags={'no_search'}))

    permission_filter_objectclasses = ['nsContainer']
    managed_permissions = {
        'System: Read ID Views': {
            'ipapermbindruletype': 'all',
            'ipapermright': {'read', 'search', 'compare'},
            'ipapermdefaultattr': {
                'cn',
                'description',
                'ipadomainresolutionorder',
                'objectClass',
            },
        },
    }

    def ensure_possible_objectclasses(self, ldap, dn, entry_attrs, *keys):
        try:
            orig_entry_attrs = ldap.get_entry(dn, ['objectclass'])
        except errors.NotFound:
            # pylint: disable=raising-bad-type, #4772
            raise self.handle_not_found(*keys)

        orig_objectclasses = {
            o.lower()
            for o in orig_entry_attrs.get('objectclass', [])
        }

        entry_attrs['objectclass'] = orig_entry_attrs['objectclass']

        for obj_class_name in self.possible_objectclasses:
            if obj_class_name.lower() not in orig_objectclasses:
                entry_attrs['objectclass'].append(obj_class_name)
Пример #38
0
class idview_show(LDAPRetrieve):
    __doc__ = _('Display information about an ID View.')

    takes_options = LDAPRetrieve.takes_options + (Flag(
        'show_hosts?',
        cli_name='show_hosts',
        doc=_('Enumerate all the hosts the view applies to.'),
    ), )

    has_output_params = global_output_params

    def show_id_overrides(self, dn, entry_attrs):
        ldap = self.obj.backend

        for objectclass, obj_type in [('ipaUserOverride', 'user'),
                                      ('ipaGroupOverride', 'group')]:

            # Attribute to store results is called (user|group)overrides
            attr_name = obj_type + 'overrides'

            try:
                overrides, _truncated = ldap.find_entries(
                    filter="objectclass=%s" % objectclass,
                    attrs_list=['ipaanchoruuid'],
                    base_dn=dn,
                    scope=ldap.SCOPE_ONELEVEL,
                    paged_search=True)

                resolved_overrides = []
                for override in overrides:
                    anchor = override.single_value['ipaanchoruuid']

                    try:
                        name = resolve_anchor_to_object_name(
                            ldap, obj_type, anchor)
                        resolved_overrides.append(name)

                    except (errors.NotFound, errors.ValidationError):
                        # Anchor could not be resolved, use raw
                        resolved_overrides.append(anchor)

                entry_attrs[attr_name] = resolved_overrides

            except errors.NotFound:
                # No overrides found, nothing to do
                pass

    def enumerate_hosts(self, dn, entry_attrs):
        ldap = self.obj.backend

        filter_params = {
            'ipaAssignedIDView': dn,
            'objectClass': 'ipaHost',
        }

        try:
            hosts, _truncated = ldap.find_entries(
                filter=ldap.make_filter(filter_params, rules=ldap.MATCH_ALL),
                attrs_list=['cn'],
                base_dn=api.env.container_host + api.env.basedn,
                scope=ldap.SCOPE_ONELEVEL,
                paged_search=True)

            entry_attrs['appliedtohosts'] = [
                host.single_value['cn'] for host in hosts
            ]
        except errors.NotFound:
            pass

    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
        self.show_id_overrides(dn, entry_attrs)

        # Enumerating hosts is a potentially expensive operation (uses paged
        # search to list all the hosts the ID view applies to). Show the list
        # of the hosts only if explicitly asked for (or asked for --all).
        # Do not display with --raw, since this attribute does not exist in
        # LDAP.

        if ((options.get('show_hosts') or options.get('all'))
                and not options.get('raw')):
            self.enumerate_hosts(dn, entry_attrs)

        return dn
Пример #39
0
    def execute(self, *keys, **options):
        view = keys[-1] if keys else None
        ldap = self.obj.backend

        # Test if idview actually exists, if it does not, NotFound is raised
        if not options.get('clear_view', False):
            view_dn = self.api.Object['idview'].get_dn_if_exists(view)
            assert isinstance(view_dn, DN)

            # Check that we're not applying the Default Trust View
            if view.lower() == DEFAULT_TRUST_VIEW_NAME:
                raise errors.ValidationError(
                    name=_('ID View'),
                    error=_('Default Trust View cannot be applied on hosts'))

        else:
            # In case we are removing assigned view, we modify the host setting
            # the ipaAssignedIDView to None
            view_dn = None

        completed = 0
        succeeded = {'host': []}
        failed = {
            'host': [],
            'hostgroup': [],
        }

        # Make sure we ignore None passed via host or hostgroup, since it does
        # not make sense
        for key in ('host', 'hostgroup'):
            if key in options and options[key] is None:
                del options[key]

        # Generate a list of all hosts to apply the view to
        hosts_to_apply = list(options.get('host', []))

        for hostgroup in options.get('hostgroup', ()):
            try:
                hosts_to_apply += get_complete_hostgroup_member_list(hostgroup)
            except errors.NotFound:
                failed['hostgroup'].append(
                    (hostgroup, unicode(_("not found"))))
            except errors.PublicError as e:
                failed['hostgroup'].append(
                    (hostgroup, "%s : %s" % (e.__class__.__name__, str(e))))

        for host in hosts_to_apply:
            try:
                # Check that the host is not a master
                # IDView must not be applied to masters
                try:
                    host_is_master(ldap, host)
                except errors.ValidationError:
                    failed['host'].append(
                        (host,
                         unicode(
                             _("ID View cannot be applied to IPA master"))))
                    continue
                host_dn = api.Object['host'].get_dn_if_exists(host)

                host_entry = ldap.get_entry(host_dn,
                                            attrs_list=['ipaassignedidview'])
                host_entry['ipaassignedidview'] = view_dn

                ldap.update_entry(host_entry)

                # If no exception was raised, view assignment went well
                completed = completed + 1
                succeeded['host'].append(host)
            except errors.EmptyModlist:
                # If view was already applied, complain about it
                failed['host'].append(
                    (host, unicode(_("ID View already applied"))))
            except errors.NotFound:
                failed['host'].append((host, unicode(_("not found"))))
            except errors.PublicError as e:
                failed['host'].append((host, str(e)))

        # Wrap dictionary containing failures in another dictionary under key
        # 'memberhost', since that is output parameter in global_output_params
        # and thus we get nice output in the CLI
        failed = {'memberhost': failed}

        # Sort the list of affected hosts
        succeeded['host'].sort()

        # Note that we're returning the list of affected hosts even if they
        # were passed via referencing a hostgroup. This is desired, since we
        # want to stress the fact that view is applied on all the current
        # member hosts of the hostgroup and not tied with the hostgroup itself.

        return dict(
            summary=unicode(_(self.msg_summary % {'value': view})),
            succeeded=succeeded,
            completed=completed,
            failed=failed,
        )
Пример #40
0
def resolve_object_to_anchor(ldap, obj_type, obj, fallback_to_ldap):
    """
    Resolves the user/group name to the anchor uuid:
        - first it tries to find the object as user or group in IPA (depending
          on the passed obj_type)
        - if the IPA lookup failed, lookup object SID in the trusted domains

    Takes options:
        ldap - the backend
        obj_type - either 'user' or 'group'
        obj - the name of the object, e.g. 'admin' or 'testuser'
    """

    try:
        entry = ldap.get_entry(api.Object[obj_type].get_dn(obj),
                               attrs_list=['ipaUniqueID', 'objectClass'])

        # First we check this is a valid object to override
        # - for groups, it must have ipaUserGroup objectclass
        # - for users, it must have posixAccount objectclass

        required_objectclass = {
            'user': '******',
            'group': 'ipausergroup',
        }[obj_type]

        if not api.Object[obj_type].has_objectclass(entry['objectclass'],
                                                    required_objectclass):
            raise errors.ValidationError(
                name=_('IPA object'),
                error=_('system IPA objects (e.g. system groups, user '
                        'private groups) cannot be overridden'))

        # The domain prefix, this will need to be reworked once we
        # introduce IPA-IPA trusts
        domain = api.env.domain
        uuid = entry.single_value['ipaUniqueID']

        return "%s%s:%s" % (IPA_ANCHOR_PREFIX, domain, uuid)
    except errors.NotFound:
        pass

    # If not successful, try looking up the object in the trusted domain
    try:
        if _dcerpc_bindings_installed:
            domain_validator = ipaserver.dcerpc.DomainValidator(api)
            if domain_validator.is_configured():
                sid = domain_validator.get_trusted_domain_object_sid(
                    obj, fallback_to_ldap=fallback_to_ldap)

                # We need to verify that the object type is correct
                type_correct = verify_trusted_domain_object_type(
                    domain_validator, obj_type, sid)

                if type_correct:
                    # There is no domain prefix since SID contains information
                    # about the domain
                    return SID_ANCHOR_PREFIX + sid

    except errors.ValidationError:
        # Domain validator raises Validation Error if object name does not
        # contain domain part (either NETBIOS\ prefix or @domain.name suffix)
        pass

    # No acceptable object was found
    raise api.Object[obj_type].handle_not_found(obj)
Пример #41
0
class baseidoverride(LDAPObject):
    """
    Base ID override object.
    """

    parent_object = 'idview'
    container_dn = api.env.container_views

    object_class = ['ipaOverrideAnchor', 'top']
    default_attributes = [
        'description',
        'ipaAnchorUUID',
    ]

    takes_params = (
        Str(
            'ipaanchoruuid',
            cli_name='anchor',
            primary_key=True,
            label=_('Anchor to override'),
        ),
        Str(
            'description?',
            cli_name='desc',
            label=_('Description'),
        ),
    )

    override_object = None

    def get_dn(self, *keys, **options):
        # If user passed raw anchor, do not try
        # to translate it.
        if ANCHOR_REGEX.match(keys[-1]):
            anchor = keys[-1]

        # Otherwise, translate object into a
        # legitimate object anchor.
        else:
            anchor = resolve_object_to_anchor(self.backend,
                                              self.override_object,
                                              keys[-1],
                                              fallback_to_ldap=options.get(
                                                  'fallback_to_ldap', False))
            if all([
                    len(keys[:-1]) == 0, self.override_object == 'user',
                    anchor.startswith(SID_ANCHOR_PREFIX)
            ]):
                keys = (DEFAULT_TRUST_VIEW_NAME, ) + keys

        keys = keys[:-1] + (anchor, )
        return super(baseidoverride, self).get_dn(*keys, **options)

    def set_anchoruuid_from_dn(self, dn, entry_attrs):
        # TODO: Use entry_attrs.single_value once LDAPUpdate supports
        # lists in primary key fields (baseldap.LDAPUpdate.execute)
        entry_attrs['ipaanchoruuid'] = dn[0].value

    def convert_anchor_to_human_readable_form(self, entry_attrs, **options):
        if not options.get('raw'):
            if 'ipaoriginaluid' in entry_attrs:
                originaluid = entry_attrs.single_value['ipaoriginaluid']
                entry_attrs.single_value['ipaanchoruuid'] = originaluid
                return

            anchor = entry_attrs.single_value['ipaanchoruuid']
            if anchor:
                try:
                    object_name = resolve_anchor_to_object_name(
                        self.backend, self.override_object, anchor)
                    entry_attrs.single_value['ipaanchoruuid'] = object_name
                except errors.NotFound:
                    # If we were unable to resolve the anchor,
                    # keep it in the raw form
                    pass
                except errors.ValidationError:
                    # Same as above, ValidationError may be raised when SIDs
                    # are attempted to be converted, but the domain is no
                    # longer trusted
                    pass

    def prohibit_ipa_users_in_default_view(self, dn, entry_attrs):
        # Check if parent object is Default Trust View, if so, prohibit
        # adding overrides for IPA objects

        if dn[1].value.lower() == DEFAULT_TRUST_VIEW_NAME:
            if dn[0].value.startswith(IPA_ANCHOR_PREFIX):
                raise errors.ValidationError(
                    name=_('ID View'),
                    error=_('Default Trust View cannot contain IPA users'))

    def filter_for_anchor(self, ldap, filter, options, obj_type):
        """Modify filter to support user and group names

        Allow users to pass in an IPA user/group name and resolve it to an
        anchor name.

        :param ldap: ldap connection
        :param filter: pre_callback filter
        :param options: option dict
        :param obj_type: 'user' or 'group'
        :return: modified or same filter
        """
        anchor = options.get('ipaanchoruuid', None)
        # return original filter if anchor is absent or correct
        if anchor is None or ANCHOR_REGEX.match(anchor):
            return filter
        try:
            resolved_anchor = resolve_object_to_anchor(
                ldap, obj_type, anchor, options.get('fallback_to_ldap', False))
        except (errors.NotFound, errors.ValidationError):
            # anchor cannot be resolved, let it pass through
            return filter
        else:
            return ldap.make_filter(
                {
                    'objectClass': self.object_class,
                    'ipaanchoruuid': resolved_anchor,
                },
                rules=ldap.MATCH_ALL)

    def get_primary_key_from_dn(self, dn):
        return resolve_anchor_to_object_name(self.backend,
                                             self.override_object, dn[0].value)
Пример #42
0
class idoverrideuser(baseidoverride):

    object_name = _('User ID override')
    object_name_plural = _('User ID overrides')

    label = _('User ID overrides')
    label_singular = _('User ID override')
    allow_rename = True

    # ID user overrides are bindable because we map SASL GSSAPI
    # authentication of trusted users to ID user overrides in the
    # default trust view.
    bindable = True

    permission_filter_objectclasses = ['ipaUserOverride']
    managed_permissions = {
        'System: Read User ID Overrides': {
            'ipapermbindruletype': 'all',
            'ipapermright': {'read', 'search', 'compare'},
            'ipapermdefaultattr': {
                'objectClass', 'ipaAnchorUUID', 'uidNumber', 'description',
                'homeDirectory', 'uid', 'ipaOriginalUid', 'loginShell',
                'gecos', 'gidNumber', 'ipaSshPubkey', 'usercertificate',
                'memberof'
            },
        },
    }

    object_class = baseidoverride.object_class + ['ipaUserOverride']
    possible_objectclasses = [
        'ipasshuser', 'ipaSshGroupOfPubKeys', 'nsmemberof'
    ]
    default_attributes = baseidoverride.default_attributes + [
        'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell',
        'ipaSshPubkey', 'gidNumber', 'gecos', 'usercertificate;binary',
        'memberofindirect', 'memberof'
    ]

    search_display_attributes = baseidoverride.default_attributes + [
        'homeDirectory',
        'uidNumber',
        'uid',
        'ipaOriginalUid',
        'loginShell',
        'ipaSshPubkey',
        'gidNumber',
        'gecos',
    ]

    attribute_members = {
        'memberof': ['group', 'role'],
        'memberofindirect': ['group', 'role'],
    }

    takes_params = baseidoverride.takes_params + (
        Str(
            'uid?',
            pattern=PATTERN_GROUPUSER_NAME,
            pattern_errmsg='may only include letters, numbers, _, -, . and $',
            maxlength=255,
            cli_name='login',
            label=_('User login'),
            normalizer=lambda value: value.lower(),
        ),
        Int(
            'uidnumber?',
            cli_name='uid',
            label=_('UID'),
            doc=_('User ID Number'),
            minvalue=1,
        ),
        Str(
            'gecos?',
            label=_('GECOS'),
        ),
        Int(
            'gidnumber?',
            label=_('GID'),
            doc=_('Group ID Number'),
            minvalue=1,
        ),
        Str(
            'homedirectory?',
            cli_name='homedir',
            label=_('Home directory'),
        ),
        Str(
            'loginshell?',
            cli_name='shell',
            label=_('Login shell'),
        ),
        Str('ipaoriginaluid?', flags=['no_option', 'no_output']),
        Str(
            'ipasshpubkey*',
            validate_sshpubkey,
            cli_name='sshpubkey',
            label=_('SSH public key'),
            normalizer=normalize_sshpubkey,
            flags=['no_search'],
        ),
        Certificate(
            'usercertificate*',
            cli_name='certificate',
            label=_('Certificate'),
            doc=_('Base-64 encoded user certificate'),
            flags=[
                'no_search',
            ],
        ),
    )

    override_object = 'user'

    def update_original_uid_reference(self, entry_attrs):
        anchor = entry_attrs.single_value['ipaanchoruuid']
        try:
            original_uid = resolve_anchor_to_object_name(
                self.backend, self.override_object, anchor)
            entry_attrs['ipaoriginaluid'] = original_uid

        except (errors.NotFound, errors.ValidationError):
            # Anchor could not be resolved, this means we had to specify the
            # object to manipulate using a raw anchor value already, hence
            # we have no way to update the original_uid
            pass

    def convert_usercertificate_pre(self, entry_attrs):
        if 'usercertificate' in entry_attrs:
            entry_attrs['usercertificate;binary'] = entry_attrs.pop(
                'usercertificate')

    def convert_usercertificate_post(self, entry_attrs, **options):
        if 'usercertificate;binary' in entry_attrs:
            entry_attrs['usercertificate'] = entry_attrs.pop(
                'usercertificate;binary')
Пример #43
0
class selinuxusermap_del(LDAPDelete):
    __doc__ = _('Delete a SELinux User Map.')

    msg_summary = _('Deleted SELinux User Map "%(value)s"')
Пример #44
0
class idoverrideuser_del(baseidoverride_del):
    __doc__ = _('Delete an User ID override.')
    msg_summary = _('Deleted User ID override "%(value)s"')
Пример #45
0
class macos_show(LDAPRetrieve):
    __doc__ = _('Display the properties of MacOS Client Profile.')

    msg_summary = _('Display the properties of MacOS Client Profile "%(value)s')
Пример #46
0
class macos_disable(Command):
    __doc__ = _('Delete a Desktop Profile.')

    msg_summary = _('Deleted Desktop Profile "%(value)s"')
Пример #47
0
    unicode = str

_dcerpc_bindings_installed = False

if api.env.in_server:
    try:
        import ipaserver.dcerpc
        _dcerpc_bindings_installed = True
    except ImportError:
        pass

__doc__ = _("""
ID Views

Manage ID Views

IPA allows to override certain properties of users and groups per each host.
This functionality is primarily used to allow migration from older systems or
other Identity Management solutions.
""")

register = Registry()

protected_default_trust_view_error = errors.ProtectedEntryError(
    label=_('ID View'), key=u"Default Trust View", reason=_('system ID View'))

fallback_to_ldap_option = Flag(
    'fallback_to_ldap?',
    default=False,
    label=_('Fallback to AD DC LDAP'),
    doc=_("Allow falling back to AD DC LDAP when resolving AD "
Пример #48
0
    def migrate(self, ldap, config, ds_ldap, ds_base_dn, options):
        """
        Migrate objects from DS to LDAP.
        """
        assert isinstance(ds_base_dn, DN)
        migrated = {}  # {'OBJ': ['PKEY1', 'PKEY2', ...], ...}
        failed = {}  # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...}
        search_bases = self._get_search_bases(options, ds_base_dn,
                                              self.migrate_order)
        migration_start = datetime.datetime.now()

        scope = _supported_scopes[options.get('scope')]

        for ldap_obj_name in self.migrate_order:
            ldap_obj = self.api.Object[ldap_obj_name]

            template = self.migrate_objects[ldap_obj_name]['filter_template']
            oc_list = options[to_cli(
                self.migrate_objects[ldap_obj_name]['oc_option'])]
            search_filter = construct_filter(template, oc_list)

            exclude = options['exclude_%ss' % to_cli(ldap_obj_name)]
            context = dict(ds_ldap=ds_ldap)

            migrated[ldap_obj_name] = []
            failed[ldap_obj_name] = {}

            try:
                entries, truncated = ds_ldap.find_entries(
                    search_filter, ['*'],
                    search_bases[ldap_obj_name],
                    scope,
                    time_limit=0,
                    size_limit=-1)
            except errors.NotFound:
                if not options.get('continue', False):
                    raise errors.NotFound(
                        reason=
                        _('%(container)s LDAP search did not return any result '
                          '(search base: %(search_base)s, '
                          'objectclass: %(objectclass)s)') % {
                              'container': ldap_obj_name,
                              'search_base': search_bases[ldap_obj_name],
                              'objectclass': ', '.join(oc_list)
                          })
                else:
                    truncated = False
                    entries = []
            if truncated:
                self.log.error('%s: %s' %
                               (ldap_obj.name, self.truncated_err_msg))

            blacklists = {}
            for blacklist in ('oc_blacklist', 'attr_blacklist'):
                blacklist_option = self.migrate_objects[ldap_obj_name][
                    blacklist + '_option']
                if blacklist_option is not None:
                    blacklists[blacklist] = options.get(
                        blacklist_option, tuple())
                else:
                    blacklists[blacklist] = tuple()

            # get default primary group for new users
            if 'def_group_dn' not in context and options.get('use_def_group'):
                def_group = config.get('ipadefaultprimarygroup')
                context['def_group_dn'] = api.Object.group.get_dn(def_group)
                try:
                    ldap.get_entry(context['def_group_dn'],
                                   ['gidnumber', 'cn'])
                except errors.NotFound:
                    error_msg = _('Default group for new users not found')
                    raise errors.NotFound(reason=error_msg)

            context['has_upg'] = ldap.has_upg()

            valid_gids = set()
            invalid_gids = set()
            migrate_cnt = 0
            context['migrate_cnt'] = 0
            for entry_attrs in entries:
                context['migrate_cnt'] = migrate_cnt
                s = datetime.datetime.now()

                ava = entry_attrs.dn[0][0]
                if ava.attr == ldap_obj.primary_key.name:
                    # In case if pkey attribute is in the migrated object DN
                    # and the original LDAP is multivalued, make sure that
                    # we pick the correct value (the unique one stored in DN)
                    pkey = ava.value.lower()
                else:
                    pkey = entry_attrs[ldap_obj.primary_key.name][0].lower()

                if pkey in exclude:
                    continue

                entry_attrs.dn = ldap_obj.get_dn(pkey)
                entry_attrs['objectclass'] = list(
                    set(
                        config.get(ldap_obj.object_class_config,
                                   ldap_obj.object_class) +
                        [o.lower() for o in entry_attrs['objectclass']]))
                entry_attrs[ldap_obj.primary_key.name][0] = entry_attrs[
                    ldap_obj.primary_key.name][0].lower()

                callback = self.migrate_objects[ldap_obj_name]['pre_callback']
                if callable(callback):
                    try:
                        entry_attrs.dn = callback(ldap,
                                                  pkey,
                                                  entry_attrs.dn,
                                                  entry_attrs,
                                                  failed[ldap_obj_name],
                                                  config,
                                                  context,
                                                  schema=options['schema'],
                                                  search_bases=search_bases,
                                                  valid_gids=valid_gids,
                                                  invalid_gids=invalid_gids,
                                                  **blacklists)
                        if not entry_attrs.dn:
                            continue
                    except errors.NotFound as e:
                        failed[ldap_obj_name][pkey] = unicode(e.reason)
                        continue

                try:
                    ldap.add_entry(entry_attrs)
                except errors.ExecutionError as e:
                    callback = self.migrate_objects[ldap_obj_name][
                        'exc_callback']
                    if callable(callback):
                        try:
                            callback(ldap, entry_attrs.dn, entry_attrs, e,
                                     options)
                        except errors.ExecutionError as e:
                            failed[ldap_obj_name][pkey] = unicode(e)
                            continue
                    else:
                        failed[ldap_obj_name][pkey] = unicode(e)
                        continue

                migrated[ldap_obj_name].append(pkey)

                callback = self.migrate_objects[ldap_obj_name]['post_callback']
                if callable(callback):
                    callback(ldap, pkey, entry_attrs.dn, entry_attrs,
                             failed[ldap_obj_name], config, context)
                e = datetime.datetime.now()
                d = e - s
                total_dur = e - migration_start
                migrate_cnt += 1
                if migrate_cnt > 0 and migrate_cnt % 100 == 0:
                    api.log.info("%d %ss migrated. %s elapsed." %
                                 (migrate_cnt, ldap_obj_name, total_dur))
                api.log.debug("%d %ss migrated, duration: %s (total %s)" %
                              (migrate_cnt, ldap_obj_name, d, total_dur))

        if 'def_group_dn' in context:
            _update_default_group(ldap, context, True)

        return (migrated, failed)
Пример #49
0
class macos_import(LDAPUpdate):
    __doc__ = _('Import a MacOS Client Profile.')

    msg_summary = _('Imported MacOS Client Profile"%(value)s"')
Пример #50
0
class idview_find(LDAPSearch):
    __doc__ = _('Search for an ID View.')
    msg_summary = ngettext('%(count)d ID View matched',
                           '%(count)d ID Views matched', 0)
Пример #51
0
class macos_enable(Command):
    __doc__ = _('Create a new Desktop Profile.')

    msg_summary = _('Added Desktop Profile "%(value)s"')
Пример #52
0
class idoverridegroup_show(baseidoverride_show):
    __doc__ = _('Display information about an Group ID override.')
Пример #53
0
class selinuxusermap_remove_host(LDAPRemoveMember):
    __doc__ = _('Remove target hosts and hostgroups from an SELinux User Map rule.')

    member_attributes = ['memberhost']
    member_count_out = ('%i object removed.', '%i objects removed.')
Пример #54
0
class idoverridegroup_mod(baseidoverride_mod):
    __doc__ = _('Modify an Group ID override.')
    msg_summary = _('Modified an Group ID override "%(value)s"')
Пример #55
0
class selinuxusermap_mod(LDAPUpdate):
    __doc__ = _('Modify a SELinux User Map.')

    msg_summary = _('Modified SELinux User Map "%(value)s"')

    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
        assert isinstance(dn, DN)
        try:
            _entry_attrs = ldap.get_entry(dn, attrs_list)
        except errors.NotFound:
            raise self.obj.handle_not_found(*keys)

        def is_to_be_deleted(x):
            return (
                (x in _entry_attrs and x in entry_attrs)
                and entry_attrs[x] is None
            )

        # makes sure the local members and hbacrule is not set at the same time
        # memberuser or memberhost could have been set using --setattr
        def is_to_be_set(x):
            return (
                (
                    (x in _entry_attrs and _entry_attrs[x] is not None) or
                    (x in entry_attrs and entry_attrs[x] is not None)
                )
                and not is_to_be_deleted(x)
            )

        are_local_members_to_be_set = any(is_to_be_set(attr)
                                          for attr in ('usercategory',
                                                       'hostcategory',
                                                       'memberuser',
                                                       'memberhost'))

        is_hbacrule_to_be_set = is_to_be_set('seealso')

        # this can disable all modifications if hbacrule and local members were
        # set at the same time bypassing this commad, e.g. using ldapmodify
        if are_local_members_to_be_set and is_hbacrule_to_be_set:
            raise errors.MutuallyExclusiveError(reason=notboth_err)

        if (is_all(entry_attrs, 'usercategory')
                and 'memberuser' in entry_attrs):
            raise errors.MutuallyExclusiveError(
                reason="user category cannot be set to 'all' while there "
                       "are allowed users"
            )
        if (is_all(entry_attrs, 'hostcategory')
                and 'memberhost' in entry_attrs):
            raise errors.MutuallyExclusiveError(
                reason="host category cannot be set to 'all' while there "
                       "are allowed hosts"
            )

        if 'ipaselinuxuser' in entry_attrs:
            validate_selinuxuser_inlist(ldap, entry_attrs['ipaselinuxuser'])

        if 'seealso' in entry_attrs:
            entry_attrs['seealso'] = self.obj._normalize_seealso(
                entry_attrs['seealso']
            )
        return dn

    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
        assert isinstance(dn, DN)
        self.obj._convert_seealso(ldap, entry_attrs, **options)
        return dn
Пример #56
0
class idoverridegroup_del(baseidoverride_del):
    __doc__ = _('Delete an Group ID override.')
    msg_summary = _('Deleted Group ID override "%(value)s"')
Пример #57
0
__doc__ = _("""
SELinux User Mapping

Map IPA users to SELinux users by host.

Hosts, hostgroups, users and groups can be either defined within
the rule or it may point to an existing HBAC rule. When using
--hbacrule option to selinuxusermap-find an exact match is made on the
HBAC rule name, so only one or zero entries will be returned.

EXAMPLES:

 Create a rule, "test1", that sets all users to xguest_u:s0 on the host "server":
   ipa selinuxusermap-add --usercat=all --selinuxuser=xguest_u:s0 test1
   ipa selinuxusermap-add-host --hosts=server.example.com test1

 Create a rule, "test2", that sets all users to guest_u:s0 and uses an existing HBAC rule for users and hosts:
   ipa selinuxusermap-add --usercat=all --hbacrule=webserver --selinuxuser=guest_u:s0 test2

 Display the properties of a rule:
   ipa selinuxusermap-show test2

 Create a rule for a specific user. This sets the SELinux context for
 user john to unconfined_u:s0-s0:c0.c1023 on any machine:
   ipa selinuxusermap-add --hostcat=all --selinuxuser=unconfined_u:s0-s0:c0.c1023 john_unconfined
   ipa selinuxusermap-add-user --users=john john_unconfined

 Disable a rule:
   ipa selinuxusermap-disable test1

 Enable a rule:
   ipa selinuxusermap-enable test1

 Find a rule referencing a specific HBAC rule:
   ipa selinuxusermap-find --hbacrule=allow_some

 Remove a rule:
   ipa selinuxusermap-del john_unconfined

SEEALSO:

 The list controlling the order in which the SELinux user map is applied
 and the default SELinux user are available in the config-show command.
""")
Пример #58
0
def _pre_migrate_user(ldap, pkey, dn, entry_attrs, failed, config, ctx,
                      **kwargs):
    assert isinstance(dn, DN)
    attr_blacklist = ['krbprincipalkey', 'memberofindirect', 'memberindirect']
    attr_blacklist.extend(kwargs.get('attr_blacklist', []))
    ds_ldap = ctx['ds_ldap']
    search_bases = kwargs.get('search_bases', None)
    valid_gids = kwargs['valid_gids']
    invalid_gids = kwargs['invalid_gids']

    if 'gidnumber' not in entry_attrs:
        raise errors.NotFound(reason=_('%(user)s is not a POSIX user') %
                              dict(user=pkey))
    else:
        # See if the gidNumber at least points to a valid group on the remote
        # server.
        if entry_attrs['gidnumber'][0] in invalid_gids:
            api.log.warning('GID number %s of migrated user %s does not point to a known group.' \
                         % (entry_attrs['gidnumber'][0], pkey))
        elif entry_attrs['gidnumber'][0] not in valid_gids:
            try:
                remote_entry = ds_ldap.find_entry_by_attr(
                    'gidnumber', entry_attrs['gidnumber'][0], 'posixgroup',
                    [''], search_bases['group'])
                valid_gids.add(entry_attrs['gidnumber'][0])
            except errors.NotFound:
                api.log.warning('GID number %s of migrated user %s does not point to a known group.' \
                             % (entry_attrs['gidnumber'][0], pkey))
                invalid_gids.add(entry_attrs['gidnumber'][0])
            except errors.SingleMatchExpected as e:
                # GID number matched more groups, this should not happen
                api.log.warning('GID number %s of migrated user %s should match 1 group, but it matched %d groups' \
                             % (entry_attrs['gidnumber'][0], pkey, e.found))
            except errors.LimitsExceeded as e:
                api.log.warning('Search limit exceeded searching for GID %s' %
                                entry_attrs['gidnumber'][0])

    # We don't want to create a UPG so set the magic value in description
    # to let the DS plugin know.
    entry_attrs.setdefault('description', [])
    entry_attrs['description'].append(NO_UPG_MAGIC)

    # fill in required attributes by IPA
    entry_attrs['ipauniqueid'] = 'autogenerate'
    if 'homedirectory' not in entry_attrs:
        homes_root = config.get('ipahomesrootdir', (paths.HOME_DIR, ))[0]
        home_dir = '%s/%s' % (homes_root, pkey)
        home_dir = home_dir.replace('//', '/').rstrip('/')
        entry_attrs['homedirectory'] = home_dir

    if 'loginshell' not in entry_attrs:
        default_shell = config.get('ipadefaultloginshell', [paths.SH])[0]
        entry_attrs.setdefault('loginshell', default_shell)

    # do not migrate all attributes
    for attr in attr_blacklist:
        entry_attrs.pop(attr, None)

    # do not migrate all object classes
    if 'objectclass' in entry_attrs:
        for object_class in kwargs.get('oc_blacklist', []):
            try:
                entry_attrs['objectclass'].remove(object_class)
            except ValueError:  # object class not present
                pass

    _create_kerberos_principals(ldap, pkey, entry_attrs, failed)

    # Fix any attributes with DN syntax that point to entries in the old
    # tree

    for attr in entry_attrs.keys():
        if ldap.has_dn_syntax(attr):
            for ind, value in enumerate(entry_attrs[attr]):
                if not isinstance(value, DN):
                    # value is not DN instance, the automatic encoding may have
                    # failed due to missing schema or the remote attribute type OID was
                    # not detected as DN type. Try to work this around
                    api.log.debug(
                        '%s: value %s of type %s in attribute %s is not a DN'
                        ', convert it', pkey, value, type(value), attr)
                    try:
                        value = DN(value)
                    except ValueError as e:
                        api.log.warning(
                            '%s: skipping normalization of value %s of type %s '
                            'in attribute %s which could not be converted to DN: %s',
                            pkey, value, type(value), attr, e)
                        continue
                try:
                    remote_entry = ds_ldap.get_entry(value, [
                        api.Object.user.primary_key.name,
                        api.Object.group.primary_key.name
                    ])
                except errors.NotFound:
                    api.log.warning(
                        '%s: attribute %s refers to non-existent entry %s' %
                        (pkey, attr, value))
                    continue
                if value.endswith(search_bases['user']):
                    primary_key = api.Object.user.primary_key.name
                    container = api.env.container_user
                elif value.endswith(search_bases['group']):
                    primary_key = api.Object.group.primary_key.name
                    container = api.env.container_group
                else:
                    api.log.warning(
                        '%s: value %s in attribute %s does not belong into any known container'
                        % (pkey, value, attr))
                    continue

                if not remote_entry.get(primary_key):
                    api.log.warning(
                        '%s: there is no primary key %s to migrate for %s' %
                        (pkey, primary_key, attr))
                    continue

                api.log.debug('converting DN value %s for %s in %s' %
                              (value, attr, dn))
                rdnval = remote_entry[primary_key][0].lower()
                entry_attrs[attr][ind] = DN((primary_key, rdnval), container,
                                            api.env.basedn)

    return dn
Пример #59
0
class sudorule_add_runasuser(LDAPAddMember):
    __doc__ = _('Add users and groups for Sudo to execute as.')

    member_attributes = ['ipasudorunas']
    member_count_out = ('%i object added.', '%i objects added.')

    def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
        assert isinstance(dn, DN)

        def check_validity(runas):
            v = unicode(runas)
            if v.upper() == u'ALL':
                return False
            return True

        try:
            _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
        except errors.NotFound:
            raise self.obj.handle_not_found(*keys)

        if any((is_all(_entry_attrs, 'ipasudorunasusercategory'),
                is_all(_entry_attrs, 'ipasudorunasgroupcategory'))):

            raise errors.MutuallyExclusiveError(
                reason=_("users cannot be added when runAs user or runAs "
                         "group category='all'"))

        if 'user' in options:
            for name in options['user']:
                if not check_validity(name):
                    raise errors.ValidationError(name='runas-user',
                          error=unicode(_("RunAsUser does not accept "
                                          "'%(name)s' as a user name")) %
                                          dict(name=name))

        if 'group' in options:
            for name in options['group']:
                if not check_validity(name):
                    raise errors.ValidationError(name='runas-user',
                          error=unicode(_("RunAsUser does not accept "
                                          "'%(name)s' as a group name")) %
                                          dict(name=name))

        for o_desc in (USER_OBJ_SPEC, GROUP_OBJ_SPEC):
            dn = pre_callback_process_external_objects(
                'ipasudorunas', o_desc,
                ldap, dn, found, not_found, *keys, **options)
        return dn

    def post_callback(self, ldap, completed, failed, dn, entry_attrs,
                      *keys, **options):
        assert isinstance(dn, DN)

        completed_ex = {}
        completed_ex['user'] = 0
        completed_ex['group'] = 0
        # Since external_post_callback returns the total number of completed
        # entries yet (that is, any external users it added plus the value of
        # passed variable 'completed', we need to pass 0 as completed,
        # so that the entries added by the framework are not counted twice
        # (once in each call of add_external_post_callback)
        for (o_type, ext_attr) in (('user', 'ipasudorunasextuser'),
                                   ('group', 'ipasudorunasextusergroup')):
            if o_type not in options:
                continue
            (completed_ex[o_type], dn) = \
                add_external_post_callback(ldap, dn,
                                           entry_attrs=entry_attrs,
                                           failed=failed,
                                           completed=0,
                                           memberattr='ipasudorunas',
                                           membertype=o_type,
                                           externalattr=ext_attr,
                                           )

        return (completed + sum(completed_ex.values()), dn)
Пример #60
0
class selinuxusermap(LDAPObject):
    """
    SELinux User Map object.
    """
    container_dn = api.env.container_selinux
    object_name = _('SELinux User Map rule')
    object_name_plural = _('SELinux User Map rules')
    object_class = ['ipaassociation', 'ipaselinuxusermap']
    permission_filter_objectclasses = ['ipaselinuxusermap']
    default_attributes = [
        'cn', 'ipaenabledflag',
        'description', 'usercategory', 'hostcategory',
        'ipaenabledflag', 'memberuser', 'memberhost',
        'seealso', 'ipaselinuxuser',
    ]
    uuid_attribute = 'ipauniqueid'
    rdn_attribute = 'ipauniqueid'
    attribute_members = {
        'memberuser': ['user', 'group'],
        'memberhost': ['host', 'hostgroup'],
    }
    managed_permissions = {
        'System: Read SELinux User Maps': {
            'replaces_global_anonymous_aci': True,
            'ipapermbindruletype': 'all',
            'ipapermright': {'read', 'search', 'compare'},
            'ipapermdefaultattr': {
                'accesstime', 'cn', 'description', 'hostcategory',
                'ipaenabledflag', 'ipaselinuxuser', 'ipauniqueid',
                'memberhost', 'memberuser', 'seealso', 'usercategory',
                'objectclass', 'member',
            },
        },
        'System: Add SELinux User Maps': {
            'ipapermright': {'add'},
            'replaces': [
                '(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Add SELinux User Maps";allow (add) groupdn = "ldap:///cn=Add SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)',
            ],
            'default_privileges': {'SELinux User Map Administrators'},
        },
        'System: Modify SELinux User Maps': {
            'ipapermright': {'write'},
            'ipapermdefaultattr': {
                'cn', 'ipaenabledflag', 'ipaselinuxuser', 'memberhost',
                'memberuser', 'seealso'
            },
            'replaces': [
                '(targetattr = "cn || memberuser || memberhost || seealso || ipaselinuxuser || ipaenabledflag")(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Modify SELinux User Maps";allow (write) groupdn = "ldap:///cn=Modify SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)',
            ],
            'default_privileges': {'SELinux User Map Administrators'},
        },
        'System: Remove SELinux User Maps': {
            'ipapermright': {'delete'},
            'replaces': [
                '(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Remove SELinux User Maps";allow (delete) groupdn = "ldap:///cn=Remove SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)',
            ],
            'default_privileges': {'SELinux User Map Administrators'},
        },
    }

    # These maps will not show as members of other entries

    label = _('SELinux User Maps')
    label_singular = _('SELinux User Map')

    takes_params = (
        Str('cn',
            cli_name='name',
            label=_('Rule name'),
            primary_key=True,
        ),
        Str('ipaselinuxuser', validate_selinuxuser,
            cli_name='selinuxuser',
            label=_('SELinux User'),
        ),
        Str('seealso?',
            cli_name='hbacrule',
            label=_('HBAC Rule'),
            doc=_('HBAC Rule that defines the users, groups and hostgroups'),
        ),
        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', ),
        ),
        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'],
        ),
    )

    def _normalize_seealso(self, seealso):
        """
        Given a HBAC rule name verify its existence and return the dn.
        """
        if not seealso:
            return None

        try:
            dn = DN(seealso)
            return str(dn)
        except ValueError:
            try:
                entry_attrs = self.backend.find_entry_by_attr(
                    self.api.Object['hbacrule'].primary_key.name,
                    seealso,
                    self.api.Object['hbacrule'].object_class,
                    [''],
                    DN(self.api.Object['hbacrule'].container_dn, api.env.basedn))
                seealso = entry_attrs.dn
            except errors.NotFound:
                raise errors.NotFound(reason=_('HBAC rule %(rule)s not found') % dict(rule=seealso))

        return seealso

    def _convert_seealso(self, ldap, entry_attrs, **options):
        """
        Convert an HBAC rule dn into a name
        """
        if options.get('raw', False):
            return

        if 'seealso' in entry_attrs:
            hbac_attrs = ldap.get_entry(entry_attrs['seealso'][0], ['cn'])
            entry_attrs['seealso'] = hbac_attrs['cn'][0]