From 221f3ce5edd1abeceb0a3cf35258f81e9ff96353 Mon Sep 17 00:00:00 2001
From: Sergey Orlov <sorlov@redhat.com>
Date: Tue, 21 May 2019 15:54:14 +0200
Subject: [PATCH] ipatests: new tests for establishing one-way AD trust with
 shared secret

Tests added for two scenarios:
1) adding one-way external trust, trust on Windows side is created using
   netdom utility.
2) adding one-way forest trust, trust on Windows side is created using
   powershell bindings to .Net functions
Tests verify that specified trusts can be established, trust domains can
be  fetched and AD user data can be queried by IPA client.

Relates: https://pagure.io/freeipa/issue/6077
Reviewed-By: Kaleemullah Siddiqui <ksiddiqu@redhat.com>
---
 ipatests/pytest_ipa/integration/tasks.py |  28 +++--
 ipatests/test_integration/test_trust.py  | 127 +++++++++++++++++++++++
 2 files changed, 148 insertions(+), 7 deletions(-)

diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
index 8846c709bc..7fdeb87e06 100644
--- a/ipatests/pytest_ipa/integration/tasks.py
+++ b/ipatests/pytest_ipa/integration/tasks.py
@@ -578,7 +578,17 @@ def unconfigure_dns_for_trust(master, ad):
         restore_dnssec_validation(master)
 
 
-def establish_trust_with_ad(master, ad_domain, extra_args=()):
+def configure_windows_dns_for_trust(ad, master):
+    ad.run_command(['dnscmd', '/zoneadd', master.domain.name,
+                    '/Forwarder', master.ip])
+
+
+def unconfigure_windows_dns_for_trust(ad, master):
+    ad.run_command(['dnscmd', '/zonedelete', master.domain.name, '/f'])
+
+
+def establish_trust_with_ad(master, ad_domain, extra_args=(),
+                            shared_secret=None):
     """
     Establishes trust with Active Directory. Trust type is detected depending
     on the presence of SfU (Services for Unix) support on the AD.
@@ -588,6 +598,7 @@ def establish_trust_with_ad(master, ad_domain, extra_args=()):
     """
 
     # Force KDC to reload MS-PAC info by trying to get TGT for HTTP
+    extra_args = list(extra_args)
     master.run_command(['kinit', '-kt', paths.HTTP_KEYTAB,
                         'HTTP/%s' % master.hostname])
     master.run_command(['systemctl', 'restart', 'krb5kdc.service'])
@@ -597,12 +608,15 @@ def establish_trust_with_ad(master, ad_domain, extra_args=()):
     master.run_command(['klist'])
     master.run_command(['smbcontrol', 'all', 'debug', '100'])
 
-    run_repeatedly(master,
-                   ['ipa', 'trust-add',
-                    '--type', 'ad', ad_domain,
-                    '--admin', 'Administrator',
-                    '--password'] + list(extra_args),
-                   stdin_text=master.config.ad_admin_password)
+    if shared_secret:
+        extra_args += ['--trust-secret']
+        stdin_text = shared_secret
+    else:
+        extra_args += ['--admin', 'Administrator', '--password']
+        stdin_text = master.config.ad_admin_password
+    run_repeatedly(
+        master, ['ipa', 'trust-add', '--type', 'ad', ad_domain] + extra_args,
+        stdin_text=stdin_text)
     master.run_command(['smbcontrol', 'all', 'debug', '1'])
     clear_sssd_cache(master)
     master.run_command(['systemctl', 'restart', 'krb5kdc.service'])
diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
index bf6baafd29..16916084a6 100644
--- a/ipatests/test_integration/test_trust.py
+++ b/ipatests/test_integration/test_trust.py
@@ -21,6 +21,8 @@ class TestTrust(IntegrationTest):
     upn_principal = '{}@{}'.format(upn_username, upn_suffix)
     upn_password = 'Secret123456'
 
+    shared_secret = 'qwertyuiopQq!1'
+
     @classmethod
     def install(cls, mh):
         if not cls.master.transport.file_exists('/usr/bin/rpcclient'):
@@ -37,6 +39,13 @@ def install(cls, mh):
         cls.tree_ad = cls.ad_treedomains[0]  # pylint: disable=no-member
         cls.ad_treedomain = cls.tree_ad.domain.name
 
+        # values used in workaround for
+        # https://bugzilla.redhat.com/show_bug.cgi?id=1711958
+        cls.srv_gc_record_name = \
+            '_ldap._tcp.Default-First-Site-Name._sites.gc._msdcs'
+        cls.srv_gc_record_value = '0 100 389 {}.'.format(cls.master.hostname)
+
+
     @classmethod
     def check_sid_generation(cls):
         command = ['ipa', 'user-show', 'admin', '--all', '--raw']
@@ -343,3 +352,121 @@ def test_trustdomains_found_in_external_rootdomain_trust(self):
 
     def test_remove_external_rootdomain_trust(self):
         self.remove_trust(self.ad)
+
+    # Test for one-way forest trust with shared secret
+
+    def test_establish_forest_trust_with_shared_secret(self):
+        self.configure_dns_and_time(self.ad)
+        tasks.configure_windows_dns_for_trust(self.ad, self.master)
+
+        # this is a workaround for
+        # https://bugzilla.redhat.com/show_bug.cgi?id=1711958
+        self.master.run_command(
+            ['ipa', 'dnsrecord-add', self.master.domain.name,
+             self.srv_gc_record_name,
+             '--srv-rec', self.srv_gc_record_value])
+
+        # create windows side of trust using powershell bindings
+        # to .Net functions
+        ps_cmd = (
+            '[System.DirectoryServices.ActiveDirectory.Forest]'
+            '::getCurrentForest()'
+            '.CreateLocalSideOfTrustRelationship("{}", 1, "{}")'.format(
+                self.master.domain.name, self.shared_secret))
+        self.ad.run_command(['powershell', '-c', ps_cmd])
+
+        # create ipa side of trust
+        tasks.establish_trust_with_ad(
+            self.master, self.ad_domain, shared_secret=self.shared_secret)
+
+    def test_trustdomains_found_in_forest_trust_with_shared_secret(self):
+        result = self.master.run_command(
+            ['ipa', 'trust-fetch-domains', self.ad.domain.name],
+            raiseonerr=False)
+        assert result.returncode == 1
+        self.check_trustdomains(
+            self.ad_domain, [self.ad_domain, self.ad_subdomain])
+
+    def test_user_gid_uid_resolution_in_forest_trust_with_shared_secret(self):
+        """Check that user has SID-generated UID"""
+        # Using domain name since it is lowercased realm name for AD domains
+        testuser = 'testuser@%s' % self.ad_domain
+        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 = r"^testuser@%s:\*:(?!10042)(\d+):(?!10047)(\d+):"\
+                         r"Test User:/home/%s/testuser:/bin/sh$"\
+                         % (re.escape(self.ad_domain),
+                            re.escape(self.ad_domain))
+
+        assert re.search(
+            testuser_regex, result.stdout_text), result.stdout_text
+
+    def test_remove_forest_trust_with_shared_secret(self):
+        ps_cmd = (
+            '[System.DirectoryServices.ActiveDirectory.Forest]'
+            '::getCurrentForest()'
+            '.DeleteLocalSideOfTrustRelationship("{}")'.format(
+                self.master.domain.name))
+        self.ad.run_command(['powershell', '-c', ps_cmd])
+
+        self.remove_trust(self.ad)
+
+        # this is cleanup for workaround for
+        # https://bugzilla.redhat.com/show_bug.cgi?id=1711958
+        self.master.run_command(
+            ['ipa', 'dnsrecord-del', self.master.domain.name,
+             self.srv_gc_record_name, '--srv-rec',
+             self.srv_gc_record_value])
+
+        tasks.unconfigure_windows_dns_for_trust(self.ad, self.master)
+
+    # Test for one-way external trust with shared secret
+
+    def test_establish_external_trust_with_shared_secret(self):
+        self.configure_dns_and_time(self.ad)
+        tasks.configure_windows_dns_for_trust(self.ad, self.master)
+
+        # create windows side of trust using netdom.exe utility
+        self.ad.run_command(
+            ['netdom.exe', 'trust', self.master.domain.name,
+             '/d:' + self.ad.domain.name,
+             '/passwordt:' + self.shared_secret, '/add', '/oneside:TRUSTED'])
+
+        # create ipa side of trust
+        tasks.establish_trust_with_ad(
+            self.master, self.ad_domain, shared_secret=self.shared_secret,
+            extra_args=['--range-type', 'ipa-ad-trust', '--external=True'])
+
+    def test_trustdomains_found_in_external_trust_with_shared_secret(self):
+        result = self.master.run_command(
+            ['ipa', 'trust-fetch-domains', self.ad.domain.name],
+            raiseonerr=False)
+        assert result.returncode == 1
+        self.check_trustdomains(
+            self.ad_domain, [self.ad_domain])
+
+    def test_user_uid_resolution_in_external_trust_with_shared_secret(self):
+        """Check that user has SID-generated UID"""
+        # Using domain name since it is lowercased realm name for AD domains
+        testuser = 'testuser@%s' % self.ad_domain
+        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 = r"^testuser@%s:\*:(?!10042)(\d+):(?!10047)(\d+):"\
+                         r"Test User:/home/%s/testuser:/bin/sh$"\
+                         % (re.escape(self.ad_domain),
+                            re.escape(self.ad_domain))
+
+        assert re.search(
+            testuser_regex, result.stdout_text), result.stdout_text
+
+    def test_remove_external_trust_with_shared_secret(self):
+        self.ad.run_command(
+            ['netdom.exe', 'trust', self.master.domain.name,
+             '/d:' + self.ad.domain.name, '/remove', '/oneside:TRUSTED']
+        )
+        self.remove_trust(self.ad)
+        tasks.unconfigure_windows_dns_for_trust(self.ad, self.master)
