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