def test_dsidm_service_delete(topology_st, create_test_service):
    """ Test dsidm service delete option

    :id: 3b382a96-51e1-11ec-a1c2-3497f624ea11
    :setup: Standalone instance
    :steps:
         1. Run dsidm service delete on created service
         2. Check that a message is provided on deletion
         3. Check that service does not exist
    :expectedresults:
         1. Success
         2. Success
         3. Success
    """

    standalone = topology_st.standalone
    services = ServiceAccounts(standalone, DEFAULT_SUFFIX)
    test_service = services.get('test_service')
    output = f'Successfully deleted {test_service.dn}'

    args = FakeArgs()
    args.dn = test_service.dn

    log.info('Test dsidm service delete')
    delete(standalone,
           DEFAULT_SUFFIX,
           topology_st.logcap.log,
           args,
           warn=False)
    check_value_in_log_and_reset(topology_st, check_value=output)

    log.info('Check that service does not exist')
    assert not test_service.exists()
def test_dsidm_service_create(topology_st):
    """ Test dsidm service create option

    :id: 338efbc6-51e1-11ec-a83a-3497f624ea11
    :setup: Standalone instance
    :steps:
         1. Run dsidm service create
         2. Check that a message is provided on creation
         3. Check that created service exists
    :expectedresults:
         1. Success
         2. Success
         3. Success
    """

    standalone = topology_st.standalone
    service_name = 'new_service'
    output = f'Successfully created {service_name}'

    args = FakeArgs()
    args.cn = service_name
    args.description = service_name

    log.info('Test dsidm service create')
    create(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
    check_value_in_log_and_reset(topology_st, check_value=output)

    log.info('Check that service is present')
    services = ServiceAccounts(standalone, DEFAULT_SUFFIX)
    new_service = services.get(service_name)
    assert new_service.exists()

    log.info('Clean up for next test')
    new_service.delete()
def test_dsidm_service_list(topology_st, create_test_service):
    """ Test dsidm service list option

    :id: 218aa060-51e1-11ec-8a70-3497f624ea11
    :setup: Standalone instance
    :steps:
         1. Run dsidm service list option without json
         2. Check the output content is correct
         3. Run dsidm service list option with json
         4. Check the json content is correct
         5. Delete the service
         6. Check the service is not in the list with json
         7. Check the service is not in the list without json
    :expectedresults:
         1. Success
         2. Success
         3. Success
         4. Success
         5. Success
         6. Success
         7. Success
    """

    standalone = topology_st.standalone
    args = FakeArgs()
    args.json = False
    service_value = 'test_service'
    json_list = ['type', 'list', 'items']

    log.info('Empty the log file to prevent false data to check about service')
    topology_st.logcap.flush()

    log.info('Test dsidm service list without json')
    list(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
    check_value_in_log_and_reset(topology_st, check_value=service_value)

    log.info('Test dsidm service list with json')
    args.json = True
    list(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
    check_value_in_log_and_reset(topology_st,
                                 content_list=json_list,
                                 check_value=service_value)

    log.info('Delete the service')
    services = ServiceAccounts(topology_st.standalone, DEFAULT_SUFFIX)
    testservice = services.get(service_value)
    testservice.delete()

    log.info('Test empty dsidm service list with json')
    list(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
    check_value_in_log_and_reset(topology_st,
                                 content_list=json_list,
                                 check_value_not=service_value)

    log.info('Test empty dsidm service list without json')
    args.json = False
    list(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
    check_value_in_log_and_reset(topology_st, check_value_not=service_value)
def test_dsidm_service_modify(topology_st, create_test_service):
    """ Test dsidm service modify add, replace, delete option

    :id: 4023ef22-51e1-11ec-93c5-3497f624ea11
    :setup: Standalone instance
    :steps:
         1. Run dsidm service modify replace description value
         2. Run dsidm service modify add seeAlso attribute to service
         3. Run dsidm service modify delete for seeAlso attribute
    :expectedresults:
         1. description value is replaced with new text
         2. seeAlso attribute is present
         3. seeAlso attribute is deleted
    """

    standalone = topology_st.standalone
    services = ServiceAccounts(standalone, DEFAULT_SUFFIX)
    test_service = services.get('test_service')
    output = f'Successfully modified {test_service.dn}'

    args = FakeArgs()
    args.selector = 'test_service'
    args.changes = ['replace:description:Test Service Modified']

    log.info('Test dsidm service modify replace')
    modify(standalone,
           DEFAULT_SUFFIX,
           topology_st.logcap.log,
           args,
           warn=False)
    check_value_in_log_and_reset(topology_st, check_value=output)

    log.info('Test dsidm service modify add')
    args.changes = [f'add:seeAlso:ou=services,{DEFAULT_SUFFIX}']
    modify(standalone,
           DEFAULT_SUFFIX,
           topology_st.logcap.log,
           args,
           warn=False)
    check_value_in_log_and_reset(topology_st, check_value=output)
    assert test_service.present('seeAlso', f'ou=services,{DEFAULT_SUFFIX}')

    log.info('Test dsidm service modify delete')
    args.changes = [f'delete:seeAlso:ou=services,{DEFAULT_SUFFIX}']
    modify(standalone,
           DEFAULT_SUFFIX,
           topology_st.logcap.log,
           args,
           warn=False)
    check_value_in_log_and_reset(topology_st, check_value=output)
    assert not test_service.present('seeAlso', f'ou=services,{DEFAULT_SUFFIX}')
def test_dsidm_service_get_rdn(topology_st, create_test_service):
    """ Test dsidm service get option

    :id: 294ef774-51e1-11ec-a2c7-3497f624ea11
    :setup: Standalone instance
    :steps:
         1. Run dsidm get option for created service with json
         2. Check the output content is correct
         3. Run dsidm get option for created service without json
         4. Check the json content is correct
    :expectedresults:
         1. Success
         2. Success
         3. Success
         4. Success
    """

    standalone = topology_st.standalone
    services = ServiceAccounts(topology_st.standalone, DEFAULT_SUFFIX)
    testservice = services.get('test_service')

    service_content = [
        f'dn: {testservice.dn}', f'cn: {testservice.rdn}',
        'description: Test Service', 'objectClass: top',
        'objectClass: nsAccount', 'objectClass: nsMemberOf'
    ]

    json_content = [
        'attrs', 'objectclass', 'top', 'nsAccount', 'nsMemberOf',
        testservice.rdn, 'cn', 'description', 'creatorsname',
        'cn=directory manager', 'modifiersname', 'createtimestamp',
        'modifytimestamp', 'nsuniqueid', 'parentid', 'entryid', 'entrydn',
        testservice.dn
    ]

    args = FakeArgs()
    args.json = False
    args.selector = 'test_service'

    log.info('Empty the log file to prevent false data to check about service')
    topology_st.logcap.flush()

    log.info('Test dsidm service get without json')
    get(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
    check_value_in_log_and_reset(topology_st, content_list=service_content)

    log.info('Test dsidm service get with json')
    args.json = True
    get(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
    check_value_in_log_and_reset(topology_st, content_list=json_content)
def create_test_service(topology_st, request):
    service_name = 'test_service'
    services = ServiceAccounts(topology_st.standalone, DEFAULT_SUFFIX)

    log.info('Create test service')
    if services.exists(service_name):
        test_service = services.get(service_name)
        test_service.delete()
    else:
        test_service = services.create_test_service()

    def fin():
        log.info('Delete test service')
        if test_service.exists():
            test_service.delete()

    request.addfinalizer(fin)
def test_dsidm_service_rename(topology_st, create_test_service):
    """ Test dsidm service rename option

    :id: 4a13ea64-51e1-11ec-b3ff-3497f624ea11
    :setup: Standalone instance
    :steps:
         1. Run dsidm service rename option on created service
         2. Check the service does not have another cn attribute with the old rdn
         3. Check the old service is deleted
    :expectedresults:
         1. Success
         2. Success
         3. Success
    """

    standalone = topology_st.standalone
    services = ServiceAccounts(standalone, DEFAULT_SUFFIX)
    test_service = services.get('test_service')

    args = FakeArgs()
    args.selector = test_service.rdn
    args.new_name = 'my_service'
    args.keep_old_rdn = False

    log.info('Test dsidm service rename')
    args.new_name = 'my_service'
    rename(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
    my_service = services.get(args.new_name)
    output = f'Successfully renamed to {my_service.dn}'
    check_value_in_log_and_reset(topology_st, check_value=output)

    log.info('New service should not have cn attribute with the old rdn')
    assert not my_service.present('cn', 'test_service')
    assert my_service.get_attr_val_utf8('cn') == 'my_service'
    assert my_service.get_attr_val_utf8('description') == 'Test Service'

    log.info('Old service dn should not exist.')
    assert not test_service.exists()

    log.info('Clean up')
    my_service.delete()
Exemple #8
0
def test_services(topology):
    """
    Test and assert that a simple service account can be bound to and created.

    These are really useful in simple tests.
    """
    ous = OrganizationalUnits(topology.standalone, DEFAULT_SUFFIX)
    services = ServiceAccounts(topology.standalone, DEFAULT_SUFFIX)

    # Create the OU for them.
    ous.create(properties={
            'ou': 'Services',
            'description': 'Computer Service accounts which request DS bind',
        })
    # Now, we can create the services from here.
    service = services.create(properties={
        'cn': 'testbind',
        'userPassword': '******'
        })

    conn = service.bind('Password1')
    conn.unbind_s()
def test_dsidm_service_get_dn(topology_st, create_test_service):
    """ Test dsidm service get_dn option

    :id: 2e4c8f98-51e1-11ec-b472-3497f624ea11
    :setup: Standalone instance
    :steps:
         1. Run dsidm service get_dn for created service
         2. Check the output content is correct
    :expectedresults:
         1. Success
         2. Success
    """

    standalone = topology_st.standalone
    services = ServiceAccounts(standalone, DEFAULT_SUFFIX)
    test_service = services.get('test_service')
    args = FakeArgs()
    args.dn = test_service.dn

    log.info('Empty the log file to prevent false data to check about service')
    topology_st.logcap.flush()

    log.info('Test dsidm service get_dn without json')
    get_dn(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
Exemple #10
0
def test_basic_feature(topology_st):
    """Check basic SASL functionality for PLAIN mechanism

    :id: 75ddc6fa-aa5a-4025-9c71-1abad20c91fc
    :setup: Standalone instance
    :steps:
        1. Stop the instance
        2. Clean up confdir from previous cert and key files
        3. Create RSA files: CA, key and cert
        4. Start the instance
        5. Create RSA entry
        6. Set nsslapd-secureport to 636 and nsslapd-security to 'on'
        7. Restart the instance
        8. Create a user
        9. Check we can bind
        10. Check that PLAIN is listed in supported mechs
        11. Set up Plain SASL credentials
        12. Try to open a connection without TLS
        13. Try to open a connection with TLS
        14. Try to open a connection with a wrong password
    :expectedresults:
        1. The instance should stop
        2. Confdir should be clean
        3. RSA files should be created
        4. The instance should start
        5. RSA entry should be created
        6. nsslapd-secureport and nsslapd-security should be set successfully
        7. The instance should be restarted
        8. User should be created
        9. Bind should be successful
        10. PLAIN should be listed in supported mechs
        11. Plain SASL should be successfully set
        12. AUTH_UNKNOWN exception should be raised
        13. The connection should open
        14. INVALID_CREDENTIALS exception should be raised
    """

    standalone = topology_st.standalone
    standalone.enable_tls()

    # Create a user
    sas = ServiceAccounts(standalone, DEFAULT_SUFFIX)
    sas._basedn = DEFAULT_SUFFIX
    sa = sas.create(properties={
        'cn': 'testaccount',
        'userPassword': '******'
    })
    # Check we can bind. This will raise exceptions if it fails.
    sa.bind('password')

    # Check that PLAIN is listed in supported mechns.
    assert (standalone.rootdse.supports_sasl_plain())

    # The sasl parameters don't change, so set them up now.
    # Do we need the sasl map dn:?
    auth_tokens = PlainSASL("dn:%s" % sa.dn, 'password')

    # Check that it fails without TLS
    with pytest.raises(ldap.AUTH_UNKNOWN):
        conn = sa.sasl_bind(uri=standalone.get_ldap_uri(),
                            saslmethod='PLAIN',
                            sasltoken=auth_tokens,
                            connOnly=True)

    # We *have* to use REQCERT NEVER here because python ldap fails cert verification for .... some reason that even
    # I can not solve. I think it's leaking state across connections in start_tls_s?

    # Check that it works with TLS
    conn = sa.sasl_bind(uri=standalone.get_ldaps_uri(),
                        saslmethod='PLAIN',
                        sasltoken=auth_tokens,
                        connOnly=True)
    conn.close()

    # Check that it correct fails our bind if we don't have the password.
    auth_tokens = PlainSASL("dn:%s" % sa.dn, 'password-wrong')
    with pytest.raises(ldap.INVALID_CREDENTIALS):
        conn = sa.sasl_bind(uri=standalone.get_ldaps_uri(),
                            saslmethod='PLAIN',
                            sasltoken=auth_tokens,
                            connOnly=True)
Exemple #11
0
def topo_tls_ldapi(topo):
    """Enable TLS on both masters and reconfigure both agreements
    to use TLS Client auth. Also, setup ldapi and export DB
    """

    m1 = topo.ms["master1"]
    m2 = topo.ms["master2"]
    # Create the certmap before we restart for enable_tls
    cm_m1 = CertmapLegacy(m1)
    cm_m2 = CertmapLegacy(m2)

    # We need to configure the same maps for both ....
    certmaps = cm_m1.list()
    certmaps['default']['DNComps'] = None
    certmaps['default']['CmapLdapAttr'] = 'nsCertSubjectDN'

    cm_m1.set(certmaps)
    cm_m2.set(certmaps)

    [i.enable_tls() for i in topo]

    # Create the replication dns
    services = ServiceAccounts(m1, DEFAULT_SUFFIX)
    repl_m1 = services.get('%s:%s' % (m1.host, m1.sslport))
    repl_m1.set('nsCertSubjectDN', m1.get_server_tls_subject())

    repl_m2 = services.get('%s:%s' % (m2.host, m2.sslport))
    repl_m2.set('nsCertSubjectDN', m2.get_server_tls_subject())

    # Check the replication is "done".
    repl = ReplicationManager(DEFAULT_SUFFIX)
    repl.wait_for_replication(m1, m2)
    # Now change the auth type

    replica_m1 = Replicas(m1).get(DEFAULT_SUFFIX)
    agmt_m1 = replica_m1.get_agreements().list()[0]

    agmt_m1.replace_many(
        ('nsDS5ReplicaBindMethod', 'SSLCLIENTAUTH'),
        ('nsDS5ReplicaTransportInfo', 'SSL'),
        ('nsDS5ReplicaPort', '%s' % m2.sslport),
    )
    agmt_m1.remove_all('nsDS5ReplicaBindDN')

    replica_m2 = Replicas(m2).get(DEFAULT_SUFFIX)
    agmt_m2 = replica_m2.get_agreements().list()[0]

    agmt_m2.replace_many(
        ('nsDS5ReplicaBindMethod', 'SSLCLIENTAUTH'),
        ('nsDS5ReplicaTransportInfo', 'SSL'),
        ('nsDS5ReplicaPort', '%s' % m1.sslport),
    )
    agmt_m2.remove_all('nsDS5ReplicaBindDN')

    log.info("Export LDAPTLS_CACERTDIR env variable for ds-replcheck")
    os.environ["LDAPTLS_CACERTDIR"] = m1.get_ssca_dir()

    for inst in topo:
        inst.config.set('nsslapd-ldapilisten', 'on')
        inst.config.set('nsslapd-ldapifilepath', '/var/run/slapd-{}.socket'.format(inst.serverid))
        inst.restart()

    repl.test_replication(m1, m2)
    repl.test_replication(m2, m1)

    return topo
Exemple #12
0
def tls_client_auth(topo_m2):
    """Enable TLS on both masters and reconfigure
    both agreements to use TLS Client auth
    """

    m1 = topo_m2.ms['master1']
    m2 = topo_m2.ms['master2']

    if ds_is_older('1.4.0.6'):
        transport = 'SSL'
    else:
        transport = 'LDAPS'

    # Create the certmap before we restart for enable_tls
    cm_m1 = CertmapLegacy(m1)
    cm_m2 = CertmapLegacy(m2)

    # We need to configure the same maps for both ....
    certmaps = cm_m1.list()
    certmaps['default']['DNComps'] = None
    certmaps['default']['CmapLdapAttr'] = 'nsCertSubjectDN'

    cm_m1.set(certmaps)
    cm_m2.set(certmaps)

    [i.enable_tls() for i in topo_m2]

    # Create the replication dns
    services = ServiceAccounts(m1, DEFAULT_SUFFIX)
    repl_m1 = services.get('%s:%s' % (m1.host, m1.sslport))
    repl_m1.set('nsCertSubjectDN', m1.get_server_tls_subject())

    repl_m2 = services.get('%s:%s' % (m2.host, m2.sslport))
    repl_m2.set('nsCertSubjectDN', m2.get_server_tls_subject())

    # Check the replication is "done".
    repl = ReplicationManager(DEFAULT_SUFFIX)
    repl.wait_for_replication(m1, m2)
    # Now change the auth type

    replica_m1 = Replicas(m1).get(DEFAULT_SUFFIX)
    agmt_m1 = replica_m1.get_agreements().list()[0]

    agmt_m1.replace_many(
        ('nsDS5ReplicaBindMethod', 'SSLCLIENTAUTH'),
        ('nsDS5ReplicaTransportInfo', transport),
        ('nsDS5ReplicaPort', str(m2.sslport)),
    )
    agmt_m1.remove_all('nsDS5ReplicaBindDN')

    replica_m2 = Replicas(m2).get(DEFAULT_SUFFIX)
    agmt_m2 = replica_m2.get_agreements().list()[0]

    agmt_m2.replace_many(
        ('nsDS5ReplicaBindMethod', 'SSLCLIENTAUTH'),
        ('nsDS5ReplicaTransportInfo', transport),
        ('nsDS5ReplicaPort', str(m1.sslport)),
    )
    agmt_m2.remove_all('nsDS5ReplicaBindDN')

    repl.test_replication_topology(topo_m2)

    return topo_m2
def test_clean_shutdown_crash(topology_m2):
    """Check that server didn't crash after shutdown when running CleanAllRUV task

    :id: c34d0b40-3c3e-4f53-8656-5e4c2a310aaf
    :setup: Replication setup with two masters
    :steps:
        1. Enable TLS on both masters
        2. Reconfigure both agreements to use TLS Client auth
        3. Stop master2
        4. Run the CleanAllRUV task
        5. Restart master1
        6. Check if master1 didn't crash
        7. Restart master1 again
        8. Check if master1 didn't crash

    :expectedresults:
        1. Success
        2. Success
        3. Success
        4. Success
        5. Success
        6. Success
        7. Success
        8. Success
    """

    m1 = topology_m2.ms["master1"]
    m2 = topology_m2.ms["master2"]

    repl = ReplicationManager(DEFAULT_SUFFIX)

    cm_m1 = CertmapLegacy(m1)
    cm_m2 = CertmapLegacy(m2)

    certmaps = cm_m1.list()
    certmaps['default']['DNComps'] = None
    certmaps['default']['CmapLdapAttr'] = 'nsCertSubjectDN'

    cm_m1.set(certmaps)
    cm_m2.set(certmaps)

    log.info('Enabling TLS')
    [i.enable_tls() for i in topology_m2]

    log.info('Creating replication dns')
    services = ServiceAccounts(m1, DEFAULT_SUFFIX)
    repl_m1 = services.get('%s:%s' % (m1.host, m1.sslport))
    repl_m1.set('nsCertSubjectDN', m1.get_server_tls_subject())

    repl_m2 = services.get('%s:%s' % (m2.host, m2.sslport))
    repl_m2.set('nsCertSubjectDN', m2.get_server_tls_subject())

    log.info('Changing auth type')
    replica_m1 = Replicas(m1).get(DEFAULT_SUFFIX)
    agmt_m1 = replica_m1.get_agreements().list()[0]
    agmt_m1.replace_many(
        ('nsDS5ReplicaBindMethod', 'SSLCLIENTAUTH'),
        ('nsDS5ReplicaTransportInfo', 'SSL'),
        ('nsDS5ReplicaPort', '%s' % m2.sslport),
    )

    agmt_m1.remove_all('nsDS5ReplicaBindDN')

    replica_m2 = Replicas(m2).get(DEFAULT_SUFFIX)
    agmt_m2 = replica_m2.get_agreements().list()[0]

    agmt_m2.replace_many(
        ('nsDS5ReplicaBindMethod', 'SSLCLIENTAUTH'),
        ('nsDS5ReplicaTransportInfo', 'SSL'),
        ('nsDS5ReplicaPort', '%s' % m1.sslport),
    )
    agmt_m2.remove_all('nsDS5ReplicaBindDN')

    log.info('Stopping master2')
    m2.stop()

    log.info('Run the cleanAllRUV task')
    cruv_task = CleanAllRUVTask(m1)
    cruv_task.create(
        properties={
            'replica-id': repl.get_rid(m1),
            'replica-base-dn': DEFAULT_SUFFIX,
            'replica-force-cleaning': 'no',
            'replica-certify-all': 'yes'
        })

    m1.restart()

    log.info('Check if master1 crashed')
    assert not m1.detectDisorderlyShutdown()

    log.info('Repeat')
    m1.restart()
    assert not m1.detectDisorderlyShutdown()
def test_chaining_paged_search(topology):
    """ Check that when the chaining target has anonymous access
    disabled that the ping still functions and allows the search
    to continue with an appropriate bind user.

    :id: 00bf31db-d93b-4224-8e70-86abb2d4cd17
    :setup: Two standalones in chaining.
    :steps:
        1. Configure chaining between the nodes
        2. Do a chaining search (w anon allow) to assert it works
        3. Configure anon dis allowed on st2
        4. Restart both
        5. Check search still works

    :expectedresults:
        1. Success
        2. Success
        3. Success
        4. Success
        5. Success
    """
    st1 = topology.ins["standalone1"]
    st2 = topology.ins["standalone2"]

    ### We setup so that st1 -> st2

    # Setup a chaining user on st2 to authenticate to.
    sa = ServiceAccounts(st2, DEFAULT_SUFFIX).create(properties = {
        'cn': 'sa',
        'userPassword': PW
    })

    # Add a proxy user.
    sproxy = ServiceAccounts(st2, DEFAULT_SUFFIX).create(properties = {
        'cn': 'proxy',
        'userPassword': PW
    })

    # Add the read and proxy ACI
    dc = Domain(st2, DEFAULT_SUFFIX)
    dc.add('aci',
        f"""(targetattr="objectClass || cn || uid")(version 3.0; acl "Enable sa read"; allow (read, search, compare)(userdn="ldap:///{sa.dn}");)"""
    )
    # Add the proxy ACI
    dc.add('aci',
        f"""(targetattr="*")(version 3.0; acl "Enable proxy access"; allow (proxy)(userdn="ldap:///{sproxy.dn}");)"""
    )

    # Clear all the BE in st1
    bes1 = Backends(st1)
    for be in bes1.list():
        be.delete()

    # Setup st1 to chain to st2
    chain_plugin_1 = ChainingBackendPlugin(st1)
    chain_plugin_1.enable()

    # Chain with the proxy user.
    chains = ChainingLinks(st1)
    chain = chains.create(properties={
        'cn': 'demochain',
        'nsfarmserverurl': st2.toLDAPURL(),
        'nsslapd-suffix': DEFAULT_SUFFIX,
        'nsmultiplexorbinddn': sproxy.dn,
        'nsmultiplexorcredentials': PW,
        'nsCheckLocalACI': 'on',
        'nsConnectionLife': '30',
    })

    mts = MappingTrees(st1)
    # Due to a bug in lib389, we need to delete and recreate the mt.
    for mt in mts.list():
        mt.delete()
    mts.ensure_state(properties={
        'cn': DEFAULT_SUFFIX,
        'nsslapd-state': 'backend',
        'nsslapd-backend': 'demochain',
        'nsslapd-distribution-plugin': 'libreplication-plugin',
        'nsslapd-distribution-funct': 'repl_chain_on_update',
    })

    # Enable pwpolicy (Not sure if part of the issue).
    st1.config.set('passwordIsGlobalPolicy', 'on')
    st2.config.set('passwordIsGlobalPolicy', 'on')

    # Restart to enable everything.
    st1.restart()

    # Get a proxy auth connection.
    sa1 = ServiceAccount(st1, sa.dn)
    sa1_conn = sa1.bind(password=PW)

    # Now do a search from st1 -> st2
    sa1_dc = Domain(sa1_conn, DEFAULT_SUFFIX)
    assert sa1_dc.exists()

    # Now on st2 disable anonymous access.
    st2.config.set('nsslapd-allow-anonymous-access', 'rootdse')

    # Stop st2 to force the connection to be dead.
    st2.stop()
    # Restart st1 - this means it must re-do the ping/keepalive.
    st1.restart()

    # do a bind - this should fail, and forces the conn offline.
    with pytest.raises(ldap.OPERATIONS_ERROR):
        sa1.bind(password=PW)

    # Allow time to attach lldb if needed.
    # print("🔥🔥🔥")
    # time.sleep(45)

    # Bring st2 online.
    st2.start()

    # Wait a bit
    time.sleep(5)

    # Get a proxy auth connection (again)
    sa1_conn = sa1.bind(password=PW)
    # Now do a search from st1 -> st2
    sa1_dc = Domain(sa1_conn, DEFAULT_SUFFIX)
    assert sa1_dc.exists()