mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-28 18:01:23 -06:00
881eea4efe
When IPA master is installed without DNS, using it as nameserver creates invalid configuration. Related to https://pagure.io/freeipa/issue/8703 Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
1125 lines
42 KiB
Python
1125 lines
42 KiB
Python
#
|
|
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
|
#
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import os
|
|
import time
|
|
import re
|
|
import textwrap
|
|
|
|
import pytest
|
|
|
|
from ipatests.test_integration.base import IntegrationTest
|
|
from ipatests.test_integration.test_ipahealthcheck import (
|
|
run_healthcheck, HEALTHCHECK_PKG
|
|
)
|
|
from ipatests.pytest_ipa.integration import tasks
|
|
from ipatests.pytest_ipa.integration.tasks import (
|
|
assert_error, replicas_cleanup
|
|
)
|
|
from ipatests.pytest_ipa.integration.firewall import Firewall
|
|
from ipatests.pytest_ipa.integration.env_config import get_global_config
|
|
from ipalib.constants import (
|
|
DOMAIN_LEVEL_1, IPA_CA_NICKNAME, CA_SUFFIX_NAME
|
|
)
|
|
from ipaplatform.paths import paths
|
|
from ipapython import certdb
|
|
from ipatests.test_integration.test_dns_locations import (
|
|
resolve_records_from_server, IPA_DEFAULT_MASTER_SRV_REC
|
|
)
|
|
from ipapython.dnsutil import DNSName
|
|
from ipalib.constants import IPA_CA_RECORD
|
|
|
|
config = get_global_config()
|
|
|
|
|
|
class ReplicaPromotionBase(IntegrationTest):
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
tasks.install_master(cls.master, domain_level=cls.domain_level)
|
|
|
|
def test_kra_install_master(self):
|
|
result1 = tasks.install_kra(self.master,
|
|
first_instance=True,
|
|
raiseonerr=False)
|
|
assert result1.returncode == 0, result1.stderr_text
|
|
tasks.kinit_admin(self.master)
|
|
result2 = self.master.run_command(["ipa", "vault-find"],
|
|
raiseonerr=False)
|
|
found = result2.stdout_text.find("0 vaults matched")
|
|
assert(found > 0), result2.stdout_text
|
|
|
|
|
|
def sssd_config_allows_ipaapi_access_to_ifp(host):
|
|
"""Checks that the sssd configuration allows the ipaapi user to access
|
|
ifp
|
|
|
|
:param host the machine on which to check that sssd allows ipaapi
|
|
access to ifp
|
|
"""
|
|
with tasks.remote_sssd_config(host) as sssd_conf:
|
|
ifp = sssd_conf.get_service('ifp')
|
|
uids = [
|
|
uid.strip() for uid in ifp.get_option('allowed_uids').split(',')
|
|
]
|
|
assert 'ipaapi' in uids
|
|
|
|
|
|
class TestReplicaPromotionLevel1(ReplicaPromotionBase):
|
|
"""
|
|
TestCase: http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan#
|
|
Test_case:_Make_sure_the_old_workflow_is_disabled_at_domain_level_1
|
|
"""
|
|
|
|
topology = 'star'
|
|
num_replicas = 1
|
|
domain_level = DOMAIN_LEVEL_1
|
|
|
|
@replicas_cleanup
|
|
def test_one_step_install_pwd_and_admin_pwd(self):
|
|
"""--password and --admin-password options are mutually exclusive
|
|
|
|
Test for ticket 6353
|
|
"""
|
|
# Configure firewall first
|
|
Firewall(self.replicas[0]).enable_services(["freeipa-ldap",
|
|
"freeipa-ldaps"])
|
|
expected_err = "--password and --admin-password options are " \
|
|
"mutually exclusive"
|
|
result = self.replicas[0].run_command([
|
|
'ipa-replica-install', '-w',
|
|
self.master.config.admin_password,
|
|
'-p', 'OTPpwd',
|
|
'-n', self.master.domain.name,
|
|
'-r', self.master.domain.realm,
|
|
'--server', self.master.hostname,
|
|
'-U'],
|
|
raiseonerr=False)
|
|
assert result.returncode == 1
|
|
assert expected_err in result.stderr_text
|
|
|
|
@replicas_cleanup
|
|
def test_one_command_installation(self):
|
|
"""
|
|
TestCase:
|
|
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
|
|
#Test_case:_Replica_can_be_installed_using_one_command
|
|
"""
|
|
# Configure firewall first
|
|
Firewall(self.replicas[0]).enable_services(["freeipa-ldap",
|
|
"freeipa-ldaps"])
|
|
self.replicas[0].run_command(['ipa-replica-install', '-w',
|
|
self.master.config.admin_password,
|
|
'-n', self.master.domain.name,
|
|
'-r', self.master.domain.realm,
|
|
'--server', self.master.hostname,
|
|
'-U'])
|
|
# Ensure that pkinit is properly configured, test for 7566
|
|
result = self.replicas[0].run_command(['ipa-pkinit-manage', 'status'])
|
|
assert "PKINIT is enabled" in result.stdout_text
|
|
|
|
# Verify that the sssd configuration allows the ipaapi user to
|
|
# access ifp
|
|
sssd_config_allows_ipaapi_access_to_ifp(self.replicas[0])
|
|
|
|
|
|
class TestUnprivilegedUserPermissions(IntegrationTest):
|
|
"""
|
|
TestCase:
|
|
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
|
|
#Test_case:_Unprivileged_users_are_not_allowed_to_enroll
|
|
_and_promote_clients
|
|
"""
|
|
num_replicas = 1
|
|
domain_level = DOMAIN_LEVEL_1
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
cls.username = 'testuser'
|
|
tasks.install_master(cls.master, domain_level=cls.domain_level)
|
|
password = cls.master.config.dirman_password
|
|
cls.new_password = '$ome0therPaaS'
|
|
adduser_stdin_text = "%s\n%s\n" % (cls.master.config.admin_password,
|
|
cls.master.config.admin_password)
|
|
user_kinit_stdin_text = "%s\n%s\n%s\n" % (password, cls.new_password,
|
|
cls.new_password)
|
|
tasks.kinit_admin(cls.master)
|
|
cls.master.run_command(['ipa', 'user-add', cls.username, '--password',
|
|
'--first', 'John', '--last', 'Donn'],
|
|
stdin_text=adduser_stdin_text)
|
|
# Now we need to change the password for the user
|
|
cls.master.run_command(['kinit', cls.username],
|
|
stdin_text=user_kinit_stdin_text)
|
|
# And again kinit admin
|
|
tasks.kinit_admin(cls.master)
|
|
|
|
def test_client_enrollment_by_unprivileged_user(self):
|
|
replica = self.replicas[0]
|
|
result1 = replica.run_command(['ipa-client-install',
|
|
'-p', self.username,
|
|
'-w', self.new_password,
|
|
'--domain', replica.domain.name,
|
|
'--realm', replica.domain.realm, '-U',
|
|
'--server', self.master.hostname],
|
|
raiseonerr=False)
|
|
assert_error(result1, "No permission to join this host", 1)
|
|
|
|
def test_replica_promotion_by_unprivileged_user(self):
|
|
replica = self.replicas[0]
|
|
tasks.install_client(self.master, replica)
|
|
# Configure firewall first
|
|
Firewall(replica).enable_services(["freeipa-ldap",
|
|
"freeipa-ldaps"])
|
|
result2 = replica.run_command(['ipa-replica-install',
|
|
'-P', self.username,
|
|
'-p', self.new_password,
|
|
'-n', self.master.domain.name,
|
|
'-r', self.master.domain.realm],
|
|
raiseonerr=False)
|
|
assert_error(result2,
|
|
"Insufficient privileges to promote the server", 1)
|
|
|
|
def test_replica_promotion_after_adding_to_admin_group(self):
|
|
self.master.run_command(['ipa', 'group-add-member', 'admins',
|
|
'--users=%s' % self.username])
|
|
|
|
# Configure firewall first
|
|
Firewall(self.replicas[0]).enable_services(["freeipa-ldap",
|
|
"freeipa-ldaps"])
|
|
self.replicas[0].run_command(['ipa-replica-install',
|
|
'-P', self.username,
|
|
'-p', self.new_password,
|
|
'-n', self.master.domain.name,
|
|
'-r', self.master.domain.realm,
|
|
'-U'])
|
|
|
|
def test_sssd_config_allows_ipaapi_access_to_ifp(self):
|
|
sssd_config_allows_ipaapi_access_to_ifp(self.replicas[0])
|
|
|
|
|
|
class TestProhibitReplicaUninstallation(IntegrationTest):
|
|
topology = 'line'
|
|
num_replicas = 2
|
|
domain_level = DOMAIN_LEVEL_1
|
|
|
|
def test_replica_uninstallation_prohibited(self):
|
|
"""
|
|
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
|
|
#Test_case:_Prohibit_ipa_server_uninstallation_from_disconnecting
|
|
_topology_segment
|
|
"""
|
|
result = self.replicas[0].run_command(['ipa-server-install',
|
|
'--uninstall', '-U'],
|
|
raiseonerr=False)
|
|
assert_error(result, "Removal of '%s' leads to disconnected"
|
|
" topology" % self.replicas[0].hostname, 1)
|
|
self.replicas[0].run_command(['ipa-server-install', '--uninstall',
|
|
'-U', '--ignore-topology-disconnect'])
|
|
Firewall(self.replicas[0]).disable_services(["freeipa-ldap",
|
|
"freeipa-ldaps"])
|
|
|
|
|
|
class TestWrongClientDomain(IntegrationTest):
|
|
topology = "star"
|
|
num_replicas = 1
|
|
domain_name = 'exxample.test'
|
|
domain_level = DOMAIN_LEVEL_1
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
tasks.install_master(cls.master, domain_level=cls.domain_level)
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def wrong_client_dom_setup(self, request):
|
|
def fin():
|
|
if len(config.domains) == 0:
|
|
# No YAML config was set
|
|
return
|
|
self.replicas[0].run_command(['ipa-client-install',
|
|
'--uninstall', '-U'],
|
|
raiseonerr=False)
|
|
tasks.kinit_admin(self.master)
|
|
self.master.run_command(['ipa', 'host-del',
|
|
self.replicas[0].hostname],
|
|
raiseonerr=False)
|
|
request.addfinalizer(fin)
|
|
|
|
def test_wrong_client_domain(self):
|
|
client = self.replicas[0]
|
|
client.run_command(['ipa-client-install', '-U',
|
|
'--domain', self.domain_name,
|
|
'--realm', self.master.domain.realm,
|
|
'-p', 'admin',
|
|
'-w', self.master.config.admin_password,
|
|
'--server', self.master.hostname,
|
|
'--force-join'])
|
|
# Configure firewall first
|
|
Firewall(client).enable_services(["freeipa-ldap",
|
|
"freeipa-ldaps"])
|
|
result = client.run_command(['ipa-replica-install', '-U', '-w',
|
|
self.master.config.dirman_password],
|
|
raiseonerr=False)
|
|
assert_error(result,
|
|
"Cannot promote this client to a replica. Local domain "
|
|
"'%s' does not match IPA domain "
|
|
"'%s'" % (self.domain_name, self.master.domain.name))
|
|
|
|
def test_upcase_client_domain(self):
|
|
client = self.replicas[0]
|
|
result = client.run_command(['ipa-client-install', '-U', '--domain',
|
|
self.master.domain.name.upper(), '-w',
|
|
self.master.config.admin_password,
|
|
'-p', 'admin',
|
|
'--server', self.master.hostname,
|
|
'--force-join'], raiseonerr=False)
|
|
assert(result.returncode == 0), (
|
|
'Failed to setup client with the upcase domain name')
|
|
# Configure firewall first
|
|
Firewall(self.replicas[0]).enable_services(["freeipa-ldap",
|
|
"freeipa-ldaps"])
|
|
result1 = client.run_command(['ipa-replica-install', '-U', '-w',
|
|
self.master.config.dirman_password],
|
|
raiseonerr=False)
|
|
assert(result1.returncode == 0), (
|
|
'Failed to promote the client installed with the upcase domain name')
|
|
|
|
def test_client_rollback(self):
|
|
"""Test that bogus error msgs are not in output on rollback.
|
|
|
|
FIXME: including in this suite to avoid setting up a
|
|
master just to test a client install failure. If
|
|
a pure client install suite is added this can be
|
|
moved.
|
|
|
|
Ticket https://pagure.io/freeipa/issue/7729
|
|
"""
|
|
client = self.replicas[0]
|
|
|
|
# Cleanup previous run
|
|
client.run_command(['ipa-server-install',
|
|
'--uninstall', '-U'], raiseonerr=False)
|
|
|
|
result = client.run_command(['ipa-client-install', '-U',
|
|
'--server', self.master.hostname,
|
|
'--domain', client.domain.name,
|
|
'-w', 'foo'], raiseonerr=False)
|
|
|
|
assert(result.returncode == 1)
|
|
|
|
assert("Unconfigured automount client failed" not in
|
|
result.stdout_text)
|
|
|
|
assert("WARNING: Unable to revert" not in result.stdout_text)
|
|
|
|
assert("An error occurred while removing SSSD" not in
|
|
result.stdout_text)
|
|
|
|
class TestRenewalMaster(IntegrationTest):
|
|
|
|
topology = 'star'
|
|
num_replicas = 1
|
|
|
|
def assertCARenewalMaster(self, host, expected):
|
|
""" Ensure there is only one CA renewal master set """
|
|
result = host.run_command(["ipa", "config-show"]).stdout_text
|
|
matches = list(re.finditer('IPA CA renewal master: (.*)', result))
|
|
assert len(matches), 1
|
|
assert matches[0].group(1) == expected
|
|
|
|
def test_replica_not_marked_as_renewal_master(self):
|
|
"""
|
|
https://fedorahosted.org/freeipa/ticket/5902
|
|
"""
|
|
master = self.master
|
|
replica = self.replicas[0]
|
|
result = master.run_command(["ipa", "config-show"]).stdout_text
|
|
assert("IPA CA renewal master: %s" % master.hostname in result), (
|
|
"Master hostname not found among CA renewal masters"
|
|
)
|
|
assert("IPA CA renewal master: %s" % replica.hostname not in result), (
|
|
"Replica hostname found among CA renewal masters"
|
|
)
|
|
|
|
def test_renewal_replica_with_ipa_ca_cert_manage(self):
|
|
"""Make replica as IPA CA renewal master using
|
|
ipa-cacert-manage --renew"""
|
|
master = self.master
|
|
replica = self.replicas[0]
|
|
self.assertCARenewalMaster(master, master.hostname)
|
|
replica.run_command([paths.IPA_CACERT_MANAGE, 'renew'])
|
|
self.assertCARenewalMaster(replica, replica.hostname)
|
|
# set master back to ca-renewal-master
|
|
master.run_command([paths.IPA_CACERT_MANAGE, 'renew'])
|
|
self.assertCARenewalMaster(master, master.hostname)
|
|
self.assertCARenewalMaster(replica, master.hostname)
|
|
|
|
def test_manual_renewal_master_transfer(self):
|
|
replica = self.replicas[0]
|
|
replica.run_command(['ipa', 'config-mod',
|
|
'--ca-renewal-master-server', replica.hostname])
|
|
result = self.master.run_command(["ipa", "config-show"]).stdout_text
|
|
assert("IPA CA renewal master: %s" % replica.hostname in result), (
|
|
"Replica hostname not found among CA renewal masters"
|
|
)
|
|
# additional check e.g. to see if there is only one renewal master
|
|
self.assertCARenewalMaster(replica, replica.hostname)
|
|
|
|
def test_renewal_master_with_csreplica_manage(self):
|
|
master = self.master
|
|
replica = self.replicas[0]
|
|
|
|
self.assertCARenewalMaster(master, replica.hostname)
|
|
self.assertCARenewalMaster(replica, replica.hostname)
|
|
|
|
master.run_command(['ipa-csreplica-manage', 'set-renewal-master',
|
|
'-p', master.config.dirman_password])
|
|
result = master.run_command(["ipa", "config-show"]).stdout_text
|
|
|
|
assert("IPA CA renewal master: %s" % master.hostname in result), (
|
|
"Master hostname not found among CA renewal masters"
|
|
)
|
|
|
|
# lets give replication some time
|
|
time.sleep(60)
|
|
|
|
self.assertCARenewalMaster(master, master.hostname)
|
|
self.assertCARenewalMaster(replica, master.hostname)
|
|
|
|
replica.run_command(['ipa-csreplica-manage', 'set-renewal-master',
|
|
'-p', replica.config.dirman_password])
|
|
result = replica.run_command(["ipa", "config-show"]).stdout_text
|
|
|
|
assert("IPA CA renewal master: %s" % replica.hostname in result), (
|
|
"Replica hostname not found among CA renewal masters"
|
|
)
|
|
|
|
self.assertCARenewalMaster(master, replica.hostname)
|
|
self.assertCARenewalMaster(replica, replica.hostname)
|
|
|
|
def test_automatic_renewal_master_transfer_ondelete(self):
|
|
# Test that after replica uninstallation, master overtakes the cert
|
|
# renewal master role from replica (which was previously set there)
|
|
tasks.uninstall_master(self.replicas[0])
|
|
result = self.master.run_command(['ipa', 'config-show']).stdout_text
|
|
assert("IPA CA renewal master: %s" % self.master.hostname in result), (
|
|
"Master hostname not found among CA renewal masters"
|
|
)
|
|
|
|
|
|
class TestReplicaInstallWithExistingEntry(IntegrationTest):
|
|
"""replica install might fail because of existing entry for replica like
|
|
`cn=ipa-http-delegation,cn=s4u2proxy,cn=etc,$SUFFIX` etc. The situation
|
|
may arise due to incorrect uninstall of replica.
|
|
|
|
https://pagure.io/freeipa/issue/7174"""
|
|
|
|
num_replicas = 1
|
|
|
|
def test_replica_install_with_existing_entry(self):
|
|
master = self.master
|
|
tasks.install_master(master)
|
|
replica = self.replicas[0]
|
|
base_dn = "dc=%s" % (",dc=".join(replica.domain.name.split(".")))
|
|
# adding entry for replica on master so that master will have it before
|
|
# replica installtion begins and creates a situation for pagure-7174
|
|
entry_ldif = textwrap.dedent("""
|
|
dn: cn=ipa-http-delegation,cn=s4u2proxy,cn=etc,{base_dn}
|
|
changetype: modify
|
|
add: memberPrincipal
|
|
memberPrincipal: HTTP/{hostname}@{realm}
|
|
|
|
dn: cn=ipa-ldap-delegation-targets,cn=s4u2proxy,cn=etc,{base_dn}
|
|
changetype: modify
|
|
add: memberPrincipal
|
|
memberPrincipal: ldap/{hostname}@{realm}""").format(
|
|
base_dn=base_dn, hostname=replica.hostname,
|
|
realm=replica.domain.name.upper())
|
|
tasks.ldapmodify_dm(master, entry_ldif)
|
|
|
|
tasks.install_replica(master, replica)
|
|
|
|
|
|
AUTH_ID_RE = re.compile(
|
|
'Authority ID: '
|
|
'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'
|
|
)
|
|
|
|
|
|
class TestSubCAkeyReplication(IntegrationTest):
|
|
"""
|
|
Test if subca key replication is not failing.
|
|
"""
|
|
topology = 'line'
|
|
num_replicas = 1
|
|
|
|
SUBCA_DESC = 'subca'
|
|
|
|
SUBCA_MASTER = 'test_subca_master'
|
|
SUBCA_MASTER_CN = 'cn=' + SUBCA_MASTER
|
|
|
|
SUBCA_REPLICA = 'test_subca_replica'
|
|
SUBCA_REPLICA_CN = 'cn=' + SUBCA_REPLICA
|
|
|
|
PKI_DEBUG_PATH = '/var/log/pki/pki-tomcat/ca/debug'
|
|
|
|
ERR_MESS = 'Caught exception during cert/key import'
|
|
|
|
SERVER_CERT_NICK = 'Server-Cert cert-pki-ca'
|
|
SERVER_KEY_NICK = 'NSS Certificate DB:Server-Cert cert-pki-ca'
|
|
SERVER_KEY_NICK_FIPS = (
|
|
'NSS FIPS 140-2 Certificate DB:Server-Cert cert-pki-ca'
|
|
)
|
|
EXPECTED_CERTS = {
|
|
IPA_CA_NICKNAME: 'CTu,Cu,Cu',
|
|
'ocspSigningCert cert-pki-ca': 'u,u,u',
|
|
'subsystemCert cert-pki-ca': 'u,u,u',
|
|
'auditSigningCert cert-pki-ca': 'u,u,Pu',
|
|
SERVER_CERT_NICK: 'u,u,u',
|
|
}
|
|
|
|
def add_subca(self, host, name, subject, raiseonerr=True):
|
|
result = host.run_command([
|
|
'ipa', 'ca-add', name,
|
|
'--subject', subject,
|
|
'--desc', self.SUBCA_DESC],
|
|
raiseonerr=raiseonerr
|
|
)
|
|
if raiseonerr:
|
|
assert "ipa: ERROR:" not in result.stderr_text
|
|
auth_id = "".join(re.findall(AUTH_ID_RE, result.stdout_text))
|
|
return '{} {}'.format(IPA_CA_NICKNAME, auth_id)
|
|
else:
|
|
assert "ipa: ERROR:" in result.stderr_text
|
|
assert result.returncode != 0
|
|
return result
|
|
|
|
def del_subca(self, host, name):
|
|
host.run_command([
|
|
'ipa', 'ca-disable', name
|
|
])
|
|
result = host.run_command([
|
|
'ipa', 'ca-del', name
|
|
])
|
|
assert "Deleted CA \"{}\"".format(name) in result.stdout_text
|
|
|
|
def check_subca(self, host, name, cert_nick):
|
|
result = host.run_command(['ipa', 'ca-show', name])
|
|
# ipa ca-show returns 0 even if the cert cannot be found locally.
|
|
assert "ipa: ERROR:" not in result.stderr_text
|
|
tasks.run_certutil(
|
|
host, ['-L', '-n', cert_nick], paths.PKI_TOMCAT_ALIAS_DIR
|
|
)
|
|
host.run_command([
|
|
paths.CERTUTIL, '-d', paths.PKI_TOMCAT_ALIAS_DIR,
|
|
'-f', paths.PKI_TOMCAT_ALIAS_PWDFILE_TXT,
|
|
'-K', '-n', cert_nick
|
|
])
|
|
|
|
def get_certinfo(self, host):
|
|
result = tasks.run_certutil(
|
|
host,
|
|
['-L', '-f', paths.PKI_TOMCAT_ALIAS_PWDFILE_TXT],
|
|
paths.PKI_TOMCAT_ALIAS_DIR
|
|
)
|
|
certs = {}
|
|
for line in result.stdout_text.splitlines():
|
|
mo = certdb.CERT_RE.match(line)
|
|
if mo:
|
|
certs[mo.group('nick')] = mo.group('flags')
|
|
|
|
result = tasks.run_certutil(
|
|
host,
|
|
['-K', '-f', paths.PKI_TOMCAT_ALIAS_PWDFILE_TXT],
|
|
paths.PKI_TOMCAT_ALIAS_DIR
|
|
)
|
|
keys = {}
|
|
for line in result.stdout_text.splitlines():
|
|
mo = certdb.KEY_RE.match(line)
|
|
if mo:
|
|
keys[mo.group('nick')] = mo.group('keyid')
|
|
return certs, keys
|
|
|
|
def check_certdb(self, master, replica):
|
|
expected_certs = self.EXPECTED_CERTS.copy()
|
|
# find and add sub CAs to expected certs
|
|
result = master.run_command(
|
|
['ipa', 'ca-find', '--desc', self.SUBCA_DESC]
|
|
)
|
|
for auth_id in re.findall(AUTH_ID_RE, result.stdout_text):
|
|
nick = '{} {}'.format(IPA_CA_NICKNAME, auth_id)
|
|
expected_certs[nick] = 'u,u,u'
|
|
|
|
if master.is_fips_mode:
|
|
# Mixed FIPS/non-FIPS installations are not supported
|
|
assert replica.is_fips_mode
|
|
key_nick = self.SERVER_KEY_NICK_FIPS
|
|
else:
|
|
key_nick = self.SERVER_KEY_NICK
|
|
|
|
# expected keys, server key has different name
|
|
expected_keys = set(expected_certs)
|
|
expected_keys.remove(self.SERVER_CERT_NICK)
|
|
expected_keys.add(key_nick)
|
|
|
|
# get certs and keys from Dogtag's NSSDB
|
|
master_certs, master_keys = self.get_certinfo(master)
|
|
replica_certs, replica_keys = self.get_certinfo(replica)
|
|
|
|
assert master_certs == expected_certs
|
|
assert replica_certs == expected_certs
|
|
|
|
assert set(master_keys) == expected_keys
|
|
assert set(replica_keys) == expected_keys
|
|
|
|
# server keys are different
|
|
master_server_key = master_keys.pop(key_nick)
|
|
replica_server_key = replica_keys.pop(key_nick)
|
|
assert master_server_key != replica_server_key
|
|
# but key ids of other keys are equal
|
|
assert master_keys == replica_keys
|
|
|
|
def check_pki_error(self, host):
|
|
pki_log_filename = "{0}.{1}.log".format(
|
|
self.PKI_DEBUG_PATH,
|
|
time.strftime("%Y-%m-%d")
|
|
)
|
|
pki_debug_log = host.get_file_contents(
|
|
pki_log_filename, encoding='utf-8'
|
|
)
|
|
# check for cert/key import error message
|
|
assert self.ERR_MESS not in pki_debug_log
|
|
|
|
def test_subca_master(self):
|
|
master = self.master
|
|
replica = self.replicas[0]
|
|
|
|
master_nick = self.add_subca(
|
|
master, self.SUBCA_MASTER, self.SUBCA_MASTER_CN
|
|
)
|
|
# give replication some time
|
|
time.sleep(15)
|
|
|
|
self.check_subca(master, self.SUBCA_MASTER, master_nick)
|
|
self.check_subca(replica, self.SUBCA_MASTER, master_nick)
|
|
self.check_pki_error(replica)
|
|
self.check_certdb(master, replica)
|
|
|
|
def test_subca_replica(self):
|
|
master = self.master
|
|
replica = self.replicas[0]
|
|
|
|
replica_nick = self.add_subca(
|
|
replica, self.SUBCA_REPLICA, self.SUBCA_REPLICA_CN
|
|
)
|
|
# give replication some time
|
|
time.sleep(15)
|
|
|
|
# replica.run_command(['ipa-certupdate'])
|
|
self.check_subca(replica, self.SUBCA_REPLICA, replica_nick)
|
|
self.check_subca(master, self.SUBCA_REPLICA, replica_nick)
|
|
self.check_pki_error(master)
|
|
self.check_certdb(master, replica)
|
|
|
|
def test_sign_with_subca_on_replica(self):
|
|
master = self.master
|
|
replica = self.replicas[0]
|
|
|
|
TEST_KEY_FILE = os.path.join(
|
|
paths.OPENSSL_PRIVATE_DIR, 'test_subca.key'
|
|
)
|
|
TEST_CRT_FILE = os.path.join(
|
|
paths.OPENSSL_PRIVATE_DIR, 'test_subca.crt'
|
|
)
|
|
|
|
caacl_cmd = [
|
|
'ipa', 'caacl-add-ca', 'hosts_services_caIPAserviceCert',
|
|
'--cas', self.SUBCA_MASTER
|
|
]
|
|
master.run_command(caacl_cmd)
|
|
|
|
request_cmd = [
|
|
paths.IPA_GETCERT, 'request', '-w', '-k', TEST_KEY_FILE,
|
|
'-f', TEST_CRT_FILE, '-X', self.SUBCA_MASTER
|
|
]
|
|
replica.run_command(request_cmd)
|
|
|
|
status_cmd = [paths.IPA_GETCERT, 'status', '-v', '-f', TEST_CRT_FILE]
|
|
status = replica.run_command(status_cmd)
|
|
assert 'State MONITORING, stuck: no' in status.stdout_text
|
|
|
|
ssl_cmd = ['openssl', 'x509', '-text', '-in', TEST_CRT_FILE,
|
|
'-nameopt', 'space_eq']
|
|
ssl = replica.run_command(ssl_cmd)
|
|
assert 'Issuer: CN = {}'.format(self.SUBCA_MASTER) in ssl.stdout_text
|
|
|
|
def test_del_subca_master_on_replica(self):
|
|
self.del_subca(self.replicas[0], self.SUBCA_MASTER)
|
|
|
|
def test_del_subca_replica(self):
|
|
self.del_subca(self.replicas[0], self.SUBCA_REPLICA)
|
|
|
|
def test_scale_add_subca(self):
|
|
master = self.master
|
|
replica = self.replicas[0]
|
|
|
|
subcas = {}
|
|
for i in range(0, 16):
|
|
name = "_".join((self.SUBCA_MASTER, str(i)))
|
|
cn = "_".join((self.SUBCA_MASTER_CN, str(i)))
|
|
subcas[name] = self.add_subca(master, name, cn)
|
|
self.add_subca(master, name, cn, raiseonerr=False)
|
|
|
|
# give replication some time
|
|
time.sleep(15)
|
|
|
|
for name in subcas:
|
|
self.check_subca(replica, name, subcas[name])
|
|
self.del_subca(replica, name)
|
|
|
|
|
|
class TestReplicaInstallCustodia(IntegrationTest):
|
|
"""
|
|
Pagure Reference: https://pagure.io/freeipa/issue/7518
|
|
"""
|
|
|
|
topology = 'line'
|
|
num_replicas = 2
|
|
domain_level = DOMAIN_LEVEL_1
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
tasks.install_master(cls.master, domain_level=cls.domain_level)
|
|
|
|
def test_replica_install_for_custodia(self):
|
|
master = self.master
|
|
replica1 = self.replicas[0]
|
|
replica2 = self.replicas[1]
|
|
|
|
# Install Replica1 without CA and stop ipa-custodia
|
|
tasks.install_replica(master, replica1, setup_ca=False)
|
|
replica1.run_command(['ipactl', 'status'])
|
|
replica1.run_command(['systemctl', 'stop', 'ipa-custodia'])
|
|
replica1.run_command(['ipactl', 'status'], raiseonerr=False)
|
|
|
|
# Install Replica2 with CA with source as Replica1.
|
|
tasks.install_replica(replica1, replica2, setup_ca=True,
|
|
nameservers='master')
|
|
result = replica2.run_command(['ipactl', 'status'])
|
|
assert 'ipa-custodia Service: RUNNING' in result.stdout_text
|
|
|
|
|
|
def update_etc_hosts(host, ip, old_hostname, new_hostname):
|
|
'''Adds or update /etc/hosts
|
|
|
|
If /etc/hosts contains an entry for old_hostname, replace it with
|
|
new_hostname.
|
|
If /etc/hosts did not contain the entry, create one for new_hostname with
|
|
the provided ip.
|
|
The function makes a backup in /etc/hosts.sav
|
|
|
|
:param host the machine on which /etc/hosts needs to be update_dns_records
|
|
:param ip the ip address for the new record
|
|
:param old_hostname the hostname to replace
|
|
:param new_hostname the new hostname to put in /etc/hosts
|
|
'''
|
|
# Make a backup
|
|
host.run_command(['/bin/cp',
|
|
paths.HOSTS,
|
|
'%s.sav' % paths.HOSTS])
|
|
contents = host.get_file_contents(paths.HOSTS, encoding='utf-8')
|
|
# If /etc/hosts already contains old_hostname, simply replace
|
|
pattern = r'^(.*\s){}(\s)'.format(old_hostname)
|
|
new_contents, mods = re.subn(pattern, r'\1{}\2'.format(new_hostname),
|
|
contents, flags=re.MULTILINE)
|
|
# If it didn't contain any entry for old_hostname, just add new_hostname
|
|
if mods == 0:
|
|
short = new_hostname.split(".", 1)[0]
|
|
new_contents = new_contents + "\n{}\t{} {}\n".format(ip,
|
|
new_hostname,
|
|
short)
|
|
host.put_file_contents(paths.HOSTS, new_contents)
|
|
|
|
|
|
def restore_etc_hosts(host):
|
|
'''Restores /etc/hosts.sav into /etc/hosts
|
|
'''
|
|
host.run_command(['/bin/mv',
|
|
'%s.sav' % paths.HOSTS,
|
|
paths.HOSTS],
|
|
raiseonerr=False)
|
|
|
|
|
|
class TestReplicaInForwardZone(IntegrationTest):
|
|
"""
|
|
Pagure Reference: https://pagure.io/freeipa/issue/7369
|
|
|
|
Scenario: install a replica whose name is in a forwarded zone
|
|
"""
|
|
|
|
forwardzone = 'forward.test'
|
|
num_replicas = 1
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
tasks.install_master(cls.master, setup_dns=True)
|
|
|
|
def test_replica_install_in_forward_zone(self):
|
|
master = self.master
|
|
replica = self.replicas[0]
|
|
|
|
# Create a forward zone on the master
|
|
master.run_command(['ipa', 'dnsforwardzone-add', self.forwardzone,
|
|
'--skip-overlap-check',
|
|
'--forwarder', master.config.dns_forwarder])
|
|
|
|
# Configure the client with a name in the forwardzone
|
|
r_shortname = replica.hostname.split(".", 1)[0]
|
|
r_new_hostname = '{}.{}'.format(r_shortname,
|
|
self.forwardzone)
|
|
|
|
# Update /etc/hosts on the master with an entry for the replica
|
|
# otherwise replica conncheck would fail
|
|
update_etc_hosts(master, replica.ip, replica.hostname,
|
|
r_new_hostname)
|
|
# Remove the replica previous hostname from /etc/hosts
|
|
# and add the replica new hostname
|
|
# otherwise replica install will complain because
|
|
# hostname does not match
|
|
update_etc_hosts(replica, replica.ip, replica.hostname,
|
|
r_new_hostname)
|
|
|
|
try:
|
|
# install client with a hostname in the forward zone
|
|
tasks.install_client(self.master, replica,
|
|
extra_args=['--hostname', r_new_hostname])
|
|
|
|
# Configure firewall first
|
|
Firewall(replica).enable_services(["freeipa-ldap",
|
|
"freeipa-ldaps"])
|
|
replica.run_command(['ipa-replica-install',
|
|
'--principal', replica.config.admin_name,
|
|
'--admin-password',
|
|
replica.config.admin_password,
|
|
'--setup-dns',
|
|
'--forwarder', master.config.dns_forwarder,
|
|
'-U'])
|
|
finally:
|
|
# Restore /etc/hosts on master and replica
|
|
restore_etc_hosts(master)
|
|
restore_etc_hosts(replica)
|
|
|
|
|
|
class TestHiddenReplicaPromotion(IntegrationTest):
|
|
"""Test hidden replica features
|
|
"""
|
|
topology = None
|
|
num_replicas = 2
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
for srv in (cls.master, cls.replicas[0]):
|
|
tasks.install_packages(srv, HEALTHCHECK_PKG)
|
|
# master with DNSSEC master
|
|
tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
|
|
cls.master.run_command([
|
|
"ipa-dns-install",
|
|
"--dnssec-master",
|
|
"--forwarder", cls.master.config.dns_forwarder,
|
|
"-U",
|
|
])
|
|
# hidden replica with CA and DNS
|
|
tasks.install_replica(
|
|
cls.master, cls.replicas[0],
|
|
setup_dns=True, setup_kra=False,
|
|
extra_args=('--hidden-replica',)
|
|
)
|
|
# manually install KRA to verify that hidden state is synced
|
|
tasks.install_kra(cls.replicas[0])
|
|
|
|
def _check_dnsrecords(self, hosts_expected, hosts_unexpected=()):
|
|
domain = DNSName(self.master.domain.name).make_absolute()
|
|
rset = [
|
|
(rname, 'SRV')
|
|
for rname, _port in IPA_DEFAULT_MASTER_SRV_REC
|
|
]
|
|
rset.append((DNSName(IPA_CA_RECORD), 'A'))
|
|
|
|
for rname, rtype in rset:
|
|
name_abs = rname.derelativize(domain)
|
|
query = resolve_records_from_server(
|
|
name_abs, rtype, self.master.ip
|
|
)
|
|
if rtype == 'SRV':
|
|
records = [q.target.to_text() for q in query]
|
|
else:
|
|
records = [q.address for q in query]
|
|
for host in hosts_expected:
|
|
value = host.hostname + "." if rtype == 'SRV' else host.ip
|
|
assert value in records
|
|
for host in hosts_unexpected:
|
|
value = host.hostname + "." if rtype == 'SRV' else host.ip
|
|
assert value not in records
|
|
|
|
def _check_server_role(self, host, status, kra=True, dns=True):
|
|
roles = [u'IPA master', u'CA server']
|
|
if kra:
|
|
roles.append(u'KRA server')
|
|
if dns:
|
|
roles.append(u'DNS server')
|
|
for role in roles:
|
|
result = self.replicas[0].run_command([
|
|
'ipa', 'server-role-find',
|
|
'--server', host.hostname,
|
|
'--role', role
|
|
])
|
|
expected = 'Role status: {}'.format(status)
|
|
assert expected in result.stdout_text
|
|
|
|
def _check_config(self, enabled=(), hidden=()):
|
|
enabled = {host.hostname for host in enabled}
|
|
hidden = {host.hostname for host in hidden}
|
|
services = [
|
|
'IPA masters', 'IPA CA servers', 'IPA KRA servers',
|
|
'IPA DNS servers'
|
|
]
|
|
|
|
result = self.replicas[0].run_command(['ipa', 'config-show'])
|
|
values = {}
|
|
for line in result.stdout_text.split('\n'):
|
|
if ':' not in line:
|
|
continue
|
|
k, v = line.split(':', 1)
|
|
values[k.strip()] = {item.strip() for item in v.split(',')}
|
|
|
|
for service in services:
|
|
hservice = 'Hidden {}'.format(service)
|
|
assert values.get(service, set()) == enabled
|
|
assert values.get(hservice, set()) == hidden
|
|
|
|
def test_hidden_replica_install(self):
|
|
self._check_server_role(self.master, 'enabled')
|
|
self._check_server_role(self.replicas[0], 'hidden')
|
|
self._check_dnsrecords([self.master], [self.replicas[0]])
|
|
self._check_config([self.master], [self.replicas[0]])
|
|
|
|
def test_ipahealthcheck_hidden_replica(self):
|
|
"""Ensure that ipa-healthcheck runs successfully on all members
|
|
of an IPA cluster that includes a hidden replica.
|
|
"""
|
|
# verify state
|
|
self._check_config([self.master], [self.replicas[0]])
|
|
# A DNA range is needed on the replica for ipa-healthcheck to work.
|
|
# Create a user so that the replica gets a range.
|
|
tasks.user_add(self.replicas[0], 'testuser')
|
|
tasks.user_del(self.replicas[0], 'testuser')
|
|
for srv in (self.master, self.replicas[0]):
|
|
returncode, _unused = run_healthcheck(
|
|
srv,
|
|
failures_only=True
|
|
)
|
|
assert returncode == 0
|
|
|
|
def test_hide_master_fails(self):
|
|
# verify state
|
|
self._check_config([self.master], [self.replicas[0]])
|
|
# nothing to do
|
|
result = self.master.run_command([
|
|
'ipa', 'server-state',
|
|
self.master.hostname, '--state=enabled'
|
|
], raiseonerr=False)
|
|
assert result.returncode == 1
|
|
assert "no modifications to be performed" in result.stderr_text
|
|
# hiding the last master fails
|
|
result = self.master.run_command([
|
|
'ipa', 'server-state',
|
|
self.master.hostname, '--state=hidden'
|
|
], raiseonerr=False)
|
|
assert result.returncode == 1
|
|
keys = [
|
|
"CA renewal master", "DNSSec key master", "CA server",
|
|
"KRA server", "DNS server", "IPA server"
|
|
]
|
|
for key in keys:
|
|
assert key in result.stderr_text
|
|
|
|
def test_hidden_replica_promote(self):
|
|
self.replicas[0].run_command([
|
|
'ipa', 'server-state',
|
|
self.replicas[0].hostname, '--state=enabled'
|
|
])
|
|
self._check_server_role(self.replicas[0], 'enabled')
|
|
self._check_dnsrecords([self.master, self.replicas[0]])
|
|
self._check_config([self.master, self.replicas[0]])
|
|
|
|
result = self.replicas[0].run_command([
|
|
'ipa', 'server-state',
|
|
self.replicas[0].hostname, '--state=enabled'
|
|
], raiseonerr=False)
|
|
assert result.returncode == 1
|
|
assert 'no modifications to be performed' in result.stderr_text
|
|
|
|
def test_hidden_replica_demote(self):
|
|
self.replicas[0].run_command([
|
|
'ipa', 'server-state',
|
|
self.replicas[0].hostname, '--state=hidden'
|
|
])
|
|
self._check_server_role(self.replicas[0], 'hidden')
|
|
self._check_dnsrecords([self.master], [self.replicas[0]])
|
|
|
|
def test_replica_from_hidden(self):
|
|
# install a replica from a hidden replica
|
|
self._check_server_role(self.replicas[0], 'hidden')
|
|
tasks.install_replica(
|
|
master=self.replicas[0],
|
|
replica=self.replicas[1],
|
|
setup_dns=True
|
|
)
|
|
self._check_server_role(self.replicas[0], 'hidden')
|
|
self._check_server_role(
|
|
self.replicas[1], 'enabled', kra=False, dns=False
|
|
)
|
|
self._check_dnsrecords(
|
|
[self.master, self.replicas[1]], [self.replicas[0]]
|
|
)
|
|
# hide the new replica
|
|
self.replicas[0].run_command([
|
|
'ipa', 'server-state',
|
|
self.replicas[1].hostname, '--state=hidden'
|
|
])
|
|
# and establish replication agreements from master
|
|
tasks.connect_replica(
|
|
master=self.master,
|
|
replica=self.replicas[1],
|
|
)
|
|
tasks.connect_replica(
|
|
master=self.master,
|
|
replica=self.replicas[1],
|
|
database=CA_SUFFIX_NAME,
|
|
)
|
|
# remove replication agreements again
|
|
tasks.disconnect_replica(
|
|
master=self.master,
|
|
replica=self.replicas[1],
|
|
)
|
|
tasks.disconnect_replica(
|
|
master=self.master,
|
|
replica=self.replicas[1],
|
|
database=CA_SUFFIX_NAME,
|
|
)
|
|
# and uninstall
|
|
tasks.uninstall_replica(
|
|
master=self.replicas[0],
|
|
replica=self.replicas[1],
|
|
)
|
|
|
|
def test_hidden_replica_backup_and_restore(self):
|
|
"""Exercises backup+restore and hidden replica uninstall
|
|
"""
|
|
self._check_server_role(self.replicas[0], 'hidden')
|
|
# backup
|
|
backup_path = tasks.get_backup_dir(self.replicas[0])
|
|
# uninstall
|
|
tasks.uninstall_master(self.replicas[0])
|
|
# restore
|
|
dirman_password = self.master.config.dirman_password
|
|
self.replicas[0].run_command(
|
|
['ipa-restore', backup_path],
|
|
stdin_text=dirman_password + '\nyes'
|
|
)
|
|
|
|
# give replication some time
|
|
time.sleep(5)
|
|
tasks.kinit_admin(self.replicas[0])
|
|
|
|
# FIXME: restore turns hidden replica into enabled replica
|
|
self._check_config([self.master, self.replicas[0]])
|
|
self._check_server_role(self.replicas[0], 'enabled')
|
|
|
|
def test_hidden_replica_automatic_crl(self):
|
|
"""Exercises if automatic CRL configuration works with
|
|
hidden replica.
|
|
"""
|
|
# Demoting Replica to be hidden.
|
|
self.replicas[0].run_command([
|
|
'ipa', 'server-state',
|
|
self.replicas[0].hostname, '--state=hidden'
|
|
])
|
|
self._check_server_role(self.replicas[0], 'hidden')
|
|
|
|
# check CRL status
|
|
result = self.replicas[0].run_command([
|
|
'ipa-crlgen-manage', 'status'])
|
|
assert "CRL generation: disabled" in result.stdout_text
|
|
|
|
# Enbable CRL status on hidden replica
|
|
self.replicas[0].run_command([
|
|
'ipa-crlgen-manage', 'enable'])
|
|
|
|
# check CRL status
|
|
result = self.replicas[0].run_command([
|
|
'ipa-crlgen-manage', 'status'])
|
|
assert "CRL generation: enabled" in result.stdout_text
|
|
|
|
|
|
class TestHiddenReplicaKRA(IntegrationTest):
|
|
"""Test KRA & hidden replica features.
|
|
"""
|
|
topology = 'star'
|
|
num_replicas = 2
|
|
|
|
@classmethod
|
|
def install(cls, mh):
|
|
tasks.install_master(cls.master, setup_dns=True, setup_kra=False)
|
|
# hidden replica with CA and DNS
|
|
tasks.install_replica(
|
|
cls.master, cls.replicas[0],
|
|
setup_dns=True, setup_kra=False,
|
|
extra_args=('--hidden-replica',)
|
|
)
|
|
# normal replica with CA and DNS
|
|
tasks.install_replica(
|
|
cls.replicas[0], cls.replicas[1],
|
|
setup_dns=True, setup_kra=False
|
|
)
|
|
|
|
def test_install_kra_on_hidden_replica(self):
|
|
# manually install KRA on hidden replica.
|
|
tasks.install_kra(self.replicas[0])
|
|
|
|
@pytest.mark.xfail(reason='freeipa ticket 8240', strict=True)
|
|
def test_kra_hidden_no_preconfig(self):
|
|
"""Test installing KRA on a replica when all KRAs are hidden.
|
|
https://pagure.io/freeipa/issue/8240
|
|
"""
|
|
|
|
result = tasks.install_kra(self.replicas[1], raiseonerr=False)
|
|
|
|
if result.returncode == 0:
|
|
# If KRA installation was successful, the only clean-up possible is
|
|
# uninstalling the whole replica as hiding the last visible KRA
|
|
# member is inhibited by design.
|
|
# This step is necessary so that the next test runs with all KRA
|
|
# members hidden too.
|
|
tasks.uninstall_replica(self.master, self.replicas[1])
|
|
|
|
assert "Failed to find an active KRA server!" not in result.stderr_text
|
|
assert result.returncode == 0
|
|
|
|
def test_kra_hidden_temp(self):
|
|
"""Test for workaround: temporarily un-hide the hidden replica.
|
|
https://pagure.io/freeipa/issue/8240
|
|
"""
|
|
self.replicas[0].run_command([
|
|
'ipa', 'server-state',
|
|
self.replicas[0].hostname, '--state=enabled'
|
|
])
|
|
result = tasks.install_kra(self.master, raiseonerr=False)
|
|
self.replicas[0].run_command([
|
|
'ipa', 'server-state',
|
|
self.replicas[0].hostname, '--state=hidden'
|
|
])
|
|
assert result.returncode == 0
|