- Resolves: RHEL-155027 Adding a group with 32Bit Idrange fails - Resolves: RHEL-153145 IdM password policy Min lifetime is not enforced when high minlife is set - Resolves: RHEL-166864 Include latest fixes in python3-ipatests package Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
264 lines
10 KiB
Diff
264 lines
10 KiB
Diff
From 3acf55ed7fcf9a3b38deb0efb98d222d57bafbbc Mon Sep 17 00:00:00 2001
|
|
From: Anuja More <amore@redhat.com>
|
|
Date: Thu, 26 Feb 2026 12:04:50 +0530
|
|
Subject: [PATCH] ipatests: add HTTP GSSAPI Kerberos authentication tests with
|
|
AD trust
|
|
|
|
Add TestTrustFunctionalHttp integration test class covering GSSAPI-protected
|
|
HTTP access in an AD trust environment:
|
|
|
|
- test_ipa_trust_func_http_krb_ipauser: IPA user with a valid Kerberos ticket
|
|
can access the GSSAPI-protected webapp; AD root and subdomain users are
|
|
denied when only the IPA user is in the Allow list
|
|
- test_ipa_trust_func_http_krb_aduser: AD root domain and subdomain users with
|
|
valid Kerberos tickets can access the webapp; IPA users are denied when the
|
|
Allow list is configured for an AD user
|
|
- test_ipa_trust_func_http_krb_nouser: a user without a Kerberos ticket
|
|
receives a 401 Unauthorized response
|
|
|
|
The class sets up an Apache httpd instance with mod_auth_gssapi on the IPA
|
|
client, obtains a service keytab via ipa-getkeytab, and uses curl with
|
|
GSSAPI negotiate to drive each scenario.
|
|
|
|
Related: https://pagure.io/freeipa/issue/9845
|
|
Assisted-by: Claude noreply@anthropic.com
|
|
Signed-off-by: Anuja More <amore@redhat.com>
|
|
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
|
Reviewed-By: David Hanina <dhanina@redhat.com>
|
|
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
|
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
|
Reviewed-By: David Hanina <dhanina@redhat.com>
|
|
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
|
---
|
|
.../test_integration/test_trust_functional.py | 209 +++++++++++++++++-
|
|
1 files changed, 209 insertions(+), 1 deletions(-)
|
|
|
|
diff --git a/ipatests/test_integration/test_trust_functional.py b/ipatests/test_integration/test_trust_functional.py
|
|
index a85f21e96463757b9a446df666d5361e65ba686c..5a9eae8cebfdae23bf37d3298e455bab8e3304ef 100644
|
|
--- a/ipatests/test_integration/test_trust_functional.py
|
|
+++ b/ipatests/test_integration/test_trust_functional.py
|
|
@@ -2,8 +2,9 @@
|
|
|
|
from __future__ import absolute_import
|
|
|
|
+import re
|
|
import time
|
|
-
|
|
+import textwrap
|
|
from ipaplatform.paths import paths
|
|
from ipatests.pytest_ipa.integration import tasks
|
|
from ipatests.test_integration.test_trust import BaseTestTrust
|
|
@@ -658,3 +659,209 @@ class TestTrustFunctionalSudo(BaseTestTrust):
|
|
raiseonerr=False)
|
|
finally:
|
|
self._cleanup_srule(srule)
|
|
+
|
|
+
|
|
+class TestTrustFunctionalHttp(BaseTestTrust):
|
|
+ topology = 'line'
|
|
+ num_ad_treedomains = 0
|
|
+
|
|
+ ad_user_password = 'Secret123'
|
|
+
|
|
+ # Apache configuration for GSSAPI-protected webapp. The /mywebapp
|
|
+ # location requires Kerberos authentication and restricts access by
|
|
+ # domain: IPA users (@IPA_REALM) or AD users (@AD_DOMAIN).
|
|
+ apache_conf = textwrap.dedent('''
|
|
+ Alias /mywebapp "/var/www/html/mywebapp"
|
|
+ <Directory "/var/www/html/mywebapp">
|
|
+ Allow from all
|
|
+ </Directory>
|
|
+ <Location "/mywebapp">
|
|
+ LogLevel debug
|
|
+ AuthType GSSAPI
|
|
+ AuthName "IPA Kerberos authentication"
|
|
+ GssapiNegotiateOnce on
|
|
+ GssapiBasicAuthMech krb5
|
|
+ GssapiCredStore keytab:{keytab_path}
|
|
+ <RequireAll>
|
|
+ Require valid-user
|
|
+ # Require expr: restrict access by domain. REMOTE_USER is set by
|
|
+ # mod_auth_gssapi after GSSAPI authentication. Allow users whose
|
|
+ # principal ends with the domain (IPA realm or AD domain).
|
|
+ Require expr %{{REMOTE_USER}} =~ /{allowed_domain_regex}$/
|
|
+ </RequireAll>
|
|
+ </Location>
|
|
+ ''')
|
|
+
|
|
+ def _configure_webapp(self, allowed_domain):
|
|
+ """Write the GSSAPI vhost config and restart httpd on the client.
|
|
+
|
|
+ allowed_domain: realm/domain for access control (e.g. IPA.TEST for
|
|
+ IPA users, AD.DOMAIN for AD users). Users whose principal ends with
|
|
+ @allowed_domain are granted access.
|
|
+ """
|
|
+ # Escape dots for regex (e.g. IPA.TEST -> IPA\\.TEST)
|
|
+ escaped = re.escape(allowed_domain)
|
|
+ allowed_domain_regex = '.*@' + escaped
|
|
+ keytab_path = f"/etc/httpd/conf/{self.clients[0].hostname}.keytab"
|
|
+ self.clients[0].put_file_contents(
|
|
+ '/etc/httpd/conf.d/mywebapp.conf',
|
|
+ self.apache_conf.format(
|
|
+ keytab_path=keytab_path,
|
|
+ allowed_domain_regex=allowed_domain_regex,
|
|
+ )
|
|
+ )
|
|
+ self.clients[0].run_command(['systemctl', 'restart', 'httpd'])
|
|
+
|
|
+ def _assert_curl_ok(self, msg=None):
|
|
+ """Run curl with GSSAPI negotiate and assert the webapp responds."""
|
|
+ url = f"http://{self.clients[0].hostname}/mywebapp/index.html"
|
|
+ result = self.clients[0].run_command([
|
|
+ paths.BIN_CURL, '-v', '--negotiate', '-u:', url
|
|
+ ])
|
|
+ assert "TEST_MY_WEB_APP" in result.stdout_text, (
|
|
+ msg or f"Expected webapp content at {url}"
|
|
+ )
|
|
+
|
|
+ def _assert_curl_GSSAPI_access_denied(self, msg=None):
|
|
+ """Run curl with GSSAPI negotiate and assert a 401 is returned."""
|
|
+ url = f"http://{self.clients[0].hostname}/mywebapp/index.html"
|
|
+ result = self.clients[0].run_command([
|
|
+ paths.BIN_CURL, '-v', '--negotiate', '-u:', url
|
|
+ ], raiseonerr=False)
|
|
+ output = f"{result.stdout_text}{result.stderr_text}"
|
|
+ assert ("401" in output
|
|
+ or "Unauthorized" in output
|
|
+ or "Authorization Required" in output), (
|
|
+ msg or f"Expected 401/Unauthorized at {url}, got: {output[:200]}"
|
|
+ )
|
|
+
|
|
+ @classmethod
|
|
+ def install(cls, mh):
|
|
+ """Extend base install to configure Apache/GSSAPI for HTTP tests.
|
|
+
|
|
+ Runs once before any test in this class. Sets up the AD trust,
|
|
+ creates the HTTP service principal and IPA test user, installs
|
|
+ mod_auth_gssapi, retrieves the service keytab, and provisions the
|
|
+ static webapp content used by all HTTP tests.
|
|
+ """
|
|
+ super().install(mh)
|
|
+ tasks.configure_dns_for_trust(cls.master, cls.ad)
|
|
+ tasks.establish_trust_with_ad(
|
|
+ cls.master, cls.ad_domain,
|
|
+ extra_args=['--range-type', 'ipa-ad-trust'])
|
|
+
|
|
+ # Create HTTP service principal on master
|
|
+ service_principal = f"HTTP/{cls.clients[0].hostname}"
|
|
+ cls.master.run_command(
|
|
+ ["ipa", "service-add", service_principal]
|
|
+ )
|
|
+
|
|
+ # Create IPA user for HTTP tests
|
|
+ tasks.create_active_user(
|
|
+ cls.master, "ipahttpuser1", password="Passw0rd1",
|
|
+ first="f", last="l"
|
|
+ )
|
|
+
|
|
+ # Clear SSSD cache on master
|
|
+ tasks.clear_sssd_cache(cls.master)
|
|
+ tasks.wait_for_sssd_domain_status_online(cls.master)
|
|
+
|
|
+ # Install Apache and the GSSAPI module on the IPA client
|
|
+ tasks.install_packages(
|
|
+ cls.clients[0], ['mod_auth_gssapi', 'httpd']
|
|
+ )
|
|
+
|
|
+ # Retrieve and protect the HTTP service keytab
|
|
+ keytab_path = f"/etc/httpd/conf/{cls.clients[0].hostname}.keytab"
|
|
+ cls.clients[0].run_command([
|
|
+ 'ipa-getkeytab', '-s', cls.master.hostname,
|
|
+ '-k', keytab_path,
|
|
+ '-p', service_principal
|
|
+ ])
|
|
+ cls.clients[0].run_command(
|
|
+ ['chown', 'apache:apache', keytab_path]
|
|
+ )
|
|
+
|
|
+ # Create webapp directory and static content
|
|
+ cls.clients[0].run_command(
|
|
+ ['mkdir', '-p', '/var/www/html/mywebapp']
|
|
+ )
|
|
+ cls.clients[0].put_file_contents(
|
|
+ '/var/www/html/mywebapp/index.html',
|
|
+ 'TEST_MY_WEB_APP\n'
|
|
+ )
|
|
+
|
|
+ def test_ipa_trust_func_http_krb_ipauser(self):
|
|
+ """
|
|
+ Test IPA User access http with kerberos ticket via valid user.
|
|
+
|
|
+ This test verifies that an IPA user with a valid Kerberos ticket
|
|
+ can successfully access an HTTP resource protected by GSSAPI
|
|
+ authentication and restricted to IPA users.
|
|
+ """
|
|
+ ipa_realm = self.clients[0].domain.realm
|
|
+ self._configure_webapp(ipa_realm)
|
|
+
|
|
+ tasks.kdestroy_all(self.clients[0])
|
|
+ tasks.kinit_as_user(
|
|
+ self.clients[0], f'ipahttpuser1@{ipa_realm}', "Passw0rd1"
|
|
+ )
|
|
+
|
|
+ self._assert_curl_ok()
|
|
+
|
|
+ users = [
|
|
+ (self.aduser, self.ad_domain),
|
|
+ (self.subaduser, self.ad_subdomain),
|
|
+ ]
|
|
+ for aduser, domain in users:
|
|
+ tasks.kdestroy_all(self.clients[0])
|
|
+ # pylint: disable=use-maxsplit-arg
|
|
+ principal = f"{aduser.split('@')[0]}@{domain.upper()}"
|
|
+ tasks.kinit_as_user(
|
|
+ self.clients[0], principal, self.ad_user_password
|
|
+ )
|
|
+ self._assert_curl_GSSAPI_access_denied(
|
|
+ msg=f"Expected 401 for AD user {aduser}"
|
|
+ )
|
|
+
|
|
+ def test_ipa_trust_func_http_krb_aduser(self):
|
|
+ """
|
|
+ Test AD root and subdomain users access http with kerberos ticket.
|
|
+
|
|
+ This test verifies that both a root AD domain user and a child
|
|
+ subdomain user with valid Kerberos tickets can successfully access
|
|
+ an HTTP resource protected by GSSAPI authentication and restricted
|
|
+ to AD domain / AD subdomain users.
|
|
+ """
|
|
+ users = [
|
|
+ (self.aduser, self.ad_domain),
|
|
+ (self.subaduser, self.ad_subdomain),
|
|
+ ]
|
|
+ for aduser, domain in users:
|
|
+ tasks.kdestroy_all(self.clients[0])
|
|
+ # pylint: disable=use-maxsplit-arg
|
|
+ principal = f"{aduser.split('@')[0]}@{domain.upper()}"
|
|
+ self._configure_webapp(domain.upper())
|
|
+ tasks.kinit_as_user(
|
|
+ self.clients[0], principal, self.ad_user_password
|
|
+ )
|
|
+ self._assert_curl_ok(
|
|
+ msg=f"Expected webapp content for AD user {aduser}"
|
|
+ )
|
|
+ tasks.kdestroy_all(self.clients[0])
|
|
+ tasks.kinit_as_user(self.clients[0], "ipahttpuser1", "Passw0rd1")
|
|
+ self._assert_curl_GSSAPI_access_denied(
|
|
+ msg=f"Expected 401 for IPA user after AD user {aduser}"
|
|
+ )
|
|
+
|
|
+ def test_ipa_trust_func_http_krb_nouser(self):
|
|
+ """
|
|
+ Test User cannot access http without kerberos ticket via valid user.
|
|
+
|
|
+ This test verifies that an user without a valid Kerberos ticket
|
|
+ is denied access to an HTTP resource protected by GSSAPI
|
|
+ authentication, receiving a 401 Unauthorized error.
|
|
+ """
|
|
+ tasks.kdestroy_all(self.clients[0])
|
|
+
|
|
+ self._assert_curl_GSSAPI_access_denied()
|
|
--
|
|
2.53.0
|
|
|