Clean up more files and directories created by the installer(s)

Ideally all files created during an IPA server installation are
removed by the uninstaller. Some files are purposefully left,
like token passwords, private keys, logs and more. Add an
allow list for those files.

Include a test to catch any additional files that may be created
and left behind.

Fixes: https://pagure.io/freeipa/issue/8080

Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
This commit is contained in:
Rob Crittenden
2023-06-12 18:06:06 -04:00
committed by Florence Blanc-Renaud
parent 60c127d197
commit 9e364910f5
15 changed files with 182 additions and 12 deletions

View File

@@ -3455,7 +3455,7 @@ def uninstall(options):
# - sssd was removed after install and before uninstall
# - there are no active domains
# in both cases we cannot continue with SSSD
pass
all_domains = []
if hostname is None:
hostname = FQDN
@@ -3514,7 +3514,9 @@ def uninstall(options):
if result.returncode != 0:
logger.error("Unenrolling host failed: %s", result.error_log)
if os.path.exists(paths.IPA_DEFAULT_CONF):
if os.path.exists(paths.IPA_DEFAULT_CONF) and os.path.exists(
paths.KRB5_KEYTAB
):
logger.info(
"Removing Kerberos service principals from /etc/krb5.keytab")
try:
@@ -3566,6 +3568,18 @@ def uninstall(options):
# Clean up the SSSD cache before SSSD service is stopped or restarted
remove_file(paths.SSSD_MC_GROUP)
remove_file(paths.SSSD_MC_PASSWD)
remove_file(paths.SSSD_MC_INITGROUPS)
remove_file(paths.SSSD_MC_SID)
for root, _dirs, files in os.walk(paths.SSSD_PIPES):
for file in files:
remove_file(os.path.join(root, file))
for domain in all_domains:
name = f"domain_realm_{domain.replace('.', '_')}"
filename = os.path.join(paths.SSSD_PUBCONF_KRB5_INCLUDE_D_DIR, name)
if os.path.exists(filename):
remove_file(filename)
if was_sssd_installed:
try:
@@ -3585,6 +3599,14 @@ def uninstall(options):
sssd_ccache_file = os.path.join(paths.SSSD_DB, sssd_domain_ccache)
remove_file(sssd_ccache_file)
remove_file(paths.SSSD_LDB)
remove_file(paths.SSSD_CONFIG_LDB)
remove_file(paths.SSSD_SECRETS)
sssd_timestamps = "timestamps_" + ipa_domain + ".ldb"
sssd_timestamps_file = os.path.join(paths.SSSD_DB, sssd_timestamps)
remove_file(sssd_timestamps_file)
# Next if-elif-elif construction deals with sssd.conf file.
# Old pre-IPA domains are preserved due merging the old sssd.conf
# during the installation of ipa-client but any new domains are
@@ -3745,6 +3767,7 @@ def uninstall(options):
# Remove the IPA configuration file
remove_file(paths.IPA_DEFAULT_CONF)
remove_file(paths.IPA_DEFAULT_CONF + '.ipabkp')
# Remove misc backups
remove_file(paths.OPENLDAP_LDAP_CONF + '.ipabkp')

View File

@@ -251,6 +251,7 @@ class BasePathNamespace:
CERTMONGER_DOGTAG_SUBMIT = "/usr/libexec/certmonger/dogtag-submit"
IPA_SERVER_GUARD = "/usr/libexec/certmonger/ipa-server-guard"
GENERATE_RNDC_KEY = "/usr/libexec/generate-rndc-key.sh"
RNDC_KEY = "/etc/rndc.key"
LIBEXEC_IPA_DIR = "/usr/libexec/ipa"
IPA_DNSKEYSYNCD_REPLICA = "/usr/libexec/ipa/ipa-dnskeysync-replica"
IPA_DNSKEYSYNCD = "/usr/libexec/ipa/ipa-dnskeysyncd"
@@ -325,6 +326,7 @@ class BasePathNamespace:
IPA_KASP_DB_BACKUP = "/var/lib/ipa/ipa-kasp.db.backup"
DNSSEC_TOKENS_DIR = "/var/lib/ipa/dnssec/tokens"
DNSSEC_SOFTHSM_PIN = "/var/lib/ipa/dnssec/softhsm_pin"
DNSSEC_ENGINE_SOCK = "/run/opendnssec/engine.sock"
IPA_CA_CSR = "/var/lib/ipa/ca.csr"
IPA_CACERT_MANAGE = "/usr/sbin/ipa-cacert-manage"
IPA_CERTUPDATE = "/usr/sbin/ipa-certupdate"
@@ -349,6 +351,11 @@ class BasePathNamespace:
SSSD_MC_GROUP = "/var/lib/sss/mc/group"
SSSD_MC_PASSWD = "/var/lib/sss/mc/passwd"
SSSD_MC_INITGROUPS = "/var/lib/sss/mc/initgroups"
SSSD_MC_SID = "/var/lib/sss/mc/sid"
SSSD_PIPES = "/var/lib/sss/pipes"
SSSD_LDB = "/var/lib/sss/db/sssd.ldb"
SSSD_CONFIG_LDB = "/var/lib/sss/db/config.ldb"
SSSD_SECRETS = "/var/lib/sss/secrets/secrets.ldb"
SSSD_PUBCONF_DIR = "/var/lib/sss/pubconf"
SSSD_PUBCONF_KNOWN_HOSTS = "/var/lib/sss/pubconf/known_hosts"
SSSD_PUBCONF_KRB5_INCLUDE_D_DIR = "/var/lib/sss/pubconf/krb5.include.d/"

View File

@@ -369,6 +369,7 @@ class BaseTaskNamespace:
os.unlink(paths.SYSTEMD_RESOLVED_IPA_CONF)
knownservices["systemd-resolved"].reload_or_restart()
ipautil.remove_directory(paths.SYSTEMD_RESOLVED_CONF_DIR)
def configure_pkcs11_modules(self, fstore):
"""Disable p11-kit modules

View File

@@ -1658,9 +1658,16 @@ def remove_ccache(ccache_path=None, run_as=None):
"Failed to clear Kerberos credentials cache: %s", e)
def remove_file(filename):
def remove_file(filename, only_if_empty=False):
"""Remove a file and log any exceptions raised.
:only_if_empty: only remove the file if empty. Default False.
"""
if only_if_empty and os.path.exists(filename):
file_stat = os.stat(filename)
if file_stat.st_size > 0:
logger.debug('%s is not empty.', filename)
return
try:
os.unlink(filename)
except Exception as e:
@@ -1669,6 +1676,15 @@ def remove_file(filename):
logger.error('Error removing %s: %s', filename, str(e))
def remove_directory(dir):
"""Remove an empty directory."""
try:
os.rmdir(dir)
except OSError as e:
if e.errno not in {errno.ENOENT, errno.ENOTEMPTY}:
logger.error("Failed to remove directory %s", dir)
def rmtree(path):
"""
Remove a directory structure and log any exceptions raised.

View File

@@ -1347,5 +1347,16 @@ class BindInstance(service.Service):
ipautil.remove_file(paths.NAMED_CONF_BAK)
ipautil.remove_file(paths.NAMED_CUSTOM_CONF)
ipautil.remove_file(paths.NAMED_CUSTOM_OPTIONS_CONF)
ipautil.remove_file(paths.NAMED_LOGGING_OPTIONS_CONF)
ipautil.remove_file(paths.RNDC_KEY)
ipautil.remove_file(
os.path.join(paths.NAMED_VAR_DIR, "_default.tsigkeys")
)
try:
while self.fstore.restore_file(self.keytab):
pass
except ValueError:
pass
ipautil.remove_keytab(self.keytab)
ipautil.remove_ccache(run_as=self.service_user)

View File

@@ -1112,6 +1112,9 @@ class CAInstance(DogtagInstance):
logger.warning("Error while removing CRL publish "
"directory: %s", e)
ipautil.remove_file(paths.DOGTAG_ADMIN_P12)
ipautil.remove_file(paths.CACERT_P12)
def unconfigure_certmonger_renewal_guard(self):
if not self.is_configured():
return

View File

@@ -147,6 +147,7 @@ class CustodiaInstance(SimpleServiceInstance):
})
keystore.remove_server_keys_file()
ipautil.remove_file(self.config_file)
ipautil.remove_file(paths.IPA_CUSTODIA_SOCKET)
sysupgrade.set_upgrade_state('custodia', 'installed', False)
def __gen_keys(self):

View File

@@ -511,6 +511,9 @@ class DNSKeySyncInstance(service.Service):
# do not delete *so pin*, user can need it to get token data
ipautil.remove_file(paths.DNSSEC_SOFTHSM_PIN)
ipautil.remove_file(paths.DNSSEC_SOFTHSM2_CONF)
ipautil.remove_file(paths.DNSSEC_OPENSSL_CONF)
ipautil.rmtree(paths.IPA_DNSSEC_DIR)
try:
shutil.rmtree(paths.DNSSEC_TOKENS_DIR)

View File

@@ -1103,6 +1103,11 @@ class DsInstance(service.Service):
except ipautil.CalledProcessError:
logger.error("Failed to remove DS instance. You may "
"need to remove instance data manually")
destfile = paths.SLAPD_INSTANCE_SYSTEMD_IPA_ENV_TEMPLATE % (
serverid
)
ipautil.remove_file(destfile)
ipautil.remove_directory(os.path.dirname(destfile))
else:
logger.error("Failed to remove DS instance. No serverid present "

View File

@@ -23,7 +23,6 @@ from __future__ import absolute_import
import logging
import os
import glob
import errno
import shlex
import shutil
import tempfile
@@ -571,14 +570,13 @@ class HTTPInstance(service.Service):
for filename in remove_files:
ipautil.remove_file(filename)
try:
os.rmdir(paths.SYSTEMD_SYSTEM_HTTPD_D_DIR)
except OSError as e:
if e.errno not in {errno.ENOENT, errno.ENOTEMPTY}:
logger.error(
"Failed to remove directory %s",
paths.SYSTEMD_SYSTEM_HTTPD_D_DIR
)
ipautil.remove_file(paths.HTTPD_NSS_CONF, only_if_empty=True)
for d in (
paths.SYSTEMD_SYSTEM_HTTPD_D_DIR,
paths.HTTPD_ALIAS_DIR
):
ipautil.remove_directory(d)
# Restore SELinux boolean states
boolean_states = {name: self.restore_state(name)
@@ -595,6 +593,14 @@ class HTTPInstance(service.Service):
if enabled:
self.enable()
ipautil.run(
[paths.SYSTEMCTL, 'disable', 'ipa-ccache-sweep.timer']
)
ipautil.remove_file(paths.IPA_CCACHE_SWEEPER_GSSPROXY_SOCK)
for filename in os.listdir(paths.IPA_CCACHES):
ipautil.remove_file(os.path.join(paths.IPA_CCACHES, filename))
def stop_tracking_certificates(self):
try:
certmonger.stop_tracking(certfile=paths.HTTPD_CERT_FILE)

View File

@@ -79,6 +79,11 @@ class KRAInstance(DogtagInstance):
config=paths.KRA_CS_CFG_PATH,
)
def uninstall(self):
DogtagInstance.uninstall(self)
ipautil.remove_file(paths.KRACERT_P12)
def configure_instance(self, realm_name, host_name, dm_password,
admin_password, pkcs12_info=None, master_host=None,
subject_base=None, ca_subject=None,

View File

@@ -663,3 +663,7 @@ class KrbInstance(service.Service):
self.kpasswd = KpasswdInstance()
self.kpasswd.uninstall()
ipautil.remove_file(paths.KRB5_KEYTAB)
ipautil.remove_file(paths.KRB5_FREEIPA)
ipautil.remove_file(paths.KRB5_FREEIPA_SERVER)

View File

@@ -365,3 +365,5 @@ class OpenDNSSECInstance(service.Service):
if running:
self.restart()
ipautil.remove_file(paths.DNSSEC_ENGINE_SOCK)

View File

@@ -1326,6 +1326,9 @@ def uninstall(installer):
logger.warning("Failed to remove file %s: %s",
paths.IPA_RENEWAL_LOCK, e)
ipautil.remove_file(paths.SVC_LIST_FILE)
ipautil.rmtree('/root/.cache/ipa')
print("Removing IPA client configuration")
try:
result = run([paths.IPA_CLIENT_INSTALL, "--on-master",

View File

@@ -12,6 +12,7 @@ pieces if possible.
from __future__ import absolute_import
from difflib import unified_diff
import os
from ipatests.test_integration.base import IntegrationTest
@@ -137,3 +138,82 @@ class TestUninstallWithoutDNS(IntegrationTest):
related: https://pagure.io/freeipa/issue/8630
"""
tasks.uninstall_master(self.master)
class TestUninstallCleanup(IntegrationTest):
"""Test installer hostname validator."""
num_replicas = 0
before = None
after = None
@classmethod
def install(cls, mh):
# These create files on first start
for svc in ('certmonger', 'sssd',):
cls.master.run_command(['systemctl', 'start', svc])
cls.before = cls.master.run_command(
"find /etc/ /run/ /var/ /root/ -mount | sort"
).stdout_text.split('\n')
tasks.install_master(cls.master, setup_dns=True,
setup_kra=True)
tasks.install_dns(
cls.master,
extra_args=['--dnssec-master', '--no-dnssec-validation']
)
@classmethod
def uninstall(cls, mh):
pass
def test_clean_uninstall(self):
tasks.uninstall_master(self.master)
self.after = self.master.run_command(
"find /etc/ /run/ /var/ /root/ -mount | sort"
).stdout_text.split('\n')
diff = unified_diff(self.before, self.after,
fromfile='before', tofile='after')
ALLOW_LIST = [
'/var/log',
'/var/tmp/systemd-private',
'/run/systemd',
'/var/lib/authselect/backups/pre_ipaclient',
'/var/named/data/named.run',
paths.DNSSEC_SOFTHSM_PIN_SO, # See commit eb54814741
'/etc/selinux/targeted/contexts/files/file_contexts.local.bin',
paths.SSSD_CONF_DELETED, # See commit dd72ed6212
'/root/.cache',
'/root/.dogtag',
'/root/.local',
'/run/dirsrv',
'/run/lock/dirsrv',
'/var/lib/authselect/backups',
'/var/lib/gssproxy/rcache/krb5_0.rcache2',
'/var/lib/ipa/ipa-kasp.db.backup',
'/var/lib/selinux/targeted/active/booleans.local',
'/var/lib/selinux/targeted/active/file_contexts.local',
'/var/lib/sss/pubconf/krb5.include.d/krb5_libdefaults',
'/var/lib/sss/pubconf/krb5.include.d/localauth_plugin',
'/var/named/dynamic/managed-keys.bind',
'/var/named/dynamic/managed-keys.bind.jnl',
]
leftovers = []
for line in diff:
line = line.strip()
if line.startswith('+'):
if line.endswith('log'):
continue
if line.startswith('+++ after'):
continue
found = False
for s in ALLOW_LIST:
if s in line:
found = True
break
if found:
continue
leftovers.append(line)
assert len(leftovers) == 0