On 10/22/2013 12:27 PM, Tomas Babej wrote:
On 10/22/2013 10:37 AM, Petr Viktorin wrote:
Replying to one part only:
On 10/21/2013 04:50 PM, Tomas Babej wrote:
On 10/16/2013 03:44 PM, Petr Viktorin wrote:
I still think it would be simpler if IPA and AD domains shared the
numbering namespace (users would need to define $AD_env2; if they had
$MASTER_env1 and $AD_env1 they would be in the same Domain).
But if we use _env1 for both the AD and the IPA domain, they need to
be separated in Domain.from_env. With patch 0101 both MASTER_env1 and
AD_env1 will be in both domains[0] and ad_domains[0].
I would rather not join IPA and AD domains as they even cannot be in
the
same domain, as the service records would clash. So these will
always be separate / sub / super domain relationship.
You're right that they should never share the same domain. But you
should never say never, especially in testing -- what if we'll want
to, in the future, test that the records *do* clash, or that IPA
refuses to install in an AD domain?
You could still set AD_env1 and MASTER_env1 to have the same domain.
Another problem is that they are now separate namespaces. In all code
that deals with domains you have to deal separately with the list of
AD domains and separately with IPA domains. This makes every piece of
code that doesn't care much about what type of domain it's dealing
with (configuration, listing, possible automation scripts for turning
on the VMs, etc.) more complicated.
Also, in this scheme, adding a new type of domain would be quite
hard, especially after more code is written with this split in mind.
Do keep the domain type, though. tl;dr I'd really prefer "domain 1
(IPA); domain 2 (AD)" rather than "IPA domain 1; AD domain 1".
This will, however, require filtering the domains depending on the
fact whether they contain AD or not. If a testcase wants to access an
AD domain, it will still need to loop over the list of domains to see
which ones are of AD type.
Any code that does not care what domain type it's dealing with, can
easily access all the domains by chaining the respective iterables. We
could have a wrapper in the Config class for that, along the lines of
get_all_domains().
So what I see here is that we're trading one complexity over another.
I think we can agree on your approach since it hides the complexity
from the user, especially in the ipa-test-config, which I admit is
getting rather ugly, as we need to introduce new option there and that
causes splitting.
If needed we can have a special check that would reject IPA masters
in AD domains and vice versa, if that really turns out to be necessary.
With this check we should be fine.
As we already pass ad_domain flag to Domain.from_env, I did incorporate
code that joins the machines to the domain depending on the their role.
Is that a viable solution for you?
Sorry. I think this design is less sustainable than having a shared
namespace for the domains.
I'll send revised patchset soon.
Updated patchset attached.
--
Tomas Babej
Associate Software Engeneer | Red Hat | Identity Management
RHCE | Brno Site | IRC: tbabej | freeipa.org
From a23c379a865d5005539b7eb0ada76e36768b0f53 Mon Sep 17 00:00:00 2001
From: Tomas Babej <[email protected]>
Date: Mon, 14 Oct 2013 16:37:55 +0200
Subject: [PATCH 106/107] ipatests: Add AD integration test case
Part of: https://fedorahosted.org/freeipa/ticket/3834
---
ipatests/test_integration/test_trust.py | 182 ++++++++++++++++++++++++++++++++
1 file changed, 182 insertions(+)
create mode 100644 ipatests/test_integration/test_trust.py
diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
new file mode 100644
index 0000000000000000000000000000000000000000..343c9785b0bd74b891638443e9699268e1366254
--- /dev/null
+++ b/ipatests/test_integration/test_trust.py
@@ -0,0 +1,182 @@
+# Authors:
+# Tomas Babej <[email protected]>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+
+from ipatests.test_integration.base import IntegrationTest
+from ipatests.test_integration import tasks
+from ipatests.test_integration import util
+
+
+class ADTrustBase(IntegrationTest):
+ """Provides common checks for the AD trust integration testing."""
+
+ topology = 'line'
+ num_ad_domains = 1
+
+ @classmethod
+ def setup_class(cls):
+ super(ADTrustBase, cls).setup_class()
+ cls.ad = cls.ad_domains[0].ads[0]
+
+ def test_install_adtrust(self):
+ """Test adtrust support installation"""
+
+ tasks.install_adtrust(self.master)
+
+ def test_check_sid_generation(self):
+ """Test SID generation"""
+
+ command = ['ipa', 'user-show', 'admin', '--all', '--raw']
+
+ # TODO: remove duplicate definition and import from common module
+ _sid_identifier_authority = '(0x[0-9a-f]{1,12}|[0-9]{1,10})'
+ sid_regex = 'S-1-5-21-%(idauth)s-%(idauth)s-%(idauth)s'\
+ % dict(idauth=_sid_identifier_authority)
+ stdout_re = re.escape(' ipaNTSecurityIdentifier: ') + sid_regex
+
+ util.run_repeatedly(self.master, command,
+ test=lambda x: re.search(stdout_re, x))
+
+ def test_configure_dns_and_time(self):
+ tasks.configure_dns_for_trust(self.master, self.ad)
+ tasks.sync_time(self.master, self.ad)
+
+
+class TestBasicADTrust(ADTrustBase):
+ """Basic Integration test for Active Directory"""
+
+ def test_estabilish_trust(self):
+ """Tests estabilishing trust with Active Directory"""
+
+ tasks.establish_trust_with_ad(self.master, self.ad,
+ extra_args=['--range-type', 'ipa-ad-trust'])
+
+ def test_range_properties_in_nonposix_trust(self):
+ """Check the properties of the created range"""
+
+ range_name = self.ad.domain.name.upper() + '_id_range'
+ result = self.master.run_command(['ipa', 'idrange-show', range_name,
+ '--all', '--raw'])
+ assert " ipaRangeType: ipa-ad-trust" in result.stdout_text
+ assert " ipaIDRangeSize: 200000" in result.stdout_text
+
+ def test_user_gid_uid_resolution_in_nonposix_trust(self):
+ """Check that user has SID-generated UID"""
+
+ testuser = 'testuser@%s' % self.ad.domain.realm
+ result = self.master.run_command(['getent', 'passwd', testuser])
+
+ # This regex checks that Test User does not have UID 10042 nor belongs
+ # to the group with GID 10047
+ testuser_regex = "^testuser@%s:\*:(?!10042)(\d+):(?!10047)(\d+):"\
+ "Test User:/home/testuser:/bin/sh$"\
+ % re.escape(self.ad.domain.name)
+
+ assert re.search(testuser_regex, result.stdout_text)
+
+ def test_remove_nonposix_trust(self):
+ tasks.remove_trust_with_ad(self.master, self.ad)
+ tasks.clear_sssd_cache(self.master)
+
+
+class TestPosixADTrust(ADTrustBase):
+ """Integration test for Active Directory with POSIX support"""
+
+ def test_estabilish_trust_with_posix_attributes(self):
+ # Not specifying the --range-type directly, it should be detected
+ tasks.establish_trust_with_ad(self.master, self.ad)
+
+ def test_range_properties_in_posix_trust(self):
+ # Check the properties of the created range
+
+ range_name = self.ad.domain.name.upper() + '_id_range'
+
+ result = self.master.run_command(['ipa', 'idrange-show', range_name,
+ '--all', '--raw'])
+
+ # Check the range type and size
+ assert " ipaRangeType: ipa-ad-trust-posix" in result.stdout_text
+ assert " ipaIDRangeSize: 200000" in result.stdout_text
+
+ def test_user_uid_gid_resolution_in_posix_trust(self):
+ # Check that user has AD-defined UID
+
+ testuser = 'testuser@%s' % self.ad.domain.realm
+ result = self.master.run_command(['getent', 'passwd', testuser])
+
+ testuser_stdout = "testuser@%s:*:10042:10047:"\
+ "Test User:/home/testuser:/bin/sh"\
+ % self.ad.domain.name
+
+ assert testuser_stdout in result.stdout_text
+
+ def test_user_without_posix_attributes_not_visible(self):
+ # Check that user has AD-defined UID
+
+ nonposixuser = 'nonposixuser@%s' % self.ad.domain.realm
+ result = self.master.run_command(['getent', 'passwd', nonposixuser],
+ raiseonerr=False)
+
+ # Getent exits with 2 for non-existent user
+ assert result.returncode == 2
+
+ def test_remove_trust_with_posix_attributes(self):
+ tasks.remove_trust_with_ad(self.master, self.ad)
+ tasks.clear_sssd_cache(self.master)
+
+
+class TestEnforcedPosixADTrust(TestPosixADTrust):
+ """
+ This test is intented to copycat PosixADTrust, since enforcing the POSIX
+ trust type should not make a difference.
+ """
+
+ def test_estabilish_trust_with_posix_attributes(self):
+ tasks.establish_trust_with_ad(self.master, self.ad,
+ extra_args=['--range-type', 'ipa-ad-trust-posix'])
+
+
+class TestInvalidRangeTypes(ADTrustBase):
+ """
+ Tests invalid values being put into trust-add command.
+ """
+
+ def test_invalid_range_types(self):
+
+ invalid_range_types = ['ipa-local',
+ 'ipa-ad-winsync',
+ 'ipa-ipa-trust',
+ 'random-invalid',
+ 're@ll%ybad12!']
+
+ for range_type in invalid_range_types:
+ tasks.kinit_admin(self.master)
+
+ result = self.master.run_command(
+ ['ipa', 'trust-add',
+ '--type', 'ad', self.ad.domain.name,
+ '--admin', 'Administrator',
+ '--range-type', range_type,
+ '--password'],
+ raiseonerr=False,
+ stdin_text=self.master.config.ad_admin_password)
+
+ # The trust-add command is supposed to fail
+ assert result.returncode == 1
--
1.8.3.1
From 2ba01fcbdc752d3f636ad88d2ed14c0273bf841b Mon Sep 17 00:00:00 2001
From: Tomas Babej <[email protected]>
Date: Wed, 4 Sep 2013 16:29:06 +0200
Subject: [PATCH 105/107] ipatests: Add AD-integration related tasks
Part of: https://fedorahosted.org/freeipa/ticket/3834
---
ipatests/ipa-test-task | 117 ++++++++++++++++++++++--
ipatests/man/ipa-test-task.1 | 32 +++++++
ipatests/test_integration/tasks.py | 180 +++++++++++++++++++++++++++++++++++++
3 files changed, 324 insertions(+), 5 deletions(-)
diff --git a/ipatests/ipa-test-task b/ipatests/ipa-test-task
index 1e204648e099c65daf43cf0996cc1c0ddd6df51a..1daf2cf818ff13a7653cb13abe0f0780ec8e16f1 100755
--- a/ipatests/ipa-test-task
+++ b/ipatests/ipa-test-task
@@ -27,6 +27,7 @@ import argparse
from ipapython.ipa_log_manager import log_mgr, standard_logging_setup
from ipatests.test_integration import config
from ipatests.test_integration import tasks
+from ipatests.test_integration.host import Host
from ipatests.beakerlib_plugin import BeakerLibProcess
@@ -39,10 +40,10 @@ class TaskRunner(object):
def get_parser(self):
parser = argparse.ArgumentParser(
- description="Perform an operation for integration testing."
- "All operations are performed on configured hosts, see"
- "http://www.freeipa.org/page/V3/Integration_testing"
- "see for configuration details")
+ description="Perform an operation for integration testing. "
+ "All operations are performed on configured hosts, see "
+ "http://www.freeipa.org/page/V3/Integration_testing "
+ "for configuration details")
parser.add_argument('--with-beakerlib', action='store_true',
dest='with_beakerlib',
@@ -153,6 +154,65 @@ class TaskRunner(object):
(Default: all hosts from config)""")
subparser.set_defaults(func=self.cleanup)
+ subparser = subparsers.add_parser(
+ 'install-adtrust',
+ help='Runs ipa-adtrust-install on the host')
+ subparser.add_argument('host', type=str,
+ help='Host to run ipa-adtrust-install on')
+ subparser.set_defaults(func=self.install_adtrust)
+
+ subparser = subparsers.add_parser(
+ 'configure-dns-for-trust',
+ help='Sets DNS ib given host for trust with the given AD')
+ subparser.add_argument('host', type=str,
+ help='Host to change DNS configuration on')
+ subparser.add_argument('ad', type=str,
+ help='AD that trust will be established with')
+ subparser.set_defaults(func=self.configure_dns_for_trust)
+
+ subparser = subparsers.add_parser(
+ 'establish-trust-with-ad',
+ help='Establishes trust between IPA host and AD')
+ subparser.add_argument('host', type=str,
+ help='IPA Host to establish AD trust on')
+ subparser.add_argument('ad', type=str,
+ help='AD to establish trust with')
+ subparser.set_defaults(func=self.establish_trust_with_ad)
+
+ subparser = subparsers.add_parser(
+ 'remove-trust-with-ad',
+ help='Removes trust between IPA host and AD')
+ subparser.add_argument('host', type=str,
+ help='IPA Host to remove AD trust on')
+ subparser.add_argument('ad', type=str,
+ help='AD to remove trust with')
+ subparser.set_defaults(func=self.remove_trust_with_ad)
+
+ subparser = subparsers.add_parser(
+ 'configure-auth-to-local-rule',
+ help='Configures auth_to_local rule on IPA host with respect to AD')
+ subparser.add_argument('host', type=str,
+ help='IPA Host to configure auth_to_local rule on')
+ subparser.add_argument('ad', type=str,
+ help='AD to configure the rule with')
+ subparser.set_defaults(func=self.configure_auth_to_local_rule)
+
+ subparser = subparsers.add_parser(
+ 'clear-sssd-cache',
+ help='Clears SSSD cache on the IPA host.')
+ subparser.add_argument('host', type=str,
+ help='IPA Host to clear SSSD cache on')
+ subparser.set_defaults(func=self.clear_sssd_cache)
+
+ subparser = subparsers.add_parser(
+ 'sync-time',
+ help='Synchronize time on host with respect to server')
+ subparser.add_argument('host', type=str,
+ help='IPA Host to set the time on')
+ subparser.add_argument('server', type=str,
+ help='Server that serves as a time source')
+ subparser.set_defaults(func=self.sync_time)
+
return parser
def main(self, argv):
@@ -180,6 +240,11 @@ class TaskRunner(object):
args.domain = self.config.domains[0]
+ if self.config.ad_domains:
+ args.ad_domain = self.config.ad_domains[0]
+ else:
+ args.ad_domain = None
+
import logging; logging.basicConfig()
try:
@@ -214,12 +279,17 @@ class TaskRunner(object):
return [self.prepare_host(h) for h in default]
def prepare_host(self, host):
- if host not in self._prepared_hosts:
+ # Prepare only UNIX hosts
+ if host not in self._prepared_hosts and isinstance(host, Host):
host.add_log_collector(self.collect_log)
tasks.prepare_host(host)
self._prepared_hosts.add(host)
return host
+ def require_ad_domain(self, args):
+ if not args.ad_domain:
+ SystemExit("At least one AD domain is required for this task")
+
def install_master(self, args):
master = self.get_host(args.host, default=args.domain.master)
log.info('Installing master %s', master.hostname)
@@ -289,6 +359,43 @@ class TaskRunner(object):
tasks.install_topo(args.topo, master, replicas, clients,
skip_master=args.skip_master)
+ def install_adtrust(self, args):
+ master = self.get_host(args.host, default=args.domain.master)
+ log.info('Configuring AD trust support on %s', master.hostname)
+ tasks.install_adtrust(master)
+
+ def configure_dns_for_trust(self, args):
+ self.require_ad_domain(args)
+ host = self.get_host(args.host, default=args.domain.master)
+ ad = self.get_host(args.ad, default=args.ad_domain.ads[0])
+ tasks.configure_dns_for_trust(host, ad)
+
+ def establish_trust_with_ad(self, args):
+ self.require_ad_domain(args)
+ host = self.get_host(args.host, default=args.domain.master)
+ ad = self.get_host(args.ad, default=args.ad_domain.ads[0])
+ tasks.establish_trust_with_ad(host, ad)
+
+ def remove_trust_with_ad(self, args):
+ self.require_ad_domain(args)
+ host = self.get_host(args.host, default=args.domain.master)
+ ad = self.get_host(args.ad, default=args.ad_domain.ads[0])
+ tasks.remove_trust_with_ad(host, ad)
+
+ def configure_auth_to_local_rule(self, args):
+ self.require_ad_domain(args)
+ host = self.get_host(args.host, default=args.domain.master)
+ ad = self.get_host(args.ad, default=args.ad_domain.ads[0])
+ tasks.configure_auth_to_local_rule(host, ad)
+
+ def clear_sssd_cache(self, args):
+ host = self.get_host(args.host, default=args.domain.master)
+ tasks.clear_sssd_cache(host)
+
+ def sync_time(self, args):
+ host = self.get_host(args.host, default=args.domain.master)
+ server = self.get_host(args.server)
+ tasks.sync_time(host, server)
if __name__ == '__main__':
exit(TaskRunner().main(sys.argv[1:]))
diff --git a/ipatests/man/ipa-test-task.1 b/ipatests/man/ipa-test-task.1
index b625b285067c8fccbda540cbadebacc75925d553..e73584bd3663fec72dafb68bcffbb166578d547f 100644
--- a/ipatests/man/ipa-test-task.1
+++ b/ipatests/man/ipa-test-task.1
@@ -116,6 +116,38 @@ Servers used for client installation are selected in a round-robin fashion.
\fBipa\-test\-task list-topos\fR
List the topologies available for the install-topo subcommand.
+.TP
+\fBipa\-test\-task install\-adtrust HOST\fR
+Run ipa-adtrust-install on the IPA and generate SIDs for the entries in IPA.
+
+.TP
+\fBipa\-test\-task configure\-dns\-for\-trust HOST AD\fR
+Based on the relationship of the domains configures the IPA DNS for trust.
+AD DNS needs to be setup manually.
+
+.TP
+\fBipa\-test\-task estabilish\-trust\-with\-ad HOST AD\fR
+Estabilishes trust with Active Directory. Trust type is detected depending on
+the presence of SfU (Services for Unix) support on the AD.
+
+.TP
+\fBipa\-test\-task remove\-trust\-with\-ad HOST AD\fR
+Removes trust with Active Directory. Also removes the associated ID range.
+
+.TP
+\fBipa\-test\-task configure\-auth\-to\-local\-rule HOST AD\fR
+Configures auth_to_local rule in /etc/krb5.conf
+
+.TP
+\fBipa\-test\-task clear\-sssd\-cache HOST\fR
+Clears SSSD cache by removing the cache files. Restarts SSSD.
+
+.TP
+\fBipa\-test\-task sync\-time HOST SERVER\fR
+Syncs the time with the remote server. Please note that this function leaves
+ntpd stopped.
+
+
.SH "EXIT STATUS"
0 if the command was successful
diff --git a/ipatests/test_integration/tasks.py b/ipatests/test_integration/tasks.py
index de650f587f93627e92bae414379573a9f4864a0f..a93a583e966da720920c63c6db643f6c5b377c83 100644
--- a/ipatests/test_integration/tasks.py
+++ b/ipatests/test_integration/tasks.py
@@ -32,6 +32,7 @@ from ldif import LDIFWriter
from ipapython import ipautil
from ipapython.dn import DN
from ipapython.ipa_log_manager import log_mgr
+from ipatests.test_integration import util
from ipatests.test_integration.config import env_to_script
log = log_mgr.get_logger(__name__)
@@ -197,6 +198,7 @@ def install_replica(master, replica, setup_ca=True):
kinit_admin(replica)
+
def install_client(master, client):
client.collect_log('/var/log/ipaclient-install.log')
@@ -212,6 +214,184 @@ def install_client(master, client):
kinit_admin(client)
+def install_adtrust(host):
+ """
+ Runs ipa-adtrust-install on the client and generates SIDs for the entries.
+ Configures the compat tree for the legacy clients.
+ """
+
+ # ipa-adtrust-install appends to ipaserver-install.log
+ host.collect_log('/var/log/ipaserver-install.log')
+
+ inst = host.domain.realm.replace('.', '-')
+ host.collect_log('/var/log/dirsrv/slapd-%s/errors' % inst)
+ host.collect_log('/var/log/dirsrv/slapd-%s/access' % inst)
+
+ kinit_admin(host)
+ host.run_command(['ipa-adtrust-install', '-U',
+ '--enable-compat',
+ '--netbios-name', host.netbios,
+ '-a', host.config.admin_password,
+ '--add-sids'])
+
+ # Restart named because it lost connection to dirsrv
+ # (Directory server restarts during the ipa-adtrust-install)
+ host.run_command(['systemctl', 'restart', 'named'])
+
+ # Check that named is running and has loaded the information from LDAP
+ dig_command = ['dig', 'SRV', '+short', '@localhost',
+ '_ldap._tcp.%s' % host.domain.name]
+ dig_output = '0 100 389 %s.' % host.hostname
+ dig_test = lambda x: re.search(re.escape(dig_output), x)
+
+ util.run_repeatedly(host, dig_command, test=dig_test)
+
+
+def configure_dns_for_trust(master, ad):
+ """
+ This configures DNS on IPA master according to the relationship of the
+ IPA's and AD's domains.
+ """
+
+ def is_subdomain(subdomain, domain):
+ subdomain_unpacked = subdomain.split('.')
+ domain_unpacked = domain.split('.')
+
+ subdomain_unpacked.reverse()
+ domain_unpacked.reverse()
+
+ subdomain = False
+
+ if len(subdomain_unpacked) > len(domain_unpacked):
+ subdomain = True
+
+ for subdomain_segment, domain_segment in zip(subdomain_unpacked,
+ domain_unpacked):
+ subdomain = subdomain and subdomain_segment == domain_segment
+
+ return subdomain
+
+ kinit_admin(master)
+
+ if is_subdomain(master.domain.name, ad.domain.name):
+ master.run_command(['ipa', 'dnszone-add', ad.domain.name,
+ '--name-server', ad.hostname,
+ '--admin-email', 'hostmaster@%s' % ad.domain.name,
+ '--forwarder', ad.ip,
+ '--forward-policy', 'only',
+ '--ip-address', ad.ip,
+ '--force'])
+ elif is_subdomain(ad.domain.name, master.domain.name):
+ master.run_command(['ipa', 'dnsrecord-add', master.domain.name,
+ '%s.%s' % (ad.shortname, ad.netbios),
+ '--a-ip-address', ad.ip])
+
+ master.run_command(['ipa', 'dnsrecord-add', master.domain.name,
+ ad.netbios,
+ '--ns-hostname',
+ '%s.%s' % (ad.shortname, ad.netbios)])
+
+ master.run_command(['ipa', 'dnszone-mod', master.domain.name,
+ '--allow-transfer', ad.ip])
+ else:
+ master.run_command(['ipa', 'dnszone-add', ad.domain.name,
+ '--name-server', ad.hostname,
+ '--admin-email', 'hostmaster@%s' % ad.domain.name,
+ '--forwarder', ad.ip,
+ '--forward-policy', 'only',
+ '--ip-address', ad.ip,
+ '--force'])
+
+
+def establish_trust_with_ad(master, ad, extra_args=()):
+ """
+ Establishes trust with Active Directory. Trust type is detected depending
+ on the presence of SfU (Services for Unix) support on the AD.
+
+ Use extra arguments to pass extra arguments to the trust-add command, such
+ as --range-type="ipa-ad-trust" to enfroce a particular range type.
+ """
+
+ # Force KDC to reload MS-PAC info by trying to get TGT for HTTP
+ master.run_command(['kinit', '-kt', '/etc/httpd/conf/ipa.keytab',
+ 'HTTP/%s' % master.hostname])
+ master.run_command(['systemctl', 'restart', 'krb5kdc.service'])
+ master.run_command(['kdestroy', '-A'])
+
+ kinit_admin(master)
+ master.run_command(['klist'])
+ master.run_command(['smbcontrol', 'all', 'debug', '100'])
+ util.run_repeatedly(master,
+ ['ipa', 'trust-add',
+ '--type', 'ad', ad.domain.name,
+ '--admin', 'Administrator',
+ '--password'] + list(extra_args),
+ stdin_text=master.config.ad_admin_password)
+ master.run_command(['smbcontrol', 'all', 'debug', '1'])
+ clear_sssd_cache(master)
+
+
+def remove_trust_with_ad(master, ad):
+ """
+ Removes trust with Active Directory. Also removes the associated ID range.
+ """
+
+ kinit_admin(master)
+
+ # Remove the trust
+ master.run_command(['ipa', 'trust-del', ad.domain.name])
+
+ # Remove the range
+ range_name = ad.domain.name.upper() + '_id_range'
+ master.run_command(['ipa', 'idrange-del', range_name])
+
+
+def configure_auth_to_local_rule(master, ad):
+ """
+ Configures auth_to_local rule in /etc/krb5.conf
+ """
+
+ section_identifier = " %s = {" % master.domain.realm
+ line1 = (" auth_to_local = RULE:[1:$1@$0](^.*@%s$)s/@%s/@%s/"
+ % (ad.domain.realm, ad.domain.realm, ad.domain.name))
+ line2 = " auth_to_local = DEFAULT"
+
+ krb5_conf_content = master.get_file_contents('/etc/krb5.conf')
+ krb5_lines = [line.rstrip() for line in krb5_conf_content.split('\n')]
+ realm_section_index = krb5_lines.index(section_identifier)
+
+ krb5_lines.insert(realm_section_index + 1, line1)
+ krb5_lines.insert(realm_section_index + 2, line2)
+
+ krb5_conf_new_content = '\n'.join(krb5_lines)
+ master.put_file_contents('/etc/krb5.conf', krb5_conf_new_content)
+
+ master.run_command(['systemctl', 'restart', 'sssd'])
+
+
+def clear_sssd_cache(host):
+ """
+ Clears SSSD cache by removing the cache files. Restarts SSSD.
+ """
+
+ host.run_command(['systemctl', 'stop', 'sssd'])
+ host.run_command(['rm', '-rfv', '/var/lib/sss/db/cache_%s.ldb'
+ % host.domain.name])
+ host.run_command(['rm', '-rfv', '/var/lib/sss/mc/group'])
+ host.run_command(['rm', '-rfv', '/var/lib/sss/mc/passwd'])
+ host.run_command(['systemctl', 'start', 'sssd'])
+
+
+def sync_time(host, server):
+ """
+ Syncs the time with the remote server. Please note that this function
+ leaves ntpd stopped.
+ """
+
+ host.run_command(['sudo', 'systemctl', 'stop', 'ntpd'])
+ host.run_command(['sudo', 'ntpdate', server.hostname])
+
+
def connect_replica(master, replica):
kinit_admin(replica)
replica.run_command(['ipa-replica-manage', 'connect', master.hostname])
--
1.8.3.1
From ed039706a48060bb22858a34bbfaf24c413204cd Mon Sep 17 00:00:00 2001
From: Tomas Babej <[email protected]>
Date: Wed, 4 Sep 2013 16:26:23 +0200
Subject: [PATCH 104/107] ipatests: Add WinHost class
Part of: https://fedorahosted.org/freeipa/ticket/3834
---
ipatests/test_integration/host.py | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/ipatests/test_integration/host.py b/ipatests/test_integration/host.py
index a5cced84cde6afea6d4d3ecc98a7cc93eb32d6b5..157c5eda8bfc9ff11348e3a5aefd993f1bf168f8 100644
--- a/ipatests/test_integration/host.py
+++ b/ipatests/test_integration/host.py
@@ -42,6 +42,8 @@ class BaseHost(object):
self.hostname = shortname + '.' + self.domain.name
self.external_hostname = hostname
+ self.netbios = self.domain.name.split('.')[0].upper()
+
self.logger_name = '%s.%s.%s' % (
self.__module__, type(self).__name__, shortname)
self.log = log_mgr.get_logger(self.logger_name)
@@ -94,6 +96,12 @@ class BaseHost(object):
def from_env(cls, env, domain, hostname, role, index):
ip = env.get('BEAKER%s%s_IP_env%s' %
(role.upper(), index, domain.index), None)
+
+ if role == 'ad':
+ cls = WinHost
+ else:
+ cls = Host
+
self = cls(domain, hostname, role, index, ip)
return self
@@ -217,3 +225,14 @@ class Host(BaseHost):
command.wait(raiseonerr=raiseonerr)
return command
+
+
+class WinHost(BaseHost):
+ """
+ Representation of a remote Windows host.
+
+ This serves as a sketch class once we move from manual preparation of
+ Active Directory to the automated setup.
+ """
+
+ pass
--
1.8.3.1
From d85372b7046e9df6872829cfd6cd648fd23ff681 Mon Sep 17 00:00:00 2001
From: Tomas Babej <[email protected]>
Date: Thu, 26 Sep 2013 13:18:59 +0200
Subject: [PATCH 103/107] ipatests: Create util module for ipatests
Part of: https://fedorahosted.org/freeipa/ticket/3834
---
ipatests/test_integration/util.py | 60 +++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 ipatests/test_integration/util.py
diff --git a/ipatests/test_integration/util.py b/ipatests/test_integration/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a1bb3fcc923c9f2721f0a4c1cb7a1ba2ccc2dd8
--- /dev/null
+++ b/ipatests/test_integration/util.py
@@ -0,0 +1,60 @@
+# Authors:
+# Tomas Babej <[email protected]>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import time
+
+
+def run_repeatedly(host, command, assert_zero_rc=True, test=None,
+ timeout=30, **kwargs):
+ """
+ Runs command on host repeatedly until it's finished successfully (returns
+ 0 exit code and its stdout passes the test function).
+
+ Returns True if the command was executed succesfully, False otherwise.
+
+ This method accepts additional kwargs and passes these arguments
+ to the actual run_command method.
+ """
+
+ time_waited = 0
+ time_step = 2
+
+ # Check that the test is a function
+ if test:
+ assert callable(test)
+
+ while(time_waited <= timeout):
+ result = host.run_command(command, raiseonerr=False, **kwargs)
+
+ return_code_ok = not assert_zero_rc or (result.returncode == 0)
+ test_ok = not test or test(result.stdout_text)
+
+ if return_code_ok and test_ok:
+ # Command successful
+ return True
+ else:
+ # Command not successful
+ time.sleep(time_step)
+ time_waited += time_step
+
+ raise AssertionError("Command: {cmd} repeatedly failed {times} times, "
+ "exceeding the timeout of {timeout} seconds."
+ .format(cmd=' '.join(command),
+ times=timeout / time_step,
+ timeout=timeout))
--
1.8.3.1
From 854239df84ed3ca2895b1b7e81ff440d4d235c9a Mon Sep 17 00:00:00 2001
From: Tomas Babej <[email protected]>
Date: Wed, 4 Sep 2013 15:02:21 +0200
Subject: [PATCH 102/107] ipatests: Extend IntegrationTest with multiple AD
domain support
Part of: https://fedorahosted.org/freeipa/ticket/3834
---
ipatests/test_integration/base.py | 28 ++++++++++++++++++----------
1 file changed, 18 insertions(+), 10 deletions(-)
diff --git a/ipatests/test_integration/base.py b/ipatests/test_integration/base.py
index 43360a83a4d2df656051beada7b69556066c2801..1bed7d55b0e89f88507165807e01ca7e185f2c80 100644
--- a/ipatests/test_integration/base.py
+++ b/ipatests/test_integration/base.py
@@ -35,10 +35,19 @@ log = log_mgr.get_logger(__name__)
class IntegrationTest(object):
num_replicas = 0
num_clients = 0
+ num_ad_domains = 0
topology = None
@classmethod
def setup_class(cls):
+
+ def get_resources(resource_container, resource_str, num_needed):
+ if len(resource_container) < num_needed:
+ raise nose.SkipTest(
+ 'Not enough %s available (have %s, need %s)' %
+ (resource_str, len(resource_container), num_needed))
+ return resource_container[:num_needed]
+
config = get_global_config()
if not config.domains:
raise nose.SkipTest('Integration testing not configured')
@@ -46,17 +55,15 @@ class IntegrationTest(object):
cls.logs_to_collect = {}
domain = config.domains[0]
+
cls.master = domain.master
- if len(domain.replicas) < cls.num_replicas:
- raise nose.SkipTest(
- 'Not enough replicas available (have %s, need %s)' %
- (len(domain.replicas), cls.num_replicas))
- if len(domain.clients) < cls.num_clients:
- raise nose.SkipTest(
- 'Not enough clients available (have %s, need %s)' %
- (len(domain.clients), cls.num_clients))
- cls.replicas = domain.replicas[:cls.num_replicas]
- cls.clients = domain.clients[:cls.num_clients]
+ cls.replicas = get_resources(domain.replicas, 'replicas',
+ cls.num_replicas)
+ cls.clients = get_resources(domain.clients, 'clients',
+ cls.num_clients)
+ cls.ad_domains = get_resources(config.ad_domains, 'AD domains',
+ cls.num_ad_domains)
+
for host in cls.get_all_hosts():
host.add_log_collector(cls.collect_log)
cls.prepare_host(host)
@@ -95,6 +102,7 @@ class IntegrationTest(object):
del cls.master
del cls.replicas
del cls.clients
+ del cls.ad_domains
@classmethod
def uninstall(cls):
--
1.8.3.1
From 722399cc090f9bd178f0a49fc6380fd8cfd5b124 Mon Sep 17 00:00:00 2001
From: Tomas Babej <[email protected]>
Date: Wed, 4 Sep 2013 14:24:41 +0200
Subject: [PATCH 101/107] ipatests: Extend domain object with 'ad' role support
and WinHosts
Part of: https://fedorahosted.org/freeipa/ticket/3834
---
ipatests/test_integration/config.py | 43 +++++++++++++++++++++----------------
1 file changed, 24 insertions(+), 19 deletions(-)
diff --git a/ipatests/test_integration/config.py b/ipatests/test_integration/config.py
index ae271e575ba1cb727b10cbb508a15b73a53e3a88..84228c736711de874984920458eba0e98d2814bf 100644
--- a/ipatests/test_integration/config.py
+++ b/ipatests/test_integration/config.py
@@ -27,7 +27,7 @@ import random
from ipapython import ipautil
from ipapython.dn import DN
from ipapython.ipa_log_manager import log_mgr
-from ipatests.test_integration.host import Host
+from ipatests.test_integration.host import BaseHost
class Config(object):
@@ -253,9 +253,10 @@ def env_normalize(env):
class Domain(object):
- """Configuration for an IPA domain"""
- def __init__(self, config, name, index):
+ """Configuration for an IPA / AD domain"""
+ def __init__(self, config, name, index, domain_type):
self.log = log_mgr.get_logger(self)
+ self.type = domain_type
self.config = config
self.name = name
@@ -268,27 +269,27 @@ class Domain(object):
self.basedn = DN(*(('dc', p) for p in name.split('.')))
@classmethod
- def from_env(cls, env, config, index):
- try:
- default_domain = env['DOMAIN']
- except KeyError:
- hostname, dot, default_domain = env['MASTER_env1'].partition('.')
- parts = default_domain.split('.')
+ def from_env(cls, env, config, index, domain_type):
- if index == 1:
- name = default_domain
+ # Roles available in the domain depend on the type of the domain
+ # Unix machines are added only to the IPA domains, Windows machines
+ # only to the AD domains
+ if domain_type == 'IPA':
+ master_role = 'MASTER'
+ domain_roles = 'master', 'replica', 'client', 'other'
else:
- # For $DOMAIN = dom.example.com, additional domains are
- # dom1.example.com, dom2.example.com, etc.
- parts[0] += str(index)
- name = '.'.join(parts)
+ master_role = 'AD'
+ domain_roles = 'ad',
- self = cls(config, name, index)
+ master_env = '%s_env%s' % (master_role, index)
+ hostname, dot, domain_name = env[master_env].partition('.')
+ self = cls(config, domain_name, index, domain_type)
- for role in 'master', 'replica', 'client', 'other':
+ for role in domain_roles:
value = env.get('%s%s' % (role.upper(), self._env), '')
+
for index, hostname in enumerate(value.split(), start=1):
- host = Host.from_env(env, self, hostname, role, index)
+ host = BaseHost.from_env(env, self, hostname, role, index)
self.hosts.append(host)
if not self.hosts:
@@ -323,9 +324,13 @@ class Domain(object):
return [h for h in self.hosts if h.role == 'client']
@property
+ def ads(self):
+ return [h for h in self.hosts if h.role == 'ad']
+
+ @property
def other_hosts(self):
return [h for h in self.hosts
- if h.role not in ('master', 'client', 'replica')]
+ if h.role not in ('master', 'client', 'replica', 'ad')]
def host_by_name(self, name):
for host in self.hosts:
--
1.8.3.1
_______________________________________________
Freeipa-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/freeipa-devel