def pre_callback(self, ldap, dn, *keys, **options): pkey = self.obj.get_primary_key_from_dn(dn) if options.get('force', False): self.add_message( messages.ServerRemovalWarning( message=_("Forcing removal of %(hostname)s") % dict(hostname=pkey))) # check the topology errors before and after removal self.context.topology_connectivity = topology.TopologyConnectivity( self.api) if options.get('ignore_topology_disconnect', False): self.add_message( messages.ServerRemovalWarning( message=_("Ignoring topology connectivity errors."))) else: self._check_topology_connectivity( self.context.topology_connectivity, pkey) # ensure that we are not removing last CA/DNS server, DNSSec master and # CA renewal master self._ensure_last_of_role(pkey, ignore_last_of_role=options.get( 'ignore_last_of_role', False)) if self.api.Command.ca_is_enabled()['result']: try: with self.api.Backend.ra_securitydomain as domain_api: domain_api.delete_domain(pkey, 'KRA') domain_api.delete_domain(pkey, 'CA') except Exception as e: self.add_message( messages.ServerRemovalWarning(message=_( "Failed to remove server from security domain: %s" % e))) # remove the references to master's ldap/http principals self._remove_server_principal_references(pkey) # remove Custodia encryption and signing keys self._remove_server_custodia_keys(ldap, pkey) # finally destroy all Kerberos principals self._remove_server_host_services(ldap, pkey) # try to clean up the leftover DNS entries self._cleanup_server_dns_records(pkey) # try to clean up the DNS config from ldap self._cleanup_server_dns_config(pkey) return dn
def _remove_server_host_services(self, ldap, master): """ delete server kerberos key and all its svc principals """ try: # do not delete ldap principal if server-del command # has been called on a machine which is being deleted # since this will break replication. # ldap principal to be cleaned later by topology plugin # necessary changes to a topology plugin are tracked # under https://pagure.io/freeipa/issue/7359 if master == self.api.env.host: filter = ('(&(krbprincipalname=*/{}@{})' '(!(krbprincipalname=ldap/*)))'.format( master, self.api.env.realm)) else: filter = '(krbprincipalname=*/{}@{})'.format( master, self.api.env.realm) entries = ldap.get_entries(self.api.env.basedn, ldap.SCOPE_SUBTREE, filter=filter) if entries: entries.sort(key=lambda x: len(x.dn), reverse=True) for entry in entries: ldap.delete_entry(entry) except errors.NotFound: pass except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to cleanup server principals/keys: " "%(err)s") % dict(err=e)))
def _remove_server_custodia_keys(self, ldap, master): """ Delete all Custodia encryption and signing keys """ conn = self.Backend.ldap2 env = self.api.env # search for memberPrincipal=*/fqdn@realm member_filter = ldap.make_filter_from_attr('memberPrincipal', "/{}@{}".format( master, env.realm), exact=False, leading_wildcard=True, trailing_wildcard=False) custodia_subtree = DN(env.container_custodia, env.basedn) try: entries = conn.get_entries(custodia_subtree, ldap.SCOPE_SUBTREE, filter=member_filter) for entry in entries: conn.delete_entry(entry) except errors.NotFound: pass except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to clean up Custodia keys for " "%(master)s: %(err)s") % dict(master=master, err=e)))
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): if (options.get('force', False) and isinstance(exc, errors.NotFound) and call_func.__name__ == 'delete_entry'): self.add_message(message=messages.ServerRemovalWarning( message=_("Server has already been deleted"))) return raise exc
def _cleanup_server_dns_records(self, hostname, **options): if not self.api.Command.dns_is_enabled(**options): return try: bindinstance.remove_master_dns_records(hostname, self.api.env.realm) dnskeysyncinstance.remove_replica_public_keys(hostname) except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to cleanup %(hostname)s DNS entries: " "%(err)s") % dict(hostname=hostname, err=e))) self.add_message( messages.ServerRemovalWarning( message=_("You may need to manually remove them from the " "tree")))
def handler(msg, ignore_last_of_role): if ignore_last_of_role: self.add_message( messages.ServerRemovalWarning( message=msg ) ) else: raise errors.ServerRemovalError(reason=_(msg))
def pre_callback(self, ldap, dn, *keys, **options): pkey = self.obj.get_primary_key_from_dn(dn) if options.get('force', False): self.add_message( messages.ServerRemovalWarning( message=_("Forcing removal of %(hostname)s") % dict(hostname=pkey))) # check the topology errors before and after removal self.context.topology_connectivity = topology.TopologyConnectivity( self.api) if options.get('ignore_topology_disconnect', False): self.add_message( messages.ServerRemovalWarning( message=_("Ignoring topology connectivity errors."))) else: self._check_topology_connectivity( self.context.topology_connectivity, pkey) # ensure that we are not removing last CA/DNS server, DNSSec master and # CA renewal master self._ensure_last_of_role(pkey, ignore_last_of_role=options.get( 'ignore_last_of_role', False)) # remove the references to master's ldap/http principals self._remove_server_principal_references(pkey) # remove Custodia encryption and signing keys self._remove_server_custodia_keys(ldap, pkey) # finally destroy all Kerberos principals self._remove_server_host_services(ldap, pkey) # try to clean up the leftover DNS entries self._cleanup_server_dns_records(pkey) return dn
def _remove_server_host_services(self, ldap, master): """ delete server kerberos key and all its svc principals """ try: entries = ldap.get_entries( self.api.env.basedn, ldap.SCOPE_SUBTREE, filter='(krbprincipalname=*/{}@{})'.format( master, self.api.env.realm)) if entries: entries.sort(key=lambda x: len(x.dn), reverse=True) for entry in entries: ldap.delete_entry(entry) except errors.NotFound: pass except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to cleanup server principals/keys: " "%(err)s") % dict(err=e)))
def wait_for_segment_removal(hostname, master_cns, suffix_name, orig_errors, new_errors): i = 0 while True: left = self.api.Command.topologysegment_find( suffix_name, iparepltoposegmentleftnode=hostname, sizelimit=0)['result'] right = self.api.Command.topologysegment_find( suffix_name, iparepltoposegmentrightnode=hostname, sizelimit=0)['result'] # Relax check if topology was or is disconnected. Disconnected # topology can contain segments with already deleted servers # Check only if segments of servers, which can contact this # server, and the deleted server were removed. # This code should handle a case where there was a topology # with a central node(B): A <-> B <-> C, where A is current # server. After removal of B, topology will be disconnected and # removal of segment B <-> C won't be replicated back to server # A, therefore presence of the segment has to be ignored. if orig_errors or new_errors: # use errors after deletion because we don't care if some # server can't contact the deleted one cant_contact_me = [ e[0] for e in new_errors if starting_host in e[2] ] can_contact_me = set(master_cns) - set(cant_contact_me) left = [ s for s in left if s['iparepltoposegmentrightnode'][0] in can_contact_me ] right = [ s for s in right if s['iparepltoposegmentleftnode'][0] in can_contact_me ] if not left and not right: self.add_message( messages.ServerRemovalInfo( message=_("Agreements deleted"))) return time.sleep(2) if i == 2: # taking too long, something is wrong, report logger.info( "Waiting for removal of replication agreements") if i > 90: logger.info("Taking too long, skipping") logger.info("Following segments were not deleted:") self.add_message( messages.ServerRemovalWarning( message=_("Following segments were not deleted:"))) for s in left: self.add_message( messages.ServerRemovalWarning(message=u" %s" % s['cn'][0])) for s in right: self.add_message( messages.ServerRemovalWarning(message=u" %s" % s['cn'][0])) return i += 1
def _remove_server_principal_references(self, master): """ This method removes information about the replica in parts of the shared tree that expose it, so clients stop trying to use this replica. """ conn = self.Backend.ldap2 env = self.api.env master_principal = "{}@{}".format(master, env.realm).encode('utf-8') # remove replica memberPrincipal from s4u2proxy configuration s4u2proxy_subtree = DN(env.container_s4u2proxy, env.basedn) dn1 = DN(('cn', 'ipa-http-delegation'), s4u2proxy_subtree) member_principal1 = b"HTTP/%s" % master_principal dn2 = DN(('cn', 'ipa-ldap-delegation-targets'), s4u2proxy_subtree) member_principal2 = b"ldap/%s" % master_principal dn3 = DN(('cn', 'ipa-cifs-delegation-targets'), s4u2proxy_subtree) member_principal3 = b"cifs/%s" % master_principal for (dn, member_principal) in ((dn1, member_principal1), (dn2, member_principal2), (dn3, member_principal3)): try: mod = [(ldap.MOD_DELETE, 'memberPrincipal', member_principal)] conn.conn.modify_s(str(dn), mod) except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE): logger.debug( "Replica (%s) memberPrincipal (%s) not found in %s", master, member_principal.decode('utf-8'), dn) except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to clean memberPrincipal " "%(principal)s from s4u2proxy entry %(dn)s: " "%(err)s") % dict(principal=(member_principal.decode('utf-8')), dn=dn, err=e))) try: etc_basedn = DN(('cn', 'etc'), env.basedn) filter = '(dnaHostname=%s)' % master entries = conn.get_entries(etc_basedn, ldap.SCOPE_SUBTREE, filter=filter) if len(entries) != 0: for entry in entries: conn.delete_entry(entry) except errors.NotFound: pass except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to clean up DNA hostname entries for " "%(master)s: %(err)s") % dict(master=master, err=e))) try: dn = DN(('cn', 'default'), ('ou', 'profile'), env.basedn) ret = conn.get_entry(dn) srvlist = ret.single_value.get('defaultServerList', '') srvlist = srvlist.split() if master in srvlist: srvlist.remove(master) if not srvlist: del ret['defaultServerList'] else: ret['defaultServerList'] = ' '.join(srvlist) conn.update_entry(ret) except (errors.NotFound, errors.MidairCollision, errors.EmptyModlist): pass except Exception as e: self.add_message( messages.ServerRemovalWarning( message=_("Failed to remove server %(master)s from server " "list: %(err)s") % dict(master=master, err=e)))
def _ensure_last_of_role(self, hostname, ignore_last_of_role=False): """ 1. When deleting server, check if there will be at least one remaining DNS and CA server. 2. Pick CA renewal master """ def handler(msg, ignore_last_of_role): if ignore_last_of_role: self.add_message(messages.ServerRemovalWarning(message=msg)) else: raise errors.ServerRemovalError(reason=_(msg)) ipa_config = self.api.Command.config_show()['result'] ipa_masters = ipa_config['ipa_master_server'] # skip these checks if the last master is being removed if len(ipa_masters) <= 1: return if self.api.Command.dns_is_enabled()['result']: dns_config = self.api.Command.dnsconfig_show()['result'] dns_servers = dns_config.get('dns_server_server', []) dnssec_keymaster = dns_config.get('dnssec_key_master_server', []) if dnssec_keymaster == hostname: handler( _("Replica is active DNSSEC key master. Uninstall " "could break your DNS system. Please disable or " "replace DNSSEC key master first."), ignore_last_of_role) if dns_servers == [hostname]: handler( _("Deleting this server will leave your installation " "without a DNS."), ignore_last_of_role) if self.api.Command.ca_is_enabled()['result']: try: vault_config = self.api.Command.vaultconfig_show()['result'] kra_servers = vault_config.get('kra_server_server', []) except errors.InvocationError: # KRA is not configured pass else: if kra_servers == [hostname]: handler( _("Deleting this server is not allowed as it would " "leave your installation without a KRA."), ignore_last_of_role) ca_servers = ipa_config.get('ca_server_server', []) ca_renewal_master = ipa_config.get('ca_renewal_master_server', []) if ca_servers == [hostname]: handler( _("Deleting this server is not allowed as it would " "leave your installation without a CA."), ignore_last_of_role) # change the renewal master if there is other master with CA if ca_renewal_master == hostname: other_cas = [ca for ca in ca_servers if ca != hostname] if other_cas: self.api.Command.config_mod( ca_renewal_master_server=other_cas[0]) if ignore_last_of_role: self.add_message( messages.ServerRemovalWarning( message=_("Ignoring these warnings and proceeding with " "removal")))