1from k5test import * 2import pwd 3import stat 4 5krb5_conf = '/etc/krb5.conf' 6krb5_conf_save = krb5_conf + '.save-ksutest' 7krb5_conf_nosave = krb5_conf + '.nosave-ksutest' 8ksu = './ksu.ksutest' 9if 'SUDO_UID' not in os.environ or os.geteuid() != 0: 10 fail('this script must be run as root via sudo') 11caller_uid = int(os.environ['SUDO_UID']) 12if caller_uid == 0: 13 fail('the user invoking sudo must not be root') 14caller_username = os.environ['SUDO_USER'] 15os.chown('testlog', caller_uid, -1) 16 17# Set the real and effective UIDs to the calling user, but preserve 18# the ability to restore root privileges. 19def be_caller(): 20 os.setresuid(caller_uid, caller_uid, 0) 21 22 23# Restore root privileges. 24def be_root(): 25 os.setresuid(0, 0, 0) 26 27 28# Remove the ksutest account. 29def cleanup_user(): 30 # userdel commonly gives a warning about being unable to delete 31 # the mail spool; filter it out. 32 out = subprocess.check_output(['userdel', '-r', 'ksutest'], 33 stderr=subprocess.STDOUT) 34 if out.count(b'\n') > 1 or b'ksutest mail spool' not in out: 35 print(out) 36 37 38# Restore /etc/krb5.conf to the state it was in previously. 39def cleanup_krb5_conf(): 40 if os.path.exists(krb5_conf_save): 41 os.unlink(krb5_conf) 42 os.rename(krb5_conf_save, krb5_conf) 43 elif os.path.exists(krb5_conf_nosave): 44 os.unlink(krb5_conf) 45 os.unlink(krb5_conf_nosave) 46 47 48def onexit(): 49 if len(sys.argv) >= 2 and sys.argv[1] == 'nocleanup': 50 return 51 be_root() 52 cleanup_user() 53 cleanup_krb5_conf() 54 if os.path.exists(ksu): 55 os.unlink(ksu) 56 57 58# Create a ksutest account and return its home directory. 59def setup_user(): 60 try: 61 ent = pwd.getpwnam('ksutest') 62 return ent.pw_dir 63 except KeyError: 64 subprocess.check_call(['useradd', '-m', '-r', 'ksutest']) 65 return pwd.getpwnam('ksutest').pw_dir 66 67 68# Make krb5.conf a copy of realm's krb5.conf file. Save the old 69# contents in krb5_conf_save, or create krb5_conf_noexist to indicate 70# that the file didn't previously exist. 71def setup_krb5_conf(realm): 72 if not os.path.exists(krb5_conf): 73 open(krb5_conf_nosave, 'w').close() 74 elif not os.path.exists(krb5_conf_save): 75 os.rename(krb5_conf, krb5_conf_save) 76 shutil.copyfile(os.path.join(realm.testdir, 'krb5.conf'), krb5_conf) 77 78 79# Temporarily acting as root, write a file named fname in ksutest's 80# home directory with the given contents. If wrong_owner is set, make 81# the file owned by the caller uid in order to trip ksu's owner check. 82def write_authz_file(fname, contents, wrong_owner=False): 83 be_root() 84 path = os.path.join(ksutest_home, fname) 85 with open(path, 'w') as f: 86 f.write('\n'.join(contents) + '\n') 87 if wrong_owner: 88 os.chown(path, caller_uid, -1) 89 be_caller() 90 91 92# Temporarily acting as root, remove fname from ksutest's home 93# directory. 94def remove_authz_file(fname): 95 be_root() 96 path = os.path.join(ksutest_home, fname) 97 if os.path.exists(path): 98 os.remove(path) 99 be_caller() 100 101 102be_caller() 103 104# Set up a realm. Set default_keytab_name since ksu won't respect the 105# KRB5_KTNAME environment variable. 106keytab = os.path.join(os.getcwd(), 'testdir', 'keytab') 107realm = K5Realm(create_user=False, 108 krb5_conf={'libdefaults': {'default_keytab_name': keytab}}) 109realm.addprinc('alice', 'pwalice') 110realm.addprinc('ksutest', 'pwksutest') 111realm.addprinc('ksutest/root', 'pwroot') 112realm.addprinc(caller_username, 'pwcaller') 113 114# Root setup: 115# - /etc/krb5.conf is a copy of the test realm krb5.conf 116# - a newly created user named ksutest exists (with homedir ksutest_home) 117# - a setuid copy of ksu exists in the build dir 118# Register an atexit handler to undo these changes. 119atexit.register(onexit) 120be_root() 121ksutest_home = setup_user() 122setup_krb5_conf(realm) 123if os.path.exists(ksu): 124 os.unlink(ksu) 125shutil.copyfile('ksu', ksu) 126os.chmod(ksu, 0o4755) 127be_caller() 128 129mark('no authorization') 130realm.kinit('alice', 'pwalice') 131realm.run([ksu, 'ksutest', '-n', 'alice', '-a', '-c', klist], expected_code=1, 132 expected_msg='authorization of alice@KRBTEST.COM failed') 133 134mark('an2ln authorization') 135realm.kinit('ksutest', 'pwksutest') 136realm.run([ksu, 'ksutest', '-a', '-c', klist], 137 expected_msg='authorization for ksutest@KRBTEST.COM successful') 138realm.run([ksu, 'ksutest', '-e', klist], expected_code=1, 139 expected_msg='account ksutest: authorization failed') 140 141mark('.k5login wrong owner') 142write_authz_file('.k5login', ['ksutest@KRBTEST.COM'], wrong_owner=True) 143realm.run([ksu, 'ksutest', '-e', klist], expected_code=1, 144 expected_msg='account ksutest: authorization failed') 145remove_authz_file('.k5login') 146 147mark('.k5users wrong owner') 148write_authz_file('.k5users', ['ksutest@KRBTEST.COM'], wrong_owner=True) 149realm.run([ksu, 'ksutest', '-e', klist], expected_code=1, 150 expected_msg='account ksutest: authorization failed') 151remove_authz_file('.k5users') 152 153mark('.k5login authorization') 154realm.kinit('alice', 'pwalice') 155write_authz_file('.k5login', ['alice@KRBTEST.COM']) 156realm.run([ksu, 'ksutest', '-a', '-c', klist], 157 expected_msg='authorization for alice@KRBTEST.COM successful') 158realm.run([ksu, 'ksutest', '-e', klist], 159 expected_msg='authorization for alice@KRBTEST.COM for execution of') 160write_authz_file('.k5login', ['bob@KRBTEST.COM']) 161realm.run([ksu, 'ksutest', '-a', '-c', klist], expected_code=1, 162 expected_msg='account ksutest: authorization failed') 163remove_authz_file('.k5login') 164 165mark('.k5users authorization (no second field)') 166write_authz_file('.k5users', ['alice@KRBTEST.COM']) 167realm.run([ksu, 'ksutest', '-a', '-c', klist], 168 expected_msg='authorization for alice@KRBTEST.COM successful') 169realm.run([ksu, 'ksutest', '-e', klist], expected_code=1, 170 expected_msg='account ksutest: authorization failed') 171write_authz_file('.k5users', ['bob@KRBTEST.COM']) 172realm.run([ksu, 'ksutest', '-a', '-c', klist], expected_code=1, 173 expected_msg='account ksutest: authorization failed') 174 175mark('k5users authorization (wildcard)') 176write_authz_file('.k5users', ['alice@KRBTEST.COM *']) 177realm.run([ksu, 'ksutest', '-a', '-c', klist], 178 expected_msg='authorization for alice@KRBTEST.COM successful') 179realm.run([ksu, 'ksutest', '-e', klist], 180 expected_msg='authorization for alice@KRBTEST.COM for execution of') 181 182mark('k5users authorization (command list)') 183write_authz_file('.k5users', ['alice@KRBTEST.COM doesnotexist ' + klist]) 184realm.run([ksu, 'ksutest', '-a', '-c', klist], expected_code=1, 185 expected_msg='account ksutest: authorization failed') 186realm.run([ksu, 'ksutest', '-e', klist], 187 expected_msg='authorization for alice@KRBTEST.COM for execution of') 188realm.run([ksu, 'ksutest', '-e', kvno], expected_code=1, 189 expected_msg='account ksutest: authorization failed') 190realm.run([ksu, 'ksutest', '-e', 'doesnotexist'], expected_code=1, 191 expected_msg='Error: not found ->') 192remove_authz_file('.k5users') 193 194mark('principal heuristic (no authz files)') 195realm.run([ksu, 'ksutest', '-a', '-c', klist], input='pwksutest\n', 196 expected_msg='Authenticated ksutest@KRBTEST.COM') 197realm.run([ksu, 'ksutest', '-e', klist], expected_code=1, 198 expected_msg='account ksutest: authorization failed') 199 200mark('principal heuristic (empty authz files)') 201write_authz_file('.k5login', []) 202write_authz_file('.k5users', []) 203realm.run([ksu, 'ksutest', '-e', klist], expected_code=1, 204 expected_msg='account ksutest: authorization failed') 205remove_authz_file('.k5login') 206remove_authz_file('.k5users') 207 208# Untested: if the ccache default principal is not authorized, 209# get_best_princ_for_target() looks for a TGT or host service ticket 210# for the target and source users (if authorized) or any other 211# authorized user. This is not really useful because a ccache usually 212# only contains tickets for its default client principal (aside from 213# caches created for S4U2Proxy). If the heuristic is ever changed to 214# search the cache collection instead of only the primary cache, we 215# should add tests for that here. 216 217mark('principal heuristic (.k5login)') 218write_authz_file('.k5login', ['ksutest@KRBTEST.COM']) 219realm.run([ksu, 'ksutest', '-a', '-c', klist], input='pwksutest\n', 220 expected_msg='Authenticated ksutest@KRBTEST.COM') 221realm.run([ksu, 'ksutest', '-e', klist], input='pwksutest\n', 222 expected_msg='Authenticated ksutest@KRBTEST.COM') 223write_authz_file('.k5login', [caller_username + '@KRBTEST.COM']) 224realm.run([ksu, 'ksutest', '-e', klist], input='pwcaller\n', 225 expected_msg='Authenticated %s@KRBTEST.COM' % caller_username) 226remove_authz_file('.k5login') 227 228mark('principal heuristic (.k5users)') 229write_authz_file('.k5users', ['alice@KRBTEST.COM ' + klist, 230 'ksutest@KRBTEST.COM', 231 caller_username + '@KRBTEST.COM *']) 232realm.run([ksu, 'ksutest', '-e', klist], 233 expected_msg='Authenticated alice@KRBTEST.COM') 234realm.run([ksu, 'ksutest', '-a', '-c', klist], input='pwksutest\n', 235 expected_msg='Authenticated ksutest@KRBTEST.COM') 236realm.run([ksu, 'ksutest', '-e', kvno, 'alice'], input='pwcaller\n', 237 expected_msg='Authenticated %s@KRBTEST.COM' % caller_username) 238write_authz_file('.k5users', ['alice@KRBTEST.COM ' + klist, 239 'ksutest/root@KRBTEST.COM ' + kvno]) 240realm.run([ksu, 'ksutest', '-e', kvno, 'alice'], input='pwroot\n', 241 expected_msg='Authenticated ksutest/root@KRBTEST.COM') 242 243mark('principal heuristic (no authorization)') 244realm.run([ksu, '.', '-e', klist], 245 expected_msg='Default principal: alice@KRBTEST.COM') 246be_root() 247realm.run([ksu, 'ksutest', '-e', klist], expected_code=1, 248 expected_msg='No credentials cache found') 249be_caller() 250realm.kinit('ksutest', 'pwksutest') 251be_root() 252realm.run([ksu, 'ksutest', '-e', klist], 253 expected_msg='Default principal: ksutest@KRBTEST.COM') 254be_caller() 255realm.run([kdestroy]) 256realm.run([ksu, '.', '-e', klist], expected_code=1, 257 expected_msg='No credentials cache found') 258 259mark('authentication without authorization') 260realm.run([ksu, '.', '-n', 'ksutest', '-e', klist], input='pwksutest\n', 261 expected_msg='Leaving uid as ' + caller_username) 262 263# It's hard to make this flag do anything detectable, but we can 264# exercise the code. 265mark('-z flag') 266realm.kinit(caller_username, 'pwcaller') 267realm.run([ksu, '.', '-z', '-e', klist], 268 expected_msg='Default principal: ' + caller_username) 269 270realm.run([ksu, '.', '-Z', '-e', klist], expected_code=1, 271 expected_msg='No credentials cache found') 272 273success('ksu tests') 274