# # Copyright (C) 2019,2020 FreeIPA Contributors see COPYING for license # """ Module provides tests for Kerberos ticket policy options """ from __future__ import absolute_import import pytest import time from datetime import datetime from ipalib.constants import IPAAPI_USER from ipaplatform.paths import paths from ipatests.test_integration.base import IntegrationTest from ipatests.test_integration.test_otp import add_otptoken, del_otptoken from ipatests.pytest_ipa.integration import tasks PASSWORD = "Secret123" USER1 = "testuser1" USER2 = "testuser2" MAXLIFE = 86400 def maxlife_within_policy(input, maxlife, slush=3600): """Given klist output of the TGT verify that it is within policy Ensure that the validity period is somewhere within the absolute maxlife and a slush value, maxlife - slush. Returns True if within policy. Input should be a string like: 11/19/2019 16:37:40 11/20/2019 16:37:39 krbtgt/... slush defaults to 1 * 60 * 60 matching the jitter window. """ data = input.split() start = datetime.strptime(data[0] + ' ' + data[1], '%m/%d/%Y %H:%M:%S') end = datetime.strptime(data[2] + ' ' + data[3], '%m/%d/%Y %H:%M:%S') diff = int((end - start).total_seconds()) return maxlife >= diff >= maxlife - slush @pytest.fixture def reset_to_default_policy(): """Reset default user authentication and user authentication type""" state = dict() def _reset_to_default_policy(host, user=None): state['host'] = host state['user'] = user yield _reset_to_default_policy host = state['host'] user = state['user'] tasks.kinit_admin(host) host.run_command(['ipa', 'krbtpolicy-reset']) if user: host.run_command(['ipa', 'user-mod', user, '--user-auth-type=']) host.run_command(['ipa', 'krbtpolicy-reset', user]) def kinit_check_life(master, user): """Acquire a TGT and check if it's within the lifetime window""" master.run_command(["kinit", user], stdin_text=f"{PASSWORD}\n") result = master.run_command("klist | grep krbtgt") assert maxlife_within_policy(result.stdout_text, MAXLIFE) is True class TestPWPolicy(IntegrationTest): """Tests password custom and default password policies. """ num_replicas = 0 @classmethod def install(cls, mh): tasks.install_master(cls.master) tasks.create_active_user(cls.master, USER1, PASSWORD) tasks.create_active_user(cls.master, USER2, PASSWORD) @pytest.fixture(autouse=True, scope="function") def with_admin(self): tasks.kinit_admin(self.master) yield tasks.kdestroy_all(self.master) def test_krbtpolicy_default(self): """Test the default kerberos ticket policy 24-hr tickets""" master = self.master master.run_command(['ipa', 'krbtpolicy-mod', USER1, '--maxlife', str(MAXLIFE)]) tasks.kdestroy_all(master) master.run_command(['kinit', USER1], stdin_text=PASSWORD + '\n') result = master.run_command('klist | grep krbtgt') assert maxlife_within_policy(result.stdout_text, MAXLIFE) is True def test_krbtpolicy_hardended(self): """Test a hardened kerberos ticket policy with 10 min tickets""" master = self.master master.run_command(['ipa', 'user-mod', USER1, '--user-auth-type', 'password', '--user-auth-type', 'hardened']) master.run_command(['ipa', 'config-mod', '--user-auth-type', 'password', '--user-auth-type', 'hardened']) master.run_command(['ipa', 'krbtpolicy-mod', USER1, '--hardened-maxlife', '600']) tasks.kdestroy_all(master) master.run_command(['kinit', USER1], stdin_text=PASSWORD + '\n') result = master.run_command('klist | grep krbtgt') assert maxlife_within_policy(result.stdout_text, 600, slush=600) is True tasks.kdestroy_all(master) # Verify that the short policy only applies to USER1 master.run_command(['kinit', USER2], stdin_text=PASSWORD + '\n') result = master.run_command('klist | grep krbtgt') assert maxlife_within_policy(result.stdout_text, MAXLIFE) is True def test_krbtpolicy_password(self): """Test the kerberos ticket policy which issues 20 min tickets""" master = self.master master.run_command(['ipa', 'krbtpolicy-mod', USER2, '--maxlife', '1200']) tasks.kdestroy_all(master) master.run_command(['kinit', USER2], stdin_text=PASSWORD + '\n') result = master.run_command('klist | grep krbtgt') assert maxlife_within_policy(result.stdout_text, 1200, slush=1200) is True def test_krbtpolicy_reset(self): """Test a hardened kerberos ticket policy reset""" master = self.master master.run_command(['ipa', 'krbtpolicy-reset', USER2]) master.run_command(['kinit', USER2], stdin_text=PASSWORD + '\n') result = master.run_command('klist | grep krbtgt') assert maxlife_within_policy(result.stdout_text, MAXLIFE) is True def test_krbtpolicy_otp(self, reset_to_default_policy): """Test otp ticket policy""" master = self.master master.run_command(['ipa', 'user-mod', USER1, '--user-auth-type', 'otp']) master.run_command(['ipa', 'config-mod', '--user-auth-type', 'otp']) master.run_command(['ipa', 'krbtpolicy-mod', USER1, '--otp-maxrenew=90', '--otp-maxlife=60']) armor = tasks.create_temp_file(self.master, create_file=False) otpuid, totp = add_otptoken(master, USER1, otptype="totp") otpvalue = totp.generate(int(time.time())).decode("ascii") reset_to_default_policy(master, USER1) try: tasks.kdestroy_all(master) # create armor for FAST master.run_command(['kinit', '-n', '-c', armor]) # expect ticket expire in otp-maxlife=60 seconds master.run_command( ['kinit', '-T', armor, USER1, '-r', '90'], stdin_text='{0}{1}\n'.format(PASSWORD, otpvalue)) master.run_command(['ipa', 'user-find', USER1]) time.sleep(30) # when user kerberos ticket expired but still within renew time, # kinit -R should give user new life master.run_command(['kinit', '-R', USER1]) master.run_command(['ipa', 'user-find', USER1]) time.sleep(60) # when renew time expires, kinit -R should fail result1 = master.run_command(['kinit', '-R', USER1], raiseonerr=False) tasks.assert_error( result1, "kinit: Ticket expired while renewing credentials", 1) master.run_command(['ipa', 'user-find', USER1], ok_returncode=1) finally: del_otptoken(master, otpuid) self.master.run_command(['rm', '-f', armor]) master.run_command(['ipa', 'config-mod', '--user-auth-type=']) def test_krbtpolicy_jitter(self): """Test jitter lifetime with no auth indicators""" kinit_check_life(self.master, USER1) def test_krbtpolicy_jitter_otp(self, reset_to_default_policy): """Test jitter lifetime with OTP""" reset_to_default_policy(self.master, USER1) self.master.run_command(["ipa", "user-mod", USER1, "--user-auth-type", "otp"]) kinit_check_life(self.master, USER1) def test_ccache_sweep_expired(self, reset_to_default_policy): """Test that the ccache sweeper works on expired ccaches - Force wipe all existing ccaches - Set the ticket policy to a short value, 20 seconds. - Do a series of kinit, ipa command, kdestroy to generate ccaches - sleep() for expiration - Run the sweeper - Verify that all expired ccaches are gone """ MAXLIFE = 20 reset_to_default_policy(self.master) # this will reset at END of test tasks.kinit_admin(self.master) self.master.run_command( ['ipa', 'krbtpolicy-mod', '--maxlife', str(MAXLIFE)] ) tasks.kdestroy_all(self.master) self.master.run_command( ['find', paths.IPA_CCACHES, '-type', 'f', '-delete'] ) for _i in range(5): tasks.kinit_admin(self.master) self.master.run_command(['ipa', 'user-show', 'admin']) tasks.kdestroy_all(self.master) result = self.master.run_command( "ls -1 {0} | wc -l".format(paths.IPA_CCACHES) ) assert int(result.stdout_text.strip()) == 5 # let ccache expire time.sleep(MAXLIFE) ccache_sweep_cmd = ["/usr/libexec/ipa/ipa-ccache-sweeper", "-m", "0"] # should be run as ipaapi for GSSProxy self.master.run_command( ["runuser", "-u", IPAAPI_USER, "--"] + ccache_sweep_cmd ) result = self.master.run_command( "ls -1 {0} | wc -l".format(paths.IPA_CCACHES) ) assert int(result.stdout_text.strip()) == 0 def test_ccache_sweep_valid(self): """Test that the ccache sweeper doesn't remove valid ccaches - Force wipe all existing ccaches - Run the sweeper - Verify that all valid ccaches weren't removed Note: assumed that ccache expiration doesn't happen during test """ tasks.kdestroy_all(self.master) self.master.run_command( ["find", paths.IPA_CCACHES, "-type", "f", "-delete"] ) for _i in range(5): tasks.kinit_admin(self.master) self.master.run_command(["ipa", "user-show", "admin"]) tasks.kdestroy_all(self.master) result = self.master.run_command( "ls -1 {0} | wc -l".format(paths.IPA_CCACHES) ) assert int(result.stdout_text.strip()) == 5 ccache_sweep_cmd = ["/usr/libexec/ipa/ipa-ccache-sweeper", "-m", "0"] # should be run as ipaapi for GSSProxy self.master.run_command( ["runuser", "-u", IPAAPI_USER, "--"] + ccache_sweep_cmd ) result = self.master.run_command( "ls -1 {0} | wc -l".format(paths.IPA_CCACHES) ) assert int(result.stdout_text.strip()) == 5