ipa/0014-ipatests-Add-integration-tests-for-ipa-join-command.patch
Florence Blanc-Renaud f8b144f61d ipa-4.13.1-2
- RHEL-147191 ipa uninstallation is failing with message "'NoneType' object has no attribute 'lower'"
- RHEL-146185 Names of domains from a trusted forest should be compared case-insentive
- RHEL-146184 ipa-advise client script requires keytab (should just require root access on client system)
- RHEL-145854 Include latest fixes in python3-ipatests package
- RHEL-143684 When using xmlrpc, ipa server failed with assert type(value) in (unicode, float, int, bool, type(None))
- RHEL-141500 ipa use systemd-sysusers

Note that there is a slight modification of the patch for systemd-sysusers.
Instead of definining kdcproxy and ipaapi with u! the patch uses u
(because u! requires systemd version 257).

Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
2026-02-09 19:20:45 +01:00

761 lines
30 KiB
Diff

From 7cb2c5d38a84eb31aa9ddd20cf9dc0b6d90fa242 Mon Sep 17 00:00:00 2001
From: PRANAV THUBE <pthube@redhat.com>
Date: Tue, 27 Jan 2026 18:45:53 +0530
Subject: [PATCH] ipatests: Add integration tests for ipa-join command
Add tests for ipa-join command covering hostname, server, keytab,
and bindpw options with positive and negative scenarios.
Related: https://pagure.io/freeipa/issue/9930
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
ipatests/prci_definitions/gating.yaml | 12 +
.../nightly_ipa-4-13_latest.yaml | 12 +
.../nightly_ipa-4-13_latest_selinux.yaml | 13 +
ipatests/pytest_ipa/integration/tasks.py | 49 ++
ipatests/test_integration/test_ipa_join.py | 614 ++++++++++++++++++
5 files changed, 700 insertions(+)
create mode 100644 ipatests/test_integration/test_ipa_join.py
diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
index 0c2a0dafa9de12add8959d9974f080ba8bec0706..182f2c5a097856d22336fa66a0876bdfcf3f3f8d 100644
--- a/ipatests/prci_definitions/gating.yaml
+++ b/ipatests/prci_definitions/gating.yaml
@@ -394,3 +394,15 @@ jobs:
template: *ci-ipa-4-13-latest
timeout: 7200
topology: *master_3client
+
+ fedora-latest-ipa-4-13/test_ipa_join:
+ requires: [fedora-latest-ipa-4-13/build]
+ priority: 100
+ job:
+ class: RunPytest
+ args:
+ build_url: '{fedora-latest-ipa-4-13/build_url}'
+ test_suite: test_integration/test_ipa_join.py
+ template: *ci-ipa-4-13-latest
+ timeout: 3600
+ topology: *master_1repl_1client
diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
index c61701ef5f88760f1d6fc36d4acce453a22b6f8f..aba33a5a05185460305c7516c93ae25c60f9dda7 100644
--- a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
@@ -2292,3 +2292,15 @@ jobs:
template: *ci-ipa-4-13-latest
timeout: 2400
topology: *ipaserver
+
+ fedora-latest-ipa-4-13/test_ipa_join:
+ requires: [fedora-latest-ipa-4-13/build]
+ priority: 50
+ job:
+ class: RunPytest
+ args:
+ build_url: '{fedora-latest-ipa-4-13/build_url}'
+ test_suite: test_integration/test_ipa_join.py
+ template: *ci-ipa-4-13-latest
+ timeout: 3600
+ topology: *master_1repl_1client
diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
index 9b96f3e857e2125478b45632d8d58e42b6e92668..7184b722076ba2cab7d782d990a9bc218158a09f 100644
--- a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
@@ -2476,3 +2476,16 @@ jobs:
template: *ci-ipa-4-13-latest
timeout: 2400
topology: *ipaserver
+
+ fedora-latest-ipa-4-13/test_ipa_join:
+ requires: [fedora-latest-ipa-4-13/build]
+ priority: 50
+ job:
+ class: RunPytest
+ args:
+ build_url: '{fedora-latest-ipa-4-13/build_url}'
+ selinux_enforcing: True
+ test_suite: test_integration/test_ipa_join.py
+ template: *ci-ipa-4-13-latest
+ timeout: 3600
+ topology: *master_1repl_1client
diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
index ff2ea9792d04ebd2e6bd7bb3b51d97f35cb3fbfb..47330d6d93401485e4eb7b2501cf5ea37498d719 100755
--- a/ipatests/pytest_ipa/integration/tasks.py
+++ b/ipatests/pytest_ipa/integration/tasks.py
@@ -3355,3 +3355,52 @@ def host_add_with_random_password(host, new_host):
re.MULTILINE)
randpasswd1 = result.group('password')
return randpasswd1
+
+
+def ipa_join(host, *extra_args, raiseonerr=True):
+ """Run ipa-join command.
+
+ :param host: The host to run command on
+ :param extra_args: Additional arguments (variable positional args)
+ e.g., '--hostname=client.example.com',
+ '--server=master.example.com',
+ '--keytab=/tmp/test.keytab',
+ '--bindpw=password',
+ '-u' (for unenroll)
+ :param raiseonerr: If True, raise exception on command failure
+ :return: Command result object
+ """
+ command = ['ipa-join']
+ command.extend(extra_args)
+ return host.run_command(command, raiseonerr=raiseonerr)
+
+
+def host_del(host, hostname, *extra_args, raiseonerr=True):
+ """Delete a host from IPA.
+
+ :param host: The IPA host to run command on
+ :param hostname: Hostname to delete
+ :param extra_args: Additional arguments (variable positional args)
+ :param raiseonerr: If True, raise exception on command failure
+ :return: Command result object
+ """
+ command = ['ipa', 'host-del', hostname]
+ command.extend(extra_args)
+ return host.run_command(command, raiseonerr=raiseonerr)
+
+
+def host_add(host, hostname, *extra_args, password=None, raiseonerr=True):
+ """Add a host to IPA.
+
+ :param host: The IPA host to run command on
+ :param hostname: Hostname to add
+ :param extra_args: Additional arguments (variable positional args)
+ :param password: OTP/enrollment password for the host (optional)
+ :param raiseonerr: If True, raise exception on command failure
+ :return: Command result object
+ """
+ command = ['ipa', 'host-add', hostname]
+ if password:
+ command.append(f'--password={password}')
+ command.extend(extra_args)
+ return host.run_command(command, raiseonerr=raiseonerr)
diff --git a/ipatests/test_integration/test_ipa_join.py b/ipatests/test_integration/test_ipa_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f7592aec8db1bfd048ec574d06d25bc24373499
--- /dev/null
+++ b/ipatests/test_integration/test_ipa_join.py
@@ -0,0 +1,614 @@
+#
+# Copyright (C) 2026 FreeIPA Contributors see COPYING for license
+#
+
+"""
+Tests for ipa-join command functionality.
+
+This module tests various combinations of ipa-join options including:
+- hostname
+- server
+- keytab
+- bindpw (OTP/enrollment password)
+- unenroll
+
+Ported from the shell-based test suite (t.ipajoin.sh and t.ipaotp.sh).
+"""
+
+from __future__ import absolute_import
+
+from ipapython.ipautil import ipa_generate_password
+from ipatests.pytest_ipa.integration import tasks
+from ipatests.test_integration.base import IntegrationTest
+
+
+# Constants
+OTP = ipa_generate_password(special=None)
+INVALID_PASSWORD = "WrongPassword"
+INVALID_SERVER = "No.Such.IPA.Server.Domain.com"
+TEST_KEYTAB = "/tmp/ipajoin.test.keytab"
+
+# Error messages
+ERR_SASL_BIND_FAILED = "SASL Bind failed"
+ERR_UNAUTHENTICATED_BIND = "Unauthenticated binds are not allowed"
+ERR_COULD_NOT_RESOLVE = "JSON-RPC call failed: Could not resolve hostname"
+ERR_UNABLE_ROOT_DN = "Unable to determine root DN"
+ERR_NO_CONFIG = "Unable to determine IPA server from /etc/ipa/default.conf"
+ERR_PREAUTH_FAILED = "Generic preauthentication failure"
+
+# Exit codes
+EXIT_SUCCESS = 0
+EXIT_GENERAL_ERROR = 1
+EXIT_PREAUTH_ERROR = 19
+EXIT_ROOT_DN_ERROR = 14
+EXIT_SASL_BIND_FAILED = 15
+EXIT_RESOLVE_ERROR = 17
+
+
+class TestIPAJoin(IntegrationTest):
+ """Tests for ipa-join command functionality.
+
+ This test class covers various ipa-join scenarios including:
+ - Basic enrollment and unenrollment
+ - Using hostname, server, keytab, and bindpw options
+ - Positive and negative test cases
+ - OTP (one-time password) enrollment tests
+
+ Tests require one master and one client.
+ """
+
+ topology = 'line'
+ num_clients = 1
+
+ @classmethod
+ def install(cls, mh):
+ tasks.install_master(cls.master, setup_dns=True)
+ tasks.install_client(cls.master, cls.clients[0])
+
+ @classmethod
+ def uninstall(cls, mh):
+ # Cleanup test keytab if exists
+ cls.clients[0].run_command(
+ ['rm', '-f', TEST_KEYTAB],
+ raiseonerr=False
+ )
+ tasks.uninstall_client(cls.clients[0])
+ tasks.uninstall_master(cls.master)
+
+ # =========================================================================
+ # ipa-join basic tests
+ # =========================================================================
+
+ def test_unenroll(self):
+ """Test ipa-join --unenroll option."""
+ result = tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+ assert result.returncode == EXIT_SUCCESS
+
+ def test_unenroll_already_unenrolled(self):
+ """Test ipa-join -u on an already unenrolled client.
+
+ When trying to unenroll a client that is not enrolled,
+ ipa-join should fail with a preauthentication error.
+ """
+ # Client is already unenrolled from previous test
+ result = tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ assert result.returncode == EXIT_PREAUTH_ERROR
+ assert ERR_PREAUTH_FAILED in result.stderr_text
+
+ def test_hostname_with_kerberos(self):
+ """Test ipa-join with --hostname using Kerberos auth."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_bindpw_invalid(self):
+ """Test ipa-join with hostname and invalid bindpw."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_hostname_bindpw_valid(self):
+ """Test ipa-join with hostname and valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_keytab_with_kerberos(self):
+ """Test ipa-join with hostname and keytab using Kerberos."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--keytab={TEST_KEYTAB}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_keytab_bindpw_invalid(self):
+ """Test ipa-join with hostname, keytab, and invalid bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_hostname_keytab_bindpw_valid(self):
+ """Test ipa-join with hostname, keytab, and valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_server_invalid_with_kerberos(self):
+ """Test ipa-join with hostname and invalid server."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={INVALID_SERVER}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_RESOLVE_ERROR
+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+
+ def test_hostname_server_invalid_bindpw_valid(self):
+ """Test ipa-join with hostname, invalid server, and valid OTP."""
+ tasks.kinit_admin(self.clients[0])
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={INVALID_SERVER}',
+ f'--bindpw={OTP}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_ROOT_DN_ERROR
+ assert ERR_UNABLE_ROOT_DN in result.stderr_text
+
+ def test_hostname_server_invalid_keytab_with_kerberos(self):
+ """Test ipa-join with hostname, invalid server, keytab."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={INVALID_SERVER}',
+ f'--keytab={TEST_KEYTAB}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_RESOLVE_ERROR
+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+
+ def test_hostname_server_invalid_keytab_bindpw_valid(self):
+ """Test ipa-join with hostname, invalid server, keytab, valid OTP."""
+ tasks.kinit_admin(self.clients[0])
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={INVALID_SERVER}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={OTP}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_ROOT_DN_ERROR
+ assert ERR_UNABLE_ROOT_DN in result.stderr_text
+
+ def test_hostname_server_valid_with_kerberos(self):
+ """Test ipa-join with hostname and valid server."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_server_valid_bindpw_invalid(self):
+ """Test ipa-join with hostname, valid server, invalid bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_hostname_server_valid_bindpw_valid(self):
+ """Test ipa-join with hostname, valid server, valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_server_valid_keytab_with_kerberos(self):
+ """Test ipa-join with hostname, valid server, keytab."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--keytab={TEST_KEYTAB}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_server_valid_keytab_bindpw_invalid(self):
+ """Test ipa-join with hostname, valid server, keytab, bad bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ # Note: Original test had "SASL Bind Failed" (capital F), checking both
+ assert "SASL Bind" in result.stderr_text
+ assert "ailed" in result.stderr_text
+
+ def test_hostname_server_valid_keytab_bindpw_valid(self):
+ """Test ipa-join with hostname, valid server, keytab, valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_keytab_only_with_kerberos(self):
+ """Test ipa-join with keytab only using Kerberos."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--keytab={TEST_KEYTAB}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_keytab_bindpw_invalid(self):
+ """Test ipa-join with keytab and invalid bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_keytab_bindpw_valid(self):
+ """Test ipa-join with keytab and valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_server_invalid_only_with_kerberos(self):
+ """Test ipa-join with invalid server only."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={INVALID_SERVER}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_RESOLVE_ERROR
+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+
+ def test_server_invalid_bindpw_valid(self):
+ """Test ipa-join with invalid server and valid OTP."""
+ tasks.kinit_admin(self.clients[0])
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={INVALID_SERVER}',
+ f'--bindpw={OTP}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_ROOT_DN_ERROR
+ assert ERR_UNABLE_ROOT_DN in result.stderr_text
+
+ def test_server_invalid_keytab_with_kerberos(self):
+ """Test ipa-join with invalid server and keytab."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={INVALID_SERVER}',
+ f'--keytab={TEST_KEYTAB}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_RESOLVE_ERROR
+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+
+ def test_server_valid_only_with_kerberos(self):
+ """Test ipa-join with valid server only."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={self.master.hostname}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_server_valid_bindpw_invalid(self):
+ """Test ipa-join with valid server and invalid bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={self.master.hostname}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_server_valid_bindpw_valid(self):
+ """Test ipa-join with valid server and valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={self.master.hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_server_valid_keytab_with_kerberos(self):
+ """Test ipa-join with valid server and keytab."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={self.master.hostname}',
+ f'--keytab={TEST_KEYTAB}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_bindpw_invalid_only(self):
+ """Test ipa-join with invalid bindpw only."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ # =========================================================================
+ # OTP (One-Time Password) tests
+ # =========================================================================
+
+ def test_otp_empty_password(self):
+ """Test ipa-join with empty OTP password (ipa_otp_1001)."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ '--bindpw=',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_ROOT_DN_ERROR
+ assert ERR_UNAUTHENTICATED_BIND in result.stderr_text
+
+ def test_otp_wrong_password(self):
+ """Test ipa-join with wrong OTP password (ipa_otp_1002)."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_otp_valid_password(self):
+ """Test ipa-join with valid OTP password (ipa_otp_1003)."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_otp_reuse_fails(self):
+ """Test that reusing the same OTP fails (ipa_otp_1004)."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+ try:
+ # First use should succeed
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ # Second use of same OTP should fail
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={OTP}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
--
2.52.0