2018-02-04 10:40:24 -06:00
|
|
|
#
|
|
|
|
# Copyright (C) 2018 FreeIPA Contributors see COPYING for license
|
|
|
|
#
|
|
|
|
|
|
|
|
"""
|
|
|
|
Module provides tests to verify that the upgrade script works.
|
|
|
|
"""
|
2018-11-29 08:41:33 -06:00
|
|
|
import base64
|
2020-06-09 11:11:26 -05:00
|
|
|
import configparser
|
|
|
|
import os
|
|
|
|
import io
|
2020-10-26 11:32:39 -05:00
|
|
|
import textwrap
|
2020-06-09 11:11:26 -05:00
|
|
|
|
2018-11-29 08:41:33 -06:00
|
|
|
from cryptography.hazmat.primitives import serialization
|
2020-06-10 04:16:07 -05:00
|
|
|
import pytest
|
2020-06-09 08:08:20 -05:00
|
|
|
|
|
|
|
from ipaplatform.paths import paths
|
2018-11-29 08:41:33 -06:00
|
|
|
from ipapython.dn import DN
|
2020-06-09 11:11:26 -05:00
|
|
|
from ipapython.ipautil import template_str
|
|
|
|
from ipaserver.install import bindinstance
|
|
|
|
from ipaserver.install.sysupgrade import STATEFILE_FILE
|
2018-02-04 10:40:24 -06:00
|
|
|
from ipatests.test_integration.base import IntegrationTest
|
2018-08-02 06:45:19 -05:00
|
|
|
from ipatests.pytest_ipa.integration import tasks
|
2018-02-04 10:40:24 -06:00
|
|
|
|
2020-06-09 11:11:26 -05:00
|
|
|
# old template without comments for testing
|
|
|
|
# and "dnssec-validation no"
|
|
|
|
OLD_NAMED_TEMPLATE = """
|
|
|
|
options {
|
|
|
|
listen-on-v6 {any;};
|
|
|
|
directory "$NAMED_VAR_DIR"; // the default
|
|
|
|
dump-file "${NAMED_DATA_DIR}cache_dump.db";
|
|
|
|
statistics-file "${NAMED_DATA_DIR}named_stats.txt";
|
|
|
|
memstatistics-file "${NAMED_DATA_DIR}named_mem_stats.txt";
|
|
|
|
tkey-gssapi-keytab "$NAMED_KEYTAB";
|
|
|
|
pid-file "$NAMED_PID";
|
|
|
|
dnssec-enable yes;
|
|
|
|
dnssec-validation no;
|
|
|
|
bindkeys-file "$BINDKEYS_FILE";
|
|
|
|
managed-keys-directory "$MANAGED_KEYS_DIR";
|
|
|
|
$INCLUDE_CRYPTO_POLICY
|
|
|
|
};
|
|
|
|
|
|
|
|
logging {
|
|
|
|
channel default_debug {
|
|
|
|
file "${NAMED_DATA_DIR}named.run";
|
|
|
|
severity dynamic;
|
|
|
|
print-time yes;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
include "$RFC1912_ZONES";
|
|
|
|
include "$ROOT_KEY";
|
|
|
|
|
|
|
|
/* WARNING: This part of the config file is IPA-managed.
|
|
|
|
* Modifications may break IPA setup or upgrades.
|
|
|
|
*/
|
|
|
|
dyndb "ipa" "$BIND_LDAP_SO" {
|
|
|
|
uri "ldapi://%2fvar%2frun%2fslapd-$SERVER_ID.socket";
|
2020-06-10 04:16:07 -05:00
|
|
|
base "cn=dns,$SUFFIX";
|
2020-06-09 11:11:26 -05:00
|
|
|
server_id "$FQDN";
|
|
|
|
auth_method "sasl";
|
|
|
|
sasl_mech "GSSAPI";
|
|
|
|
sasl_user "DNS/$FQDN";
|
|
|
|
};
|
|
|
|
/* End of IPA-managed part. */
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def named_test_template(host):
|
|
|
|
# create bind instance to get a substitution dict
|
|
|
|
bind = bindinstance.BindInstance()
|
2020-06-10 04:16:07 -05:00
|
|
|
bind.setup_templating(
|
2020-06-09 11:11:26 -05:00
|
|
|
fqdn=host.hostname,
|
|
|
|
realm_name=host.domain.realm,
|
|
|
|
domain_name=host.domain.name,
|
|
|
|
)
|
|
|
|
sub_dict = bind.sub_dict.copy()
|
|
|
|
sub_dict.update(BINDKEYS_FILE="/etc/named.iscdlv.key")
|
|
|
|
return template_str(OLD_NAMED_TEMPLATE, sub_dict)
|
|
|
|
|
|
|
|
|
|
|
|
def clear_sysupgrade(host, *sections):
|
|
|
|
# get state file
|
|
|
|
statefile = os.path.join(paths.STATEFILE_DIR, STATEFILE_FILE)
|
|
|
|
state = host.get_file_contents(statefile, encoding="utf-8")
|
|
|
|
# parse it
|
|
|
|
parser = configparser.ConfigParser()
|
|
|
|
parser.optionxform = str
|
|
|
|
parser.read_string(state)
|
|
|
|
# remove sections
|
|
|
|
for section in sections:
|
|
|
|
parser.remove_section(section)
|
|
|
|
# dump the modified config
|
|
|
|
out = io.StringIO()
|
|
|
|
parser.write(out)
|
|
|
|
# upload it
|
|
|
|
host.put_file_contents(statefile, out.getvalue())
|
|
|
|
|
2018-02-04 10:40:24 -06:00
|
|
|
|
|
|
|
class TestUpgrade(IntegrationTest):
|
2019-06-27 03:57:02 -05:00
|
|
|
"""
|
|
|
|
Test ipa-server-upgrade.
|
|
|
|
|
|
|
|
Note that ipa-server-upgrade on a CA-less installation is tested
|
|
|
|
in ``test_caless.TestIPACommands.test_invoke_upgrader``.
|
|
|
|
|
|
|
|
"""
|
2018-02-04 10:40:24 -06:00
|
|
|
@classmethod
|
|
|
|
def install(cls, mh):
|
2020-06-09 11:11:26 -05:00
|
|
|
tasks.install_master(cls.master)
|
|
|
|
tasks.install_dns(cls.master)
|
2018-02-04 10:40:24 -06:00
|
|
|
|
|
|
|
def test_invoke_upgrader(self):
|
|
|
|
cmd = self.master.run_command(['ipa-server-upgrade'],
|
|
|
|
raiseonerr=False)
|
2018-08-09 03:43:26 -05:00
|
|
|
assert ("DN: cn=Schema Compatibility,cn=plugins,cn=config does not \
|
|
|
|
exists or haven't been updated" not in cmd.stdout_text)
|
2018-02-04 10:40:24 -06:00
|
|
|
assert cmd.returncode == 0
|
2018-11-29 08:41:33 -06:00
|
|
|
|
|
|
|
def test_double_encoded_cacert(self):
|
|
|
|
"""Test for BZ 1644874
|
|
|
|
|
|
|
|
In old IPA version, the entry cn=CAcert,cn=ipa,cn=etc,$basedn
|
|
|
|
could contain a double-encoded cert, which leads to ipa-server-upgrade
|
|
|
|
failure.
|
|
|
|
Force a double-encoded value then call upgrade to check the fix.
|
|
|
|
"""
|
|
|
|
# Read the current entry from LDAP
|
|
|
|
ldap = self.master.ldap_connect()
|
2019-12-28 03:26:22 -06:00
|
|
|
basedn = self.master.domain.basedn
|
2018-11-29 08:41:33 -06:00
|
|
|
dn = DN(('cn', 'CAcert'), ('cn', 'ipa'), ('cn', 'etc'), basedn)
|
2022-02-21 06:36:10 -06:00
|
|
|
entry = ldap.get_entry(dn)
|
2018-11-29 08:41:33 -06:00
|
|
|
# Extract the certificate as DER then double-encode
|
|
|
|
cacert = entry['cacertificate;binary'][0]
|
|
|
|
cacert_der = cacert.public_bytes(serialization.Encoding.DER)
|
|
|
|
cacert_b64 = base64.b64encode(cacert_der)
|
|
|
|
# overwrite the value with double-encoded cert
|
|
|
|
entry.single_value['cACertificate;binary'] = cacert_b64
|
2022-02-21 06:36:10 -06:00
|
|
|
ldap.update_entry(entry)
|
2018-11-29 08:41:33 -06:00
|
|
|
|
|
|
|
# try the upgrade
|
|
|
|
self.master.run_command(['ipa-server-upgrade'])
|
|
|
|
|
2018-12-04 09:44:54 -06:00
|
|
|
# reconnect to the master (upgrade stops 389-ds)
|
|
|
|
ldap = self.master.ldap_connect()
|
2018-11-29 08:41:33 -06:00
|
|
|
# read the value after upgrade, should be fixed
|
2022-02-21 06:36:10 -06:00
|
|
|
entry = ldap.get_entry(dn)
|
2018-11-29 08:41:33 -06:00
|
|
|
try:
|
|
|
|
_cacert = entry['cacertificate;binary']
|
|
|
|
except ValueError:
|
|
|
|
raise AssertionError('%s contains a double-encoded cert'
|
|
|
|
% entry.dn)
|
2020-06-09 08:08:20 -05:00
|
|
|
|
2020-06-09 11:11:26 -05:00
|
|
|
def get_named_confs(self):
|
|
|
|
named_conf = self.master.get_file_contents(
|
|
|
|
paths.NAMED_CONF, encoding="utf-8"
|
|
|
|
)
|
|
|
|
print(named_conf)
|
|
|
|
custom_conf = self.master.get_file_contents(
|
2020-06-10 04:16:07 -05:00
|
|
|
paths.NAMED_CUSTOM_CONF, encoding="utf-8"
|
2020-06-09 11:11:26 -05:00
|
|
|
)
|
|
|
|
print(custom_conf)
|
|
|
|
opt_conf = self.master.get_file_contents(
|
2020-06-10 04:16:07 -05:00
|
|
|
paths.NAMED_CUSTOM_OPTIONS_CONF, encoding="utf-8"
|
2020-06-09 11:11:26 -05:00
|
|
|
)
|
|
|
|
print(opt_conf)
|
2021-05-13 10:16:04 -05:00
|
|
|
|
|
|
|
log_conf = self.master.get_file_contents(
|
|
|
|
paths.NAMED_LOGGING_OPTIONS_CONF, encoding="utf-8"
|
|
|
|
)
|
|
|
|
print(log_conf)
|
|
|
|
return named_conf, custom_conf, opt_conf, log_conf
|
2020-06-09 11:11:26 -05:00
|
|
|
|
2020-06-10 04:16:07 -05:00
|
|
|
@pytest.mark.skip_if_platform(
|
|
|
|
"debian", reason="Debian does not use crypto policy"
|
|
|
|
)
|
|
|
|
def test_named_conf_crypto_policy(self):
|
|
|
|
named_conf = self.master.get_file_contents(
|
|
|
|
paths.NAMED_CONF, encoding="utf-8"
|
|
|
|
)
|
|
|
|
assert paths.NAMED_CRYPTO_POLICY_FILE in named_conf
|
|
|
|
|
2020-06-09 11:11:26 -05:00
|
|
|
def test_current_named_conf(self):
|
2021-05-13 10:16:04 -05:00
|
|
|
named_conf, custom_conf, opt_conf, log_conf = self.get_named_confs()
|
|
|
|
# verify that all includes are present exactly one time
|
2020-06-10 04:16:07 -05:00
|
|
|
inc_opt_conf = f'include "{paths.NAMED_CUSTOM_OPTIONS_CONF}";'
|
2020-06-09 11:11:26 -05:00
|
|
|
assert named_conf.count(inc_opt_conf) == 1
|
2020-06-10 04:16:07 -05:00
|
|
|
inc_custom_conf = f'include "{paths.NAMED_CUSTOM_CONF}";'
|
2020-06-09 11:11:26 -05:00
|
|
|
assert named_conf.count(inc_custom_conf) == 1
|
2021-05-13 10:16:04 -05:00
|
|
|
inc_log_conf = f'include "{paths.NAMED_LOGGING_OPTIONS_CONF}";'
|
|
|
|
assert named_conf.count(inc_log_conf) == 1
|
2020-06-09 11:11:26 -05:00
|
|
|
|
|
|
|
assert "dnssec-validation yes;" in opt_conf
|
|
|
|
assert "dnssec-validation" not in named_conf
|
|
|
|
|
|
|
|
assert custom_conf
|
2021-05-13 10:16:04 -05:00
|
|
|
assert log_conf
|
2020-06-09 11:11:26 -05:00
|
|
|
|
|
|
|
def test_update_named_conf_simple(self):
|
2020-06-09 08:08:20 -05:00
|
|
|
# remove files to force a migration
|
|
|
|
self.master.run_command(
|
|
|
|
[
|
|
|
|
"rm",
|
|
|
|
"-f",
|
2020-06-10 04:16:07 -05:00
|
|
|
paths.NAMED_CUSTOM_CONF,
|
|
|
|
paths.NAMED_CUSTOM_OPTIONS_CONF,
|
2021-05-13 10:16:04 -05:00
|
|
|
paths.NAMED_LOGGING_OPTIONS_CONF,
|
2020-06-09 08:08:20 -05:00
|
|
|
]
|
|
|
|
)
|
|
|
|
self.master.run_command(['ipa-server-upgrade'])
|
2021-05-13 10:16:04 -05:00
|
|
|
named_conf, custom_conf, opt_conf, log_conf = self.get_named_confs()
|
2020-06-09 11:11:26 -05:00
|
|
|
|
|
|
|
# not empty
|
|
|
|
assert custom_conf.strip()
|
2021-05-13 10:16:04 -05:00
|
|
|
assert log_conf.strip()
|
2020-06-09 11:11:26 -05:00
|
|
|
# has dnssec-validation enabled in option config
|
|
|
|
assert "dnssec-validation yes;" in opt_conf
|
|
|
|
assert "dnssec-validation" not in named_conf
|
|
|
|
|
|
|
|
# verify that both includes are present exactly one time
|
2020-06-10 04:16:07 -05:00
|
|
|
inc_opt_conf = f'include "{paths.NAMED_CUSTOM_OPTIONS_CONF}";'
|
2020-06-09 11:11:26 -05:00
|
|
|
assert named_conf.count(inc_opt_conf) == 1
|
2020-06-10 04:16:07 -05:00
|
|
|
inc_custom_conf = f'include "{paths.NAMED_CUSTOM_CONF}";'
|
2020-06-09 11:11:26 -05:00
|
|
|
assert named_conf.count(inc_custom_conf) == 1
|
2021-05-13 10:16:04 -05:00
|
|
|
inc_log_conf = f'include "{paths.NAMED_LOGGING_OPTIONS_CONF}";'
|
|
|
|
assert named_conf.count(inc_log_conf) == 1
|
2020-06-09 11:11:26 -05:00
|
|
|
|
|
|
|
def test_update_named_conf_old(self):
|
|
|
|
# remove files to force a migration
|
|
|
|
self.master.run_command(
|
|
|
|
[
|
|
|
|
"rm",
|
|
|
|
"-f",
|
2020-06-10 04:16:07 -05:00
|
|
|
paths.NAMED_CUSTOM_CONF,
|
|
|
|
paths.NAMED_CUSTOM_OPTIONS_CONF,
|
2021-05-13 10:16:04 -05:00
|
|
|
paths.NAMED_LOGGING_OPTIONS_CONF,
|
2020-06-09 11:11:26 -05:00
|
|
|
]
|
2020-06-09 08:08:20 -05:00
|
|
|
)
|
2020-06-09 11:11:26 -05:00
|
|
|
# dump an old named conf to verify migration
|
|
|
|
old_contents = named_test_template(self.master)
|
|
|
|
self.master.put_file_contents(paths.NAMED_CONF, old_contents)
|
|
|
|
clear_sysupgrade(self.master, "dns", "named.conf")
|
|
|
|
# check
|
|
|
|
self.master.run_command(["named-checkconf", paths.NAMED_CONF])
|
|
|
|
|
|
|
|
# upgrade
|
|
|
|
self.master.run_command(['ipa-server-upgrade'])
|
|
|
|
|
2021-05-13 10:16:04 -05:00
|
|
|
named_conf, custom_conf, opt_conf, log_conf = self.get_named_confs()
|
2020-06-09 11:11:26 -05:00
|
|
|
|
|
|
|
# not empty
|
|
|
|
assert custom_conf.strip()
|
2021-05-13 10:16:04 -05:00
|
|
|
assert log_conf.strip()
|
2020-06-09 11:11:26 -05:00
|
|
|
# dnssec-validation is migrated as "disabled" from named.conf
|
|
|
|
assert "dnssec-validation no;" in opt_conf
|
|
|
|
assert "dnssec-validation" not in named_conf
|
|
|
|
|
|
|
|
# verify that both includes are present exactly one time
|
2020-06-10 04:16:07 -05:00
|
|
|
inc_opt_conf = f'include "{paths.NAMED_CUSTOM_OPTIONS_CONF}";'
|
2020-06-09 11:11:26 -05:00
|
|
|
assert named_conf.count(inc_opt_conf) == 1
|
2020-06-10 04:16:07 -05:00
|
|
|
inc_custom_conf = f'include "{paths.NAMED_CUSTOM_CONF}";'
|
2020-06-09 11:11:26 -05:00
|
|
|
assert named_conf.count(inc_custom_conf) == 1
|
2021-05-13 10:16:04 -05:00
|
|
|
inc_log_conf = f'include "{paths.NAMED_LOGGING_OPTIONS_CONF}";'
|
|
|
|
assert named_conf.count(inc_log_conf) == 1
|
2020-06-10 11:16:54 -05:00
|
|
|
|
|
|
|
def test_admin_root_alias_upgrade_CVE_2020_10747(self):
|
|
|
|
# Test upgrade for CVE-2020-10747 fix
|
|
|
|
# https://bugzilla.redhat.com/show_bug.cgi?id=1810160
|
|
|
|
rootprinc = "root@{}".format(self.master.domain.realm)
|
|
|
|
self.master.run_command(
|
|
|
|
["ipa", "user-remove-principal", "admin", rootprinc]
|
|
|
|
)
|
|
|
|
result = self.master.run_command(["ipa", "user-show", "admin"])
|
|
|
|
assert rootprinc not in result.stdout_text
|
|
|
|
|
|
|
|
self.master.run_command(['ipa-server-upgrade'])
|
|
|
|
result = self.master.run_command(["ipa", "user-show", "admin"])
|
|
|
|
assert rootprinc in result.stdout_text
|
2020-10-26 11:32:39 -05:00
|
|
|
|
|
|
|
def test_pwpolicy_upgrade(self):
|
|
|
|
"""Test that ipapwdpolicy objectclass is added to all policies"""
|
|
|
|
entry_ldif = textwrap.dedent("""
|
|
|
|
dn: cn=global_policy,cn={realm},cn=kerberos,{base_dn}
|
|
|
|
changetype: modify
|
|
|
|
delete: objectclass
|
|
|
|
objectclass: ipapwdpolicy
|
|
|
|
""").format(
|
|
|
|
base_dn=str(self.master.domain.basedn),
|
|
|
|
realm=self.master.domain.realm)
|
|
|
|
tasks.ldapmodify_dm(self.master, entry_ldif)
|
|
|
|
|
|
|
|
tasks.kinit_admin(self.master)
|
|
|
|
self.master.run_command(['ipa-server-upgrade'])
|
|
|
|
result = self.master.run_command(["ipa", "pwpolicy-find"])
|
|
|
|
# if it is still missing the oc it won't be displayed
|
|
|
|
assert 'global_policy' in result.stdout_text
|
2020-11-25 03:00:39 -06:00
|
|
|
|
|
|
|
def test_kra_detection(self):
|
|
|
|
"""Test that ipa-server-upgrade correctly detects KRA presence
|
|
|
|
|
|
|
|
Test for https://pagure.io/freeipa/issue/8596
|
|
|
|
When the directory /var/lib/pki/pki-tomcat/kra/ exists, the upgrade
|
|
|
|
wrongly assumes that KRA component is installed and crashes.
|
2021-01-14 02:44:30 -06:00
|
|
|
The test creates an empty dir and calls kra.is_installed()
|
2020-11-25 03:00:39 -06:00
|
|
|
to make sure that KRA detection is not based on the directory
|
|
|
|
presence.
|
2021-01-14 02:44:30 -06:00
|
|
|
Note: because of issue https://github.com/dogtagpki/pki/issues/3397
|
|
|
|
ipa-server-upgrade fails even with the kra detection fix. That's
|
|
|
|
why the test does not exercise the whole ipa-server-upgrade command
|
|
|
|
but only the KRA detection part.
|
2020-11-25 03:00:39 -06:00
|
|
|
"""
|
|
|
|
kra_path = os.path.join(paths.VAR_LIB_PKI_TOMCAT_DIR, "kra")
|
|
|
|
try:
|
|
|
|
self.master.run_command(["mkdir", "-p", kra_path])
|
2021-01-14 02:44:30 -06:00
|
|
|
script = (
|
|
|
|
"from ipalib import api; "
|
|
|
|
"from ipaserver.install import krainstance; "
|
|
|
|
"api.bootstrap(); "
|
|
|
|
"api.finalize(); "
|
|
|
|
"kra = krainstance.KRAInstance(api.env.realm); "
|
|
|
|
"print(kra.is_installed())"
|
|
|
|
)
|
|
|
|
result = self.master.run_command(['python3', '-c', script])
|
|
|
|
assert "False" in result.stdout_text
|
2020-11-25 03:00:39 -06:00
|
|
|
finally:
|
|
|
|
self.master.run_command(["rmdir", kra_path])
|