Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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)))
Ejemplo n.º 3
0
 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)))
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
    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")))
Ejemplo n.º 6
0
 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))
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
    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)))
Ejemplo n.º 9
0
        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
Ejemplo n.º 10
0
    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)))
Ejemplo n.º 11
0
    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")))