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