6 # On macOS with System Integrity Protection enabled, this script hangs
7 # in the wait_for_prop() call after starting the first kpropd process,
8 # most likely due to signal restrictions preventing the listening
9 # child from informing the parent that a full resync was processed.
11 out = subprocess.check_output(['csrutil', 'status'],
12 universal_newlines=True)
13 if 'status: enabled' in out:
14 skip_rest('iprop tests', 'System Integrity Protection is enabled')
16 # Read lines from kpropd output until we are synchronized. Error if
17 # full_expected is true and we didn't see a full propagation or vice
19 def wait_for_prop(kpropd, full_expected, expected_old, expected_new):
20 output('*** Waiting for sync from kpropd\n')
21 full_seen = sleep_seen = False
22 old_sno = new_sno = -1
24 line = kpropd.stdout.readline()
26 fail('kpropd process exited unexpectedly')
27 output('kpropd: ' + line)
29 m = re.match(r'Calling iprop_get_updates_1 \(sno=(\d+) ', line)
32 old_sno = int(m.group(1))
33 # Also record this as the new sno, in case we get back
35 new_sno = int(m.group(1))
37 m = re.match(r'Got incremental updates \(sno=(\d+) ', line)
39 new_sno = int(m.group(1))
41 if 'KDC is synchronized' in line or 'Incremental updates:' in line:
44 # After a full resync request, these lines could appear in
46 if 'Waiting for' in line:
48 if 'load process for full propagation completed' in line:
51 # Detect some failure conditions.
52 if 'Still waiting for full resync' in line:
53 fail('kadmind gave consecutive full resyncs')
54 if 'Rejected connection' in line:
55 fail('kpropd rejected kprop connection')
56 if 'get updates failed' in line:
57 fail('iprop_get_updates failed')
58 if 'permission denied' in line:
59 fail('kadmind denied update')
60 if ('error from primary' in line or
61 'error returned from primary' in line):
62 fail('kadmind reported error')
63 if 'invalid return' in line:
64 fail('kadmind returned invalid result')
66 if full_expected and not full_seen:
67 fail('Expected full dump but saw only incremental')
68 if full_seen and not full_expected:
69 fail('Expected incremental prop but saw full dump')
70 if old_sno != expected_old:
71 fail('Expected old serial %d from kpropd sync' % expected_old)
72 if new_sno != expected_new:
73 fail('Expected new serial %d from kpropd sync' % expected_new)
75 # Wait until kpropd is sleeping before continuing, to avoid races.
76 # (This is imperfect since there's there is a short window between
77 # the fprintf and the sleep; kpropd will need design changes to
80 line = kpropd.stdout.readline()
81 output('kpropd: ' + line)
82 if 'Waiting for' in line:
84 output('*** Sync complete\n')
86 # Verify the output of kproplog against the expected number of
87 # entries, first and last serial number, and a list of principal names
88 # for the update entrires.
89 def check_ulog(num, first, last, entries, env=None):
90 out = realm.run([kproplog], env=env)
91 if 'Number of entries : ' + str(num) + '\n' not in out:
92 fail('Expected %d entries' % num)
94 firststr = first and str(first) or 'None'
95 if 'First serial # : ' + firststr + '\n' not in out:
96 fail('Expected first serial number %d' % first)
97 laststr = last and str(last) or 'None'
98 if 'Last serial # : ' + laststr + '\n' not in out:
99 fail('Expected last serial number %d' % last)
100 assert(len(entries) == num)
103 for line in out.splitlines():
104 m = re.match(r'\tUpdate serial # : (\d+)$', line)
107 if m.group(1) != str(ser):
108 fail('Expected serial number %d in update entry' % ser)
109 m = re.match(r'\tUpdate principal : (.*)$', line)
111 eprinc = entries[ser - first]
113 fail('Expected dummy update entry %d' % ser)
114 elif m.group(1) != eprinc:
115 fail('Expected princ %s in update entry %d' % (eprinc, ser))
116 if line == '\tDummy entry':
117 eprinc = entries[ser - first]
119 fail('Expected princ %s in update entry %d' % (eprinc, ser))
121 # replica1 will receive updates from primary, and replica2 will
122 # receive updates from replica1. Because of the awkward way iprop and
123 # kprop port configuration currently works, we need separate config
124 # files for the replica and primary sides of replica1, but they use
125 # the same DB and ulog file.
126 conf = {'realms': {'$realm': {'iprop_enable': 'true',
127 'iprop_logfile': '$testdir/db.ulog'}}}
128 conf_rep1 = {'realms': {'$realm': {'iprop_replica_poll': '600',
129 'iprop_logfile': '$testdir/ulog.replica1'}},
130 'dbmodules': {'db': {'database_name': '$testdir/db.replica1'}}}
131 conf_rep1m = {'realms': {'$realm': {'iprop_logfile': '$testdir/ulog.replica1',
132 'iprop_port': '$port8'}},
133 'dbmodules': {'db': {'database_name': '$testdir/db.replica1'}}}
134 conf_rep2 = {'realms': {'$realm': {'iprop_replica_poll': '600',
135 'iprop_logfile': '$testdir/ulog.replica2',
136 'iprop_port': '$port8'}},
137 'dbmodules': {'db': {'database_name': '$testdir/db.replica2'}}}
139 conf_foo = {'libdefaults': {'default_realm': 'FOO'},
140 'domain_realm': {hostname: 'FOO'}}
141 conf_rep3 = {'realms': {'$realm': {'iprop_replica_poll': '600',
142 'iprop_logfile': '$testdir/ulog.replica3',
143 'iprop_port': '$port8'},
144 'FOO': {'iprop_logfile': '$testdir/ulog.replica3'}},
145 'dbmodules': {'db': {'database_name': '$testdir/db.replica3'}}}
147 krb5_conf_rep4 = {'domain_realm': {hostname: 'FOO'}}
148 conf_rep4 = {'realms': {'$realm': {'iprop_replica_poll': '600',
149 'iprop_logfile': '$testdir/ulog.replica4',
150 'iprop_port': '$port8'}},
151 'dbmodules': {'db': {'database_name': '$testdir/db.replica4'}}}
153 for realm in multidb_realms(kdc_conf=conf, create_user=False,
155 replica1 = realm.special_env('replica1', True, kdc_conf=conf_rep1)
156 replica1m = realm.special_env('replica1m', True, krb5_conf=conf_foo,
158 replica2 = realm.special_env('replica2', True, kdc_conf=conf_rep2)
160 # A default_realm and domain_realm that do not match the KDC's
161 # realm. The FOO realm iprop_logfile setting is needed to run
162 # kproplog during a replica3 test, since kproplog has no realm
164 replica3 = realm.special_env('replica3', True, krb5_conf=conf_foo,
167 # A default realm and a domain realm map that differ.
168 replica4 = realm.special_env('replica4', True, krb5_conf=krb5_conf_rep4,
171 # Define some principal names. pr3 is long enough to cause internal
172 # reallocs, but not long enough to grow the basic ulog entry size.
173 pr1 = 'wakawaka@' + realm.realm
174 pr2 = 'w@' + realm.realm
175 c = 'chocolate-flavored-school-bus'
177 pr3 = (cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + c +
180 # Create the kpropd ACL file.
181 acl_file = os.path.join(realm.testdir, 'kpropd-acl')
182 acl = open(acl_file, 'w')
183 acl.write(realm.host_princ + '\n')
186 ulog = os.path.join(realm.testdir, 'db.ulog')
187 if not os.path.exists(ulog):
188 fail('update log not created: ' + ulog)
190 # Create the principal used to authenticate kpropd to kadmind.
191 kiprop_princ = 'kiprop/' + hostname
192 realm.addprinc(kiprop_princ)
193 realm.extract_keytab(kiprop_princ, realm.keytab)
195 # Create the initial replica databases.
196 dumpfile = os.path.join(realm.testdir, 'dump')
197 realm.run([kdb5_util, 'dump', dumpfile])
198 realm.run([kdb5_util, 'load', dumpfile], replica1)
199 realm.run([kdb5_util, 'load', dumpfile], replica2)
200 realm.run([kdb5_util, '-r', realm.realm, 'load', dumpfile], replica3)
201 realm.run([kdb5_util, 'load', dumpfile], replica4)
203 # Reinitialize the primary ulog so we know exactly what to expect
205 realm.run([kproplog, '-R'])
206 check_ulog(1, 1, 1, [None])
208 # Make some changes to the primary DB.
212 realm.run([kadminl, 'modprinc', '-allow_tix', pr2])
213 realm.run([kadminl, 'modprinc', '+allow_tix', pr2])
214 check_ulog(6, 1, 6, [None, pr1, pr3, pr2, pr2, pr2])
216 # Start kpropd for replica1 and get a full dump from primary.
217 mark('propagate M->1 full')
218 kpropd1 = realm.start_kpropd(replica1, ['-d'])
219 wait_for_prop(kpropd1, True, 1, 6)
220 out = realm.run([kadminl, 'listprincs'], env=replica1)
221 if pr1 not in out or pr2 not in out or pr3 not in out:
222 fail('replica1 does not have all principals from primary')
223 check_ulog(1, 6, 6, [None], replica1)
225 # Make a change and check that it propagates incrementally.
226 mark('propagate M->1 incremental')
227 realm.run([kadminl, 'modprinc', '-allow_tix', pr2])
228 check_ulog(7, 1, 7, [None, pr1, pr3, pr2, pr2, pr2, pr2])
229 kpropd1.send_signal(signal.SIGUSR1)
230 wait_for_prop(kpropd1, False, 6, 7)
231 check_ulog(2, 6, 7, [None, pr2], replica1)
232 realm.run([kadminl, 'getprinc', pr2], env=replica1,
233 expected_msg='Attributes: DISALLOW_ALL_TIX')
235 # Start kadmind -proponly for replica1. (Use the replica1m
236 # environment which defines iprop_port to $port8.)
237 replica1_out_dump_path = os.path.join(realm.testdir, 'dump.replica1.out')
238 replica2_in_dump_path = os.path.join(realm.testdir, 'dump.replica2.in')
239 replica2_kprop_port = str(realm.portbase + 9)
240 kadmind_proponly = realm.start_server([kadmind, '-r', realm.realm,
241 '-nofork', '-proponly',
245 '-F', replica1_out_dump_path],
246 'starting...', replica1m)
248 # Test similar default_realm and domain_realm map settings with -r realm.
249 mark('propagate 1->3 full')
250 replica3_in_dump_path = os.path.join(realm.testdir, 'dump.replica3.in')
251 kpropd3 = realm.start_server([kpropd, '-d', '-D', '-r', realm.realm, '-P',
252 replica2_kprop_port, '-f',
253 replica3_in_dump_path, '-p', kdb5_util, '-a',
254 acl_file, '-A', hostname], 'ready', replica3)
255 wait_for_prop(kpropd3, True, 1, 7)
256 out = realm.run([kadminl, '-r', realm.realm, 'listprincs'], env=replica3)
257 if pr1 not in out or pr2 not in out or pr3 not in out:
258 fail('replica3 does not have all principals from replica1')
259 check_ulog(1, 7, 7, [None], env=replica3)
261 # Test an incremental propagation for the kpropd -r case.
262 mark('propagate M->1->3 incremental')
263 realm.run([kadminl, 'modprinc', '-maxlife', '20 minutes', pr1])
264 check_ulog(8, 1, 8, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1])
265 kpropd1.send_signal(signal.SIGUSR1)
266 wait_for_prop(kpropd1, False, 7, 8)
267 check_ulog(3, 6, 8, [None, pr2, pr1], replica1)
268 realm.run([kadminl, 'getprinc', pr1], env=replica1,
269 expected_msg='Maximum ticket life: 0 days 00:20:00')
270 kpropd3.send_signal(signal.SIGUSR1)
271 wait_for_prop(kpropd3, False, 7, 8)
272 check_ulog(2, 7, 8, [None, pr1], replica3)
273 realm.run([kadminl, '-r', realm.realm, 'getprinc', pr1], env=replica3,
274 expected_msg='Maximum ticket life: 0 days 00:20:00')
277 # Test dissimilar default_realm and domain_realm map settings (no
279 mark('propagate 1->4 full')
280 replica4_in_dump_path = os.path.join(realm.testdir, 'dump.replica4.in')
281 kpropd4 = realm.start_server([kpropd, '-d', '-D', '-P',
282 replica2_kprop_port, '-f',
283 replica4_in_dump_path, '-p', kdb5_util,
284 '-a', acl_file, '-A', hostname], 'ready',
286 wait_for_prop(kpropd4, True, 1, 8)
287 out = realm.run([kadminl, 'listprincs'], env=replica4)
288 if pr1 not in out or pr2 not in out or pr3 not in out:
289 fail('replica4 does not have all principals from replica1')
292 # Start kpropd for replica2. The -A option isn't needed since
293 # we're talking to the same host as primary (we specify it anyway
294 # to exercise the code), but replica2 defines iprop_port to $port8
295 # so it will talk to replica1. Get a full dump from replica1.
296 mark('propagate 1->2 full')
297 kpropd2 = realm.start_server([kpropd, '-d', '-D', '-P',
298 replica2_kprop_port, '-f',
299 replica2_in_dump_path, '-p', kdb5_util,
300 '-a', acl_file, '-A', hostname], 'ready',
302 wait_for_prop(kpropd2, True, 1, 8)
303 check_ulog(2, 7, 8, [None, pr1], replica2)
304 out = realm.run([kadminl, 'listprincs'], env=replica1)
305 if pr1 not in out or pr2 not in out or pr3 not in out:
306 fail('replica2 does not have all principals from replica1')
308 # Make another change and check that it propagates incrementally
310 mark('propagate M->1->2 incremental')
311 realm.run([kadminl, 'modprinc', '-maxrenewlife', '22 hours', pr1])
312 check_ulog(9, 1, 9, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1, pr1])
313 kpropd1.send_signal(signal.SIGUSR1)
314 wait_for_prop(kpropd1, False, 8, 9)
315 check_ulog(4, 6, 9, [None, pr2, pr1, pr1], replica1)
316 realm.run([kadminl, 'getprinc', pr1], env=replica1,
317 expected_msg='Maximum renewable life: 0 days 22:00:00\n')
318 kpropd2.send_signal(signal.SIGUSR1)
319 wait_for_prop(kpropd2, False, 8, 9)
320 check_ulog(3, 7, 9, [None, pr1, pr1], replica2)
321 realm.run([kadminl, 'getprinc', pr1], env=replica2,
322 expected_msg='Maximum renewable life: 0 days 22:00:00\n')
324 # Reset the ulog on replica1 to force a full resync from primary.
325 # The resync will use the old dump file and then propagate
326 # changes. replica2 should still be in sync with replica1 after
327 # the resync, so make sure it doesn't take a full resync.
328 mark('propagate M->1->2 full')
329 realm.run([kproplog, '-R'], replica1)
330 check_ulog(1, 1, 1, [None], replica1)
331 kpropd1.send_signal(signal.SIGUSR1)
332 wait_for_prop(kpropd1, True, 1, 9)
333 check_ulog(4, 6, 9, [None, pr2, pr1, pr1], replica1)
334 kpropd2.send_signal(signal.SIGUSR1)
335 wait_for_prop(kpropd2, False, 9, 9)
336 check_ulog(3, 7, 9, [None, pr1, pr1], replica2)
338 # Make another change and check that it propagates incrementally to
340 mark('propagate M->1->2 incremental (after reset)')
341 realm.run([kadminl, 'modprinc', '+allow_tix', pr2])
342 check_ulog(10, 1, 10, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1, pr1, pr2])
343 kpropd1.send_signal(signal.SIGUSR1)
344 wait_for_prop(kpropd1, False, 9, 10)
345 check_ulog(5, 6, 10, [None, pr2, pr1, pr1, pr2], replica1)
346 realm.run([kadminl, 'getprinc', pr2], env=replica1,
347 expected_msg='Attributes:\n')
348 kpropd2.send_signal(signal.SIGUSR1)
349 wait_for_prop(kpropd2, False, 9, 10)
350 check_ulog(4, 7, 10, [None, pr1, pr1, pr2], replica2)
351 realm.run([kadminl, 'getprinc', pr2], env=replica2,
352 expected_msg='Attributes:\n')
354 # Create a policy and check that it propagates via full resync.
355 mark('propagate M->1->2 full (new policy)')
356 realm.run([kadminl, 'addpol', '-minclasses', '2', 'testpol'])
357 check_ulog(1, 1, 1, [None])
358 kpropd1.send_signal(signal.SIGUSR1)
359 wait_for_prop(kpropd1, True, 10, 1)
360 check_ulog(1, 1, 1, [None], replica1)
361 realm.run([kadminl, 'getpol', 'testpol'], env=replica1,
362 expected_msg='Minimum number of password character classes: 2')
363 kpropd2.send_signal(signal.SIGUSR1)
364 wait_for_prop(kpropd2, True, 10, 1)
365 check_ulog(1, 1, 1, [None], replica2)
366 realm.run([kadminl, 'getpol', 'testpol'], env=replica2,
367 expected_msg='Minimum number of password character classes: 2')
369 # Modify the policy and test that it also propagates via full resync.
370 mark('propagate M->1->2 full (policy change)')
371 realm.run([kadminl, 'modpol', '-minlength', '17', 'testpol'])
372 check_ulog(1, 1, 1, [None])
373 kpropd1.send_signal(signal.SIGUSR1)
374 wait_for_prop(kpropd1, True, 1, 1)
375 check_ulog(1, 1, 1, [None], replica1)
376 realm.run([kadminl, 'getpol', 'testpol'], env=replica1,
377 expected_msg='Minimum password length: 17')
378 kpropd2.send_signal(signal.SIGUSR1)
379 wait_for_prop(kpropd2, True, 1, 1)
380 check_ulog(1, 1, 1, [None], replica2)
381 realm.run([kadminl, 'getpol', 'testpol'], env=replica2,
382 expected_msg='Minimum password length: 17')
384 # Delete the policy and test that it propagates via full resync.
385 mark('propgate M->1->2 full (policy delete)')
386 realm.run([kadminl, 'delpol', 'testpol'])
387 check_ulog(1, 1, 1, [None])
388 kpropd1.send_signal(signal.SIGUSR1)
389 wait_for_prop(kpropd1, True, 1, 1)
390 check_ulog(1, 1, 1, [None], replica1)
391 realm.run([kadminl, 'getpol', 'testpol'], env=replica1, expected_code=1,
392 expected_msg='Policy does not exist')
393 kpropd2.send_signal(signal.SIGUSR1)
394 wait_for_prop(kpropd2, True, 1, 1)
395 check_ulog(1, 1, 1, [None], replica2)
396 realm.run([kadminl, 'getpol', 'testpol'], env=replica2, expected_code=1,
397 expected_msg='Policy does not exist')
399 # Modify a principal on the primary and test that it propagates
401 mark('propagate M->1->2 incremental (after policy changes)')
402 realm.run([kadminl, 'modprinc', '-maxlife', '10 minutes', pr1])
403 check_ulog(2, 1, 2, [None, pr1])
404 kpropd1.send_signal(signal.SIGUSR1)
405 wait_for_prop(kpropd1, False, 1, 2)
406 check_ulog(2, 1, 2, [None, pr1], replica1)
407 realm.run([kadminl, 'getprinc', pr1], env=replica1,
408 expected_msg='Maximum ticket life: 0 days 00:10:00')
409 kpropd2.send_signal(signal.SIGUSR1)
410 wait_for_prop(kpropd2, False, 1, 2)
411 check_ulog(2, 1, 2, [None, pr1], replica2)
412 realm.run([kadminl, 'getprinc', pr1], env=replica2,
413 expected_msg='Maximum ticket life: 0 days 00:10:00')
415 # Delete a principal and test that it propagates incrementally.
416 mark('propagate M->1->2 incremental (princ delete)')
417 realm.run([kadminl, 'delprinc', pr3])
418 check_ulog(3, 1, 3, [None, pr1, pr3])
419 kpropd1.send_signal(signal.SIGUSR1)
420 wait_for_prop(kpropd1, False, 2, 3)
421 check_ulog(3, 1, 3, [None, pr1, pr3], replica1)
422 realm.run([kadminl, 'getprinc', pr3], env=replica1, expected_code=1,
423 expected_msg='Principal does not exist')
424 kpropd2.send_signal(signal.SIGUSR1)
425 wait_for_prop(kpropd2, False, 2, 3)
426 check_ulog(3, 1, 3, [None, pr1, pr3], replica2)
427 realm.run([kadminl, 'getprinc', pr3], env=replica2, expected_code=1,
428 expected_msg='Principal does not exist')
430 # Rename a principal and test that it propagates incrementally.
431 mark('propagate M->1->2 incremental (princ rename)')
432 renpr = "quacked@" + realm.realm
433 realm.run([kadminl, 'renprinc', pr1, renpr])
434 check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr])
435 kpropd1.send_signal(signal.SIGUSR1)
436 wait_for_prop(kpropd1, False, 3, 6)
437 check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr], replica1)
438 realm.run([kadminl, 'getprinc', pr1], env=replica1, expected_code=1,
439 expected_msg='Principal does not exist')
440 realm.run([kadminl, 'getprinc', renpr], env=replica1)
441 kpropd2.send_signal(signal.SIGUSR1)
442 wait_for_prop(kpropd2, False, 3, 6)
443 check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr], replica2)
444 realm.run([kadminl, 'getprinc', pr1], env=replica2, expected_code=1,
445 expected_msg='Principal does not exist')
446 realm.run([kadminl, 'getprinc', renpr], env=replica2)
450 # Reset the ulog on the primary to force a full resync.
451 mark('propagate M->1->2 full (ulog reset)')
452 realm.run([kproplog, '-R'])
453 check_ulog(1, 1, 1, [None])
454 kpropd1.send_signal(signal.SIGUSR1)
455 wait_for_prop(kpropd1, True, 6, 1)
456 check_ulog(1, 1, 1, [None], replica1)
457 kpropd2.send_signal(signal.SIGUSR1)
458 wait_for_prop(kpropd2, True, 6, 1)
459 check_ulog(1, 1, 1, [None], replica2)
461 # Stop the kprop daemons so we can test kpropd -t.
462 realm.stop_kpropd(kpropd1)
464 stop_daemon(kadmind_proponly)
467 # Test the case where no updates are needed.
468 out = realm.run_kpropd_once(replica1, ['-d'])
469 if 'KDC is synchronized' not in out:
470 fail('Expected synchronized from kpropd -t')
471 check_ulog(1, 1, 1, [None], replica1)
473 # Make a change on the primary and fetch it incrementally.
474 realm.run([kadminl, 'modprinc', '-maxlife', '5 minutes', pr1])
475 check_ulog(2, 1, 2, [None, pr1])
476 out = realm.run_kpropd_once(replica1, ['-d'])
477 if 'Got incremental updates (sno=2 ' not in out:
478 fail('Expected full dump and synchronized from kpropd -t')
479 check_ulog(2, 1, 2, [None, pr1], replica1)
480 realm.run([kadminl, 'getprinc', pr1], env=replica1,
481 expected_msg='Maximum ticket life: 0 days 00:05:00')
483 # Propagate a policy change via full resync.
484 realm.run([kadminl, 'addpol', '-minclasses', '3', 'testpol'])
485 check_ulog(1, 1, 1, [None])
486 out = realm.run_kpropd_once(replica1, ['-d'])
487 if ('Full propagation transfer finished' not in out or
488 'KDC is synchronized' not in out):
489 fail('Expected full dump and synchronized from kpropd -t')
490 check_ulog(1, 1, 1, [None], replica1)
491 realm.run([kadminl, 'getpol', 'testpol'], env=replica1,
492 expected_msg='Minimum number of password character classes: 3')
494 success('iprop tests')