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
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)
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
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'") )
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
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'))
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'])}
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)
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
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
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
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
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
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) )
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"))
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
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)
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
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
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
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
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))
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 ..."))
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)
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
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
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)
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 __get_trusted_domain_user_and_groups(self, object_name): """ Returns a tuple with user SID and a list of SIDs of all groups he is a member of. LIMITATIONS: - only Trusted Admins group members can use this function as it uses secret for IPA-Trusted domain link - List of group SIDs does not contain group memberships outside of the trusted domain """ components = normalize_name(object_name) domain = components.get('domain') flatname = components.get('flatname') name = components.get('name') is_valid_sid = is_sid_valid(object_name) if is_valid_sid: # Find a trusted domain for the SID domain = self.get_domain_by_sid(object_name) # Now search a trusted domain for a user with this SID attrs = ['cn'] filter = '(&(objectClass=user)(objectSid=%(sid)s))' \ % dict(sid=object_name) try: entries = self.get_trusted_domain_objects(domain=domain, filter=filter, attrs=attrs, scope=_ldap.SCOPE_SUBTREE) except errors.NotFound: raise errors.NotFound(reason=_('trusted domain user not found')) user_dn = entries[0][0] elif domain or flatname: attrs = ['cn'] filter = '(&(sAMAccountName=%(name)s)(objectClass=user))' \ % dict(name=name) try: entries = self.get_trusted_domain_objects(domain, flatname, filter, attrs, _ldap.SCOPE_SUBTREE) except errors.NotFound: raise errors.NotFound(reason=_('trusted domain user not found')) user_dn = entries[0][0] else: # No domain or realm specified, ambiguous search raise errors.ValidationError(name=_('trusted domain object'), error= _('Ambiguous search, user domain was not specified')) # Get SIDs of user object and it's groups # tokenGroups attribute must be read with a scope BASE for a known user # distinguished name to avoid search error attrs = ['objectSID', 'tokenGroups'] filter = "(objectClass=user)" entries = self.get_trusted_domain_objects(domain, flatname, filter, attrs, _ldap.SCOPE_BASE, user_dn) object_sid = self.__sid_to_str(entries[0][1]['objectSid'][0]) group_sids = [self.__sid_to_str(sid) for sid in entries[0][1]['tokenGroups']] return (object_sid, group_sids)
def 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'))
class idoverridegroup_add(baseidoverride_add): __doc__ = _('Add a new Group ID override.') msg_summary = _('Added Group ID override "%(value)s"')
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)
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)
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.')
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
__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. """)
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)
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
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, )
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)
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)
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')
class selinuxusermap_del(LDAPDelete): __doc__ = _('Delete a SELinux User Map.') msg_summary = _('Deleted SELinux User Map "%(value)s"')
class idoverrideuser_del(baseidoverride_del): __doc__ = _('Delete an User ID override.') msg_summary = _('Deleted User ID override "%(value)s"')
class macos_show(LDAPRetrieve): __doc__ = _('Display the properties of MacOS Client Profile.') msg_summary = _('Display the properties of MacOS Client Profile "%(value)s')
class macos_disable(Command): __doc__ = _('Delete a Desktop Profile.') msg_summary = _('Deleted Desktop Profile "%(value)s"')
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 "
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)
class macos_import(LDAPUpdate): __doc__ = _('Import a MacOS Client Profile.') msg_summary = _('Imported MacOS Client Profile"%(value)s"')
class idview_find(LDAPSearch): __doc__ = _('Search for an ID View.') msg_summary = ngettext('%(count)d ID View matched', '%(count)d ID Views matched', 0)
class macos_enable(Command): __doc__ = _('Create a new Desktop Profile.') msg_summary = _('Added Desktop Profile "%(value)s"')
class idoverridegroup_show(baseidoverride_show): __doc__ = _('Display information about an Group ID override.')
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.')
class idoverridegroup_mod(baseidoverride_mod): __doc__ = _('Modify an Group ID override.') msg_summary = _('Modified an Group ID override "%(value)s"')
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
class idoverridegroup_del(baseidoverride_del): __doc__ = _('Delete an Group ID override.') msg_summary = _('Deleted Group ID override "%(value)s"')
__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. """)
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
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)
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]