Skip to content

Commit e4bbbc5

Browse files
Test: changes for krb_misc
Signed-off-by: Madhuri Upadhye <mupadhye@redhat.com>
1 parent 8a9e9d9 commit e4bbbc5

2 files changed

Lines changed: 273 additions & 3 deletions

File tree

sssd_test_framework/roles/kdc.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,21 +163,38 @@ def __init__(self, role: KDC, name: str) -> None:
163163
self.name: str = name
164164
"""Principal name."""
165165

166-
def add(self, *, password: str | None = "Secret123") -> KDCPrincipal:
166+
def add(
167+
self,
168+
*,
169+
password: str | None = "Secret123",
170+
requires_preauth: bool = False,
171+
args: str | None = None,
172+
) -> KDCPrincipal:
167173
"""
168174
Add a new Kerberos principal.
169175
170176
Random password is generated if ``password`` is ``None``.
171177
172178
:param password: Principal's password, defaults to 'Secret123'
173179
:type password: str | None
180+
:param requires_preauth: Add +requires_preauth flag (for clock skew tests), defaults to False
181+
:type requires_preauth: bool, optional
182+
:param args: Extra addprinc options (e.g. '+nokey'), defaults to None
183+
:type args: str | None, optional
174184
:return: Self.
175185
:rtype: KDCPrincipal
176186
"""
187+
opts: list[str] = []
188+
if requires_preauth:
189+
opts.append("+requires_preauth")
190+
if args:
191+
opts.append(args)
192+
opts_str = " ".join(opts) if opts else ""
193+
177194
if password is not None:
178-
self.role.kadmin(f'addprinc -pw "{password}" "{self.name}"')
195+
self.role.kadmin(f'addprinc -pw "{password}" {opts_str} "{self.name}"')
179196
else:
180-
self.role.kadmin(f'addprinc -randkey "{self.name}"')
197+
self.role.kadmin(f'addprinc -randkey {opts_str} "{self.name}"')
181198

182199
return self
183200

sssd_test_framework/utils/authentication.py

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,11 @@ def __init__(self, host: MultihostHost) -> None:
860860
self.opts = "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
861861
"""SSH CLI options."""
862862

863+
self.passwd: SSHPasswdUtils = SSHPasswdUtils(host)
864+
"""
865+
Change password via SSH session using passwd command.
866+
"""
867+
863868
def password_with_output(
864869
self, username: str, password: str, hostname: str = "localhost"
865870
) -> tuple[int, int, str, str]:
@@ -1076,6 +1081,177 @@ def password_expired(self, username: str, password: str, new_password: str, host
10761081
return rc == 0
10771082

10781083

1084+
class SSHPasswdUtils(MultihostUtility[MultihostHost]):
1085+
"""
1086+
Change password via SSH session using passwd command.
1087+
1088+
SSHs as user, logs in, runs ``passwd`` interactively, and
1089+
changes the password. Used when testing krb5_child
1090+
"Initial authentication for change password".
1091+
"""
1092+
1093+
def __init__(self, host: MultihostHost) -> None:
1094+
"""
1095+
:param host: Multihost host.
1096+
:type host: MultihostHost
1097+
"""
1098+
super().__init__(host)
1099+
1100+
self.opts = (
1101+
"-o UserKnownHostsFile=/dev/null"
1102+
" -o StrictHostKeyChecking=no"
1103+
)
1104+
1105+
def password_with_output(
1106+
self,
1107+
username: str,
1108+
password: str,
1109+
new_password: str,
1110+
retyped: str | None = None,
1111+
*,
1112+
hostname: str = "localhost",
1113+
) -> tuple[int, int, str, str]:
1114+
"""
1115+
SSH to host, run passwd, and change password.
1116+
1117+
:param username: Username.
1118+
:type username: str
1119+
:param password: Current password.
1120+
:type password: str
1121+
:param new_password: New password.
1122+
:type new_password: str
1123+
:param retyped: Retyped new password (defaults to new_password).
1124+
:type retyped: str | None, optional
1125+
:param hostname: SSH target host, defaults to "localhost".
1126+
:type hostname: str
1127+
:return: Tuple containing [return code, command code, stdout, stderr].
1128+
:rtype: Tuple[int, int, str, str]
1129+
"""
1130+
if retyped is None:
1131+
retyped = new_password
1132+
1133+
result = self.host.conn.expect_nobody(
1134+
rf"""
1135+
exp_internal 0
1136+
1137+
proc exitmsg {{ msg code }} {{
1138+
catch close
1139+
lassign [wait] pid spawnid os_error_flag rc
1140+
1141+
puts ""
1142+
puts "expect result: $msg"
1143+
puts "expect exit code: $code"
1144+
puts "expect spawn exit code: $rc"
1145+
exit $code
1146+
}}
1147+
1148+
set timeout {DEFAULT_AUTHENTICATION_TIMEOUT}
1149+
set prompt "\n.*\[#\$>\] $"
1150+
1151+
spawn ssh {self.opts} \
1152+
-o PreferredAuthentications=password \
1153+
-o NumberOfPasswordPrompts=1 \
1154+
-l "{username}" "{hostname}"
1155+
1156+
expect {{
1157+
"password:" {{send "{password}\n"}}
1158+
timeout {{exitmsg "Unexpected output" 201}}
1159+
eof {{exitmsg "Unexpected end of file" 202}}
1160+
}}
1161+
1162+
expect {{
1163+
-re $prompt {{}}
1164+
timeout {{exitmsg "Unexpected output" 201}}
1165+
eof {{exitmsg "Unexpected end of file" 202}}
1166+
}}
1167+
1168+
send "passwd\r"
1169+
1170+
expect {{
1171+
-nocase "Current Password:" {{send "{password}\n"}}
1172+
timeout {{exitmsg "Unexpected output" 201}}
1173+
eof {{exitmsg "Unexpected end of file" 202}}
1174+
}}
1175+
1176+
expect {{
1177+
-nocase "New password:" {{send "{new_password}\n"}}
1178+
timeout {{exitmsg "Unexpected output" 201}}
1179+
eof {{exitmsg "Unexpected end of file" 202}}
1180+
}}
1181+
1182+
expect {{
1183+
-nocase "Retype new password:" {{send "{retyped}\n"}}
1184+
timeout {{exitmsg "Unexpected output" 201}}
1185+
eof {{exitmsg "Unexpected end of file" 202}}
1186+
}}
1187+
1188+
expect {{
1189+
-re "passwd: .+ updated successfully" {{
1190+
send "exit\r"
1191+
expect eof
1192+
exitmsg "Password change was successful" 0
1193+
}}
1194+
"Sorry, passwords do not match." {{
1195+
exitmsg "Passwords do not match" 1
1196+
}}
1197+
"Password change failed." {{
1198+
exitmsg "Password change failed" 1
1199+
}}
1200+
timeout {{exitmsg "Unexpected output" 201}}
1201+
eof {{exitmsg "Unexpected end of file" 202}}
1202+
}}
1203+
1204+
exitmsg "Unexpected code path" 203
1205+
""",
1206+
verbose=False,
1207+
)
1208+
1209+
if result.rc > 200:
1210+
raise ExpectScriptError(result.rc)
1211+
1212+
expect_data = result.stdout_lines[-3:]
1213+
1214+
# Get command exit code.
1215+
cmdrc = int(expect_data[2].split(":")[1].strip())
1216+
1217+
# Alter stdout, first line is spawned command,
1218+
# the last three are our expect output.
1219+
stdout = "\n".join(result.stdout_lines[1:-3])
1220+
1221+
return result.rc, cmdrc, stdout, result.stderr
1222+
1223+
def password(
1224+
self,
1225+
username: str,
1226+
password: str,
1227+
new_password: str,
1228+
retyped: str | None = None,
1229+
*,
1230+
hostname: str = "localhost",
1231+
) -> bool:
1232+
"""
1233+
SSH to host, run passwd, and change password.
1234+
1235+
:param username: Username.
1236+
:type username: str
1237+
:param password: Current password.
1238+
:type password: str
1239+
:param new_password: New password.
1240+
:type new_password: str
1241+
:param retyped: Retyped new password (defaults to new_password).
1242+
:type retyped: str | None, optional
1243+
:param hostname: SSH target host, defaults to "localhost".
1244+
:type hostname: str
1245+
:return: True if password change succeeded, False otherwise.
1246+
:rtype: bool
1247+
"""
1248+
rc, _, _, _ = self.password_with_output(
1249+
username, password, new_password, retyped,
1250+
hostname=hostname,
1251+
)
1252+
return rc == 0
1253+
1254+
10791255
class SudoAuthenticationUtils(MultihostUtility[MultihostHost]):
10801256
"""
10811257
Methods for testing authentication and authorization via sudo.
@@ -1510,6 +1686,83 @@ def list_tgt_times(self, realm: str) -> tuple[datetime, datetime]:
15101686

15111687
raise Exception("TGT was not found")
15121688

1689+
def ktutil_create_mixed_keytab(
1690+
self,
1691+
wrong_principal: str,
1692+
valid_keytab: str,
1693+
output_keytab: str,
1694+
password: str = "Secret123",
1695+
*,
1696+
raise_on_error: bool = True,
1697+
) -> ProcessResult:
1698+
"""
1699+
Create keytab with wrong principal first, then entries from valid keytab.
1700+
1701+
BZ 805281: Uses ktutil to add a password-based entry (wrong realm) first,
1702+
then merge with an existing keytab. Tests that SSSD selects the correct
1703+
principal when multiple realms exist in one keytab.
1704+
1705+
:param wrong_principal: Principal to add first (e.g. nfs/host@TEST.EXAMPLE.COM)
1706+
:param valid_keytab: Path to keytab with correct principal
1707+
:param output_keytab: Path for the combined keytab output
1708+
:param password: Password for addent -password, defaults to "Secret123"
1709+
:param raise_on_error: Raise on failure, defaults to True
1710+
:return: Process result from expect
1711+
"""
1712+
return self.host.conn.expect(
1713+
f"""
1714+
spawn ktutil
1715+
expect "ktutil: "
1716+
send "addent -password -p {wrong_principal} -k 3 -e rc4-hmac\\r"
1717+
expect "Password: *"
1718+
send "{password}\\r"
1719+
send "rkt {valid_keytab}\\r"
1720+
send "wkt {output_keytab}\\r"
1721+
expect eof
1722+
""",
1723+
raise_on_error=raise_on_error,
1724+
)
1725+
1726+
def ktutil_create_keytab(
1727+
self,
1728+
principal: str,
1729+
output_keytab: str,
1730+
password: str = "Secret123",
1731+
enctype: str = "aes256-cts-hmac-sha1-96",
1732+
kvno: int = 1,
1733+
*,
1734+
raise_on_error: bool = True,
1735+
) -> ProcessResult:
1736+
"""
1737+
Create keytab with single password-based entry (BZ 1198478).
1738+
1739+
Uses ktutil to add a principal with password and write to keytab file.
1740+
Useful for dummy keytabs (principal in keytab but not on KDC).
1741+
1742+
:param principal: Principal name (e.g. bla@EXAMPLE.COM)
1743+
:param output_keytab: Path for the keytab output
1744+
:param password: Password for addent -password, defaults to "Secret123"
1745+
:param enctype: Encryption type (default: aes256-cts-hmac-sha1-96)
1746+
:param kvno: Key version number, defaults to 1
1747+
:param raise_on_error: Raise on failure, defaults to True
1748+
:return: Process result from expect
1749+
"""
1750+
return self.host.conn.expect(
1751+
f"""
1752+
spawn ktutil
1753+
expect "ktutil: "
1754+
send "addent -password -p {principal} -k {kvno} -e {enctype}\\r"
1755+
expect "Password: *"
1756+
send "{password}\\r"
1757+
expect "ktutil: "
1758+
send "wkt {output_keytab}\\r"
1759+
expect "ktutil: "
1760+
send "q\\r"
1761+
expect eof
1762+
""",
1763+
raise_on_error=raise_on_error,
1764+
)
1765+
15131766
def __enter__(self) -> KerberosAuthenticationUtils:
15141767
"""
15151768
Connect to the host over ssh if not already connected.

0 commit comments

Comments
 (0)