xref: /freebsd/crypto/krb5/src/clients/ksu/t_ksu.py (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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