You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by se...@apache.org on 2021/06/14 18:35:06 UTC

[directory-studio] 01/01: Use Kerby for GSSAPI tests and enable SASL in 389ds

This is an automated email from the ASF dual-hosted git repository.

seelmann pushed a commit to branch kerby-gssapi-tests
in repository https://gitbox.apache.org/repos/asf/directory-studio.git

commit c35509ad346f1e04247d929268a18c4c544ae575
Author: Stefan Seelmann <ma...@stefan-seelmann.de>
AuthorDate: Mon Jun 14 20:34:35 2021 +0200

    Use Kerby for GSSAPI tests and enable SASL in 389ds
---
 Jenkinsfile                                        |  35 +++----
 .../ui/widgets/AuthenticationParameterPage.java    |   2 +-
 .../core/DirectoryApiConnectionWrapperTest.java    | 103 +++++++++++++++++-
 .../integration/junit5/Fedora389dsLdapServer.java  |   5 +-
 .../test/integration/junit5/OpenLdapServer.java    |   2 +-
 .../test/integration/junit5/Fedora389ds.ldif       |  43 ++++++++
 .../integration/ui/NewConnectionWizardTest.java    |  89 +++++++++++-----
 tools/testlab/README.md                            |  77 ++++++++++++++
 tools/testlab/kerby-data/backend/json-backend.json | 116 +++++++++++++++++++++
 tools/testlab/kerby-data/conf/adminClient.conf     |  23 ++++
 tools/testlab/kerby-data/conf/adminServer.conf     |  24 +++++
 tools/testlab/kerby-data/conf/backend.conf         |  20 ++++
 tools/testlab/kerby-data/conf/kdc.conf             |  23 ++++
 tools/testlab/kerby-data/conf/krb5.conf            |  29 ++++++
 tools/testlab/kerby-data/keytabs/admin.keytab      | Bin 0 -> 148 bytes
 tools/testlab/krb5.conf                            |  29 ++++++
 tools/testlab/ldap.keytab                          | Bin 0 -> 328 bytes
 17 files changed, 562 insertions(+), 58 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index a70db52..dffdaa0 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -37,13 +37,7 @@ pipeline {
       }
       steps {
         script {
-          docker.image('osixia/openldap:1.5.0').withRun('-e LDAP_TLS_VERIFY_CLIENT=never') { openldap ->
-            docker.image('389ds/dirsrv').withRun('-e DS_DM_PASSWORD=admin', 'bash -c "set -m; /usr/lib/dirsrv/dscontainer -r & while ! /usr/lib/dirsrv/dscontainer -H; do sleep 5; done; sleep 5; /usr/sbin/dsconf localhost backend create --suffix dc=example,dc=org --be-name example; fg"') { fedora389ds ->
-              docker.image('apachedirectory/maven-build:jdk-8').inside("--link=${openldap.id}:openldap -e OPENLDAP_HOST=openldap -e OPENLDAP_PORT=389 -e OPENLDAP_PORT_SSL=636 --link=${fedora389ds.id}:fedora389ds -e FEDORA_389DS_HOST=fedora389ds -e FEDORA_389DS_PORT=3389 -e FEDORA_389DS_PORT_SSL=3636") {
-                sh 'export DISPLAY=:99; env; ps aux'
-              }
-            }
-          }
+          inTestLab({ sh 'export DISPLAY=:99; env; ps aux' })
         }
       }
       post {
@@ -64,13 +58,7 @@ pipeline {
           }
           steps {
             script {
-              docker.image('osixia/openldap:1.5.0').withRun('-e LDAP_TLS_VERIFY_CLIENT=never') { openldap ->
-                docker.image('389ds/dirsrv').withRun('-e DS_DM_PASSWORD=admin', 'bash -c "set -m; /usr/lib/dirsrv/dscontainer -r & while ! /usr/lib/dirsrv/dscontainer -H; do sleep 5; done; sleep 5; /usr/sbin/dsconf localhost backend create --suffix dc=example,dc=org --be-name example; fg"') { fedora389ds ->
-                  docker.image('apachedirectory/maven-build:jdk-11').inside("--link=${openldap.id}:openldap -e OPENLDAP_HOST=openldap -e OPENLDAP_PORT=389 -e OPENLDAP_PORT_SSL=636 --link=${fedora389ds.id}:fedora389ds -e FEDORA_389DS_HOST=fedora389ds -e FEDORA_389DS_PORT=3389 -e FEDORA_389DS_PORT_SSL=3636") {
-                    sh 'export DISPLAY=:99; mvn -V -U -f pom-first.xml clean install && mvn -V clean install -Dorg.eclipse.swtbot.search.timeout=20000 -Denable-ui-tests'
-                  }
-                }
-              }
+              inTestLab({ sh 'export DISPLAY=:99; mvn -V -U -f pom-first.xml clean install && mvn -V clean install -Dorg.eclipse.swtbot.search.timeout=20000 -Denable-ui-tests' })
             }
           }
           post {
@@ -91,13 +79,7 @@ pipeline {
           }
           steps {
             script {
-              docker.image('osixia/openldap:1.5.0').withRun('-e LDAP_TLS_VERIFY_CLIENT=never') { openldap ->
-                docker.image('389ds/dirsrv').withRun('-e DS_DM_PASSWORD=admin', 'bash -c "set -m; /usr/lib/dirsrv/dscontainer -r & while ! /usr/lib/dirsrv/dscontainer -H; do sleep 5; done; sleep 5; /usr/sbin/dsconf localhost backend create --suffix dc=example,dc=org --be-name example; fg"') { fedora389ds ->
-                  docker.image('apachedirectory/maven-build:jdk-17').inside("--link=${openldap.id}:openldap -e OPENLDAP_HOST=openldap -e OPENLDAP_PORT=389 -e OPENLDAP_PORT_SSL=636 --link=${fedora389ds.id}:fedora389ds -e FEDORA_389DS_HOST=fedora389ds -e FEDORA_389DS_PORT=3389 -e FEDORA_389DS_PORT_SSL=3636") {
-                    sh 'export DISPLAY=:99; mvn -V -U -f pom-first.xml clean install && mvn -V clean install -Dorg.eclipse.swtbot.search.timeout=20000 -Denable-ui-tests'
-                  }
-                }
-              }
+              inTestLab({ sh 'export DISPLAY=:99; mvn -V -U -f pom-first.xml clean install && mvn -V clean install -Dorg.eclipse.swtbot.search.timeout=20000 -Denable-ui-tests' })
             }
           }
           post {
@@ -150,3 +132,14 @@ pipeline {
   }
 }
 
+def inTestLab(Closure action){
+  docker.image('coheigea/kerby').withRun('-h kerby.example.com -v $(pwd)/tools/testlab/kerby-data:/kerby-data') { kerby ->
+    docker.image('osixia/openldap:1.5.0').withRun('-h openldap.example.com -v $(pwd)/tools/testlab/ldap.keytab:/etc/krb5.keytab -v $(pwd)/tools/testlab/krb5.conf:/etc/krb5.conf -e LDAP_TLS_VERIFY_CLIENT=never') { openldap ->
+      docker.image('389ds/dirsrv').withRun('-h fedora389ds.example.com -v $(pwd)/tools/testlab/ldap.keytab:/etc/krb5.keytab -v $(pwd)/tools/testlab/krb5.conf:/etc/krb5.conf -e DS_DM_PASSWORD=admin', 'bash -c "zypper install -y cyrus-sasl-crammd5 cyrus-sasl-digestmd5 cyrus-sasl-gssapi; set -m; /usr/lib/dirsrv/dscontainer -r & while ! /usr/lib/dirsrv/dscontainer -H; do sleep 5; done; sleep 5; /usr/sbin/dsconf localhost backend create --suffix dc=example,dc=org --be-name example; fg"') { fe [...]
+        docker.image('apachedirectory/maven-build:jdk-8').inside("--link=${kerby.id}:kerby.example.com --link=${openldap.id}:openldap.example.com -v \$(pwd)/tools/testlab/krb5.conf:/etc/krb5.conf -e OPENLDAP_HOST=openldap -e OPENLDAP_PORT=389 -e OPENLDAP_PORT_SSL=636 --link=${fedora389ds.id}:fedora389ds.example.com -e FEDORA_389DS_HOST=fedora389ds -e FEDORA_389DS_PORT=3389 -e FEDORA_389DS_PORT_SSL=3636") {
+          action()
+        }
+      }
+    }
+  }
+}
diff --git a/plugins/connection.ui/src/main/java/org/apache/directory/studio/connection/ui/widgets/AuthenticationParameterPage.java b/plugins/connection.ui/src/main/java/org/apache/directory/studio/connection/ui/widgets/AuthenticationParameterPage.java
index a82cb5d..f59b136 100644
--- a/plugins/connection.ui/src/main/java/org/apache/directory/studio/connection/ui/widgets/AuthenticationParameterPage.java
+++ b/plugins/connection.ui/src/main/java/org/apache/directory/studio/connection/ui/widgets/AuthenticationParameterPage.java
@@ -218,7 +218,7 @@ public class AuthenticationParameterPage extends AbstractConnectionParameterPage
 
     private String getSaslRealm()
     {
-        return saslRealmText.getText();
+        return Strings.isEmpty( saslRealmText.getText() ) ? null : saslRealmText.getText();
     }
 
 
diff --git a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/DirectoryApiConnectionWrapperTest.java b/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/DirectoryApiConnectionWrapperTest.java
index 49000f2..982c4a6 100644
--- a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/DirectoryApiConnectionWrapperTest.java
+++ b/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/DirectoryApiConnectionWrapperTest.java
@@ -92,6 +92,7 @@ import org.apache.directory.studio.connection.core.ConnectionCorePlugin;
 import org.apache.directory.studio.connection.core.ConnectionParameter;
 import org.apache.directory.studio.connection.core.ConnectionParameter.AuthenticationMethod;
 import org.apache.directory.studio.connection.core.ConnectionParameter.EncryptionMethod;
+import org.apache.directory.studio.connection.core.ConnectionParameter.Krb5CredentialConfiguration;
 import org.apache.directory.studio.connection.core.ICertificateHandler.TrustLevel;
 import org.apache.directory.studio.connection.core.IReferralHandler;
 import org.apache.directory.studio.connection.core.event.ConnectionEventRegistry;
@@ -378,7 +379,7 @@ public class DirectoryApiConnectionWrapperTest
      * Test binding to the server using SASL and no encryption.
      */
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
+    @LdapServersSource(mode = Mode.All)
     public void testSaslBindPlain( TestLdapServer ldapServer )
     {
         ldapServer.setConfidentialityRequired( false );
@@ -432,7 +433,7 @@ public class DirectoryApiConnectionWrapperTest
      * Test binding to the server using SASL and ldaps:// encryption.
      */
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
+    @LdapServersSource(mode = Mode.All)
     public void testSaslBindLdaps( TestLdapServer ldapServer )
     {
         ldapServer.setConfidentialityRequired( true );
@@ -462,7 +463,7 @@ public class DirectoryApiConnectionWrapperTest
      * Test binding to the server using SASL and StartTLS encryption.
      */
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds)
+    @LdapServersSource(mode = Mode.All)
     public void testSaslBindStartTls( TestLdapServer ldapServer )
     {
         ldapServer.setConfidentialityRequired( true );
@@ -488,6 +489,102 @@ public class DirectoryApiConnectionWrapperTest
 
 
     /**
+     * Test binding to the server using GSSAPI and no encryption.
+     */
+    @ParameterizedTest
+    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
+    public void testSaslGssapiBindPlain( TestLdapServer ldapServer )
+    {
+        ldapServer.setConfidentialityRequired( false );
+        StudioProgressMonitor monitor = getProgressMonitor();
+        Connection connection = getConnection( monitor, ldapServer, "hnelson", "secret" );
+        connection.setAuthMethod( AuthenticationMethod.SASL_GSSAPI );
+        connection.getConnectionParameter().setKrb5CredentialConfiguration( Krb5CredentialConfiguration.OBTAIN_TGT );
+
+        assertFalse( connectionWrapper.isConnected() );
+
+        connectionWrapper.connect( monitor );
+        connectionWrapper.bind( monitor );
+
+        assertTrue( connectionWrapper.isConnected() );
+        assertFalse( connectionWrapper.isSecured() );
+        assertNull( monitor.getException() );
+
+        connectionWrapper.unbind();
+        connectionWrapper.disconnect();
+        assertFalse( connectionWrapper.isConnected() );
+    }
+
+
+    /**
+     * Test binding to the server using GSSAPI and ldaps:// encryption.
+     */
+    @ParameterizedTest
+    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
+    public void testSaslGssapiBindLdaps( TestLdapServer ldapServer ) throws Exception
+    {
+        // obtain native TGT
+        String[] cmd =
+            { "/bin/sh", "-c", "echo secret | /usr/bin/kinit hnelson" };
+        Process process = Runtime.getRuntime().exec( cmd );
+        int exitCode = process.waitFor();
+        assertEquals( 0, exitCode );
+
+        ldapServer.setConfidentialityRequired( true );
+        StudioProgressMonitor monitor = getProgressMonitor();
+        Connection connection = getConnection( monitor, ldapServer, "hnelson", "secret" );
+        connection.setPort( ldapServer.getPortSSL() );
+        connection.setEncryptionMethod( EncryptionMethod.LDAPS );
+        connection.setAuthMethod( AuthenticationMethod.SASL_GSSAPI );
+        connection.getConnectionParameter().setKrb5CredentialConfiguration( Krb5CredentialConfiguration.USE_NATIVE );
+        acceptAllCertificates();
+
+        assertFalse( connectionWrapper.isConnected() );
+
+        connectionWrapper.connect( monitor );
+        connectionWrapper.bind( monitor );
+
+        assertTrue( connectionWrapper.isConnected() );
+        assertTrue( connectionWrapper.isSecured() );
+        assertNull( monitor.getException() );
+
+        connectionWrapper.unbind();
+        connectionWrapper.disconnect();
+        assertFalse( connectionWrapper.isConnected() );
+    }
+
+
+    /**
+     * Test binding to the server using GSSAPI and StartTLS encryption.
+     */
+    @ParameterizedTest
+    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
+    public void testSaslGssapiBindStartTls( TestLdapServer ldapServer )
+    {
+        ldapServer.setConfidentialityRequired( true );
+        StudioProgressMonitor monitor = getProgressMonitor();
+        Connection connection = getConnection( monitor, ldapServer, "hnelson", "secret" );
+        connection.setEncryptionMethod( EncryptionMethod.START_TLS );
+        connection.setAuthMethod( AuthenticationMethod.SASL_GSSAPI );
+        connection.getConnectionParameter().setKrb5CredentialConfiguration( Krb5CredentialConfiguration.OBTAIN_TGT );
+        acceptAllCertificates();
+
+        assertFalse( connectionWrapper.isConnected() );
+
+        connectionWrapper.connect( monitor );
+        connectionWrapper.bind( monitor );
+
+        assertTrue( connectionWrapper.isConnected() );
+        assertTrue( connectionWrapper.isSecured() );
+        assertNull( monitor.getException() );
+
+        connectionWrapper.unbind();
+        connectionWrapper.disconnect();
+        assertFalse( connectionWrapper.isConnected() );
+    }
+
+
+    /**
      * Test failed binds to the server.
      */
     @ParameterizedTest
diff --git a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/junit5/Fedora389dsLdapServer.java b/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/junit5/Fedora389dsLdapServer.java
index 7f8a0ac..c65e4ba 100644
--- a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/junit5/Fedora389dsLdapServer.java
+++ b/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/junit5/Fedora389dsLdapServer.java
@@ -21,8 +21,6 @@
 package org.apache.directory.studio.test.integration.junit5;
 
 
-import static org.apache.directory.studio.test.integration.junit5.Constants.LOCALHOST;
-
 import org.apache.directory.api.ldap.model.entry.DefaultModification;
 import org.apache.directory.api.ldap.model.entry.Modification;
 import org.apache.directory.api.ldap.model.entry.ModificationOperation;
@@ -36,7 +34,7 @@ import org.apache.directory.api.ldap.model.entry.ModificationOperation;
  */
 public class Fedora389dsLdapServer extends TestLdapServer
 {
-    private static final String FEDORA_389DS_HOST = getEnvOrDefault( "FEDORA_389DS_HOST", LOCALHOST );
+    private static final String FEDORA_389DS_HOST = getEnvOrDefault( "FEDORA_389DS_HOST", "fedora389ds.example.com" );
     private static final int FEDORA_389DS_PORT = Integer.parseInt( getEnvOrDefault( "FEDORA_389DS_PORT", "21389" ) );
     private static final int FEDORA_389DS_PORT_SSL = Integer
         .parseInt( getEnvOrDefault( "FEDORA_389DS_PORT_SSL", "21636" ) );
@@ -65,6 +63,5 @@ public class Fedora389dsLdapServer extends TestLdapServer
                 "nsslapd-require-secure-binds", confidentialityRequired ? "on" : "off" );
             connection.modify( "cn=config", modification );
         } );
-
     }
 }
diff --git a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/junit5/OpenLdapServer.java b/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/junit5/OpenLdapServer.java
index 69b80cb..5a1e971 100644
--- a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/junit5/OpenLdapServer.java
+++ b/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/junit5/OpenLdapServer.java
@@ -40,7 +40,7 @@ import org.apache.directory.ldap.client.api.LdapConnection;
  */
 public class OpenLdapServer extends TestLdapServer
 {
-    private static final String OPENLDAP_HOST = getEnvOrDefault( "OPENLDAP_HOST", LOCALHOST );
+    private static final String OPENLDAP_HOST = getEnvOrDefault( "OPENLDAP_HOST", "openldap.example.com" );
     private static final int OPENLDAP_PORT = Integer.parseInt( getEnvOrDefault( "OPENLDAP_PORT", "20389" ) );
     private static final int OPENLDAP_PORT_SSL = Integer.parseInt( getEnvOrDefault( "OPENLDAP_PORT_SSL", "20636" ) );
     private static final String OPENLDAP_ADMIN_DN = getEnvOrDefault( "OPENLDAP_ADMIN_DN",
diff --git a/tests/test.integration.core/src/main/resources/org/apache/directory/studio/test/integration/junit5/Fedora389ds.ldif b/tests/test.integration.core/src/main/resources/org/apache/directory/studio/test/integration/junit5/Fedora389ds.ldif
new file mode 100644
index 0000000..3c76c87
--- /dev/null
+++ b/tests/test.integration.core/src/main/resources/org/apache/directory/studio/test/integration/junit5/Fedora389ds.ldif
@@ -0,0 +1,43 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+dn: cn=config
+changetype: modify
+replace: passwordStorageScheme
+passwordStorageScheme: CLEAR
+-
+replace: nsslapd-enable-upgrade-hash
+nsslapd-enable-upgrade-hash: off
+-
+
+dn: cn=Kerberos uid mapping,cn=mapping,cn=sasl,cn=config
+changetype: modify
+replace: nsSaslMapRegexString
+nsSaslMapRegexString: \(.*\)
+-
+
+dn: cn=Kerberos uid mapping,cn=mapping,cn=sasl,cn=config
+changetype: modify
+replace: nsSaslMapBaseDNTemplate
+nsSaslMapBaseDNTemplate: dc=example,dc=org
+-
+
+dn: cn=Kerberos uid mapping,cn=mapping,cn=sasl,cn=config
+changetype: modify
+replace: nsSaslMapFilterTemplate
+nsSaslMapFilterTemplate: (uid=\1)
+-
diff --git a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/NewConnectionWizardTest.java b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/NewConnectionWizardTest.java
index 2fcba85..fba86f3 100644
--- a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/NewConnectionWizardTest.java
+++ b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/NewConnectionWizardTest.java
@@ -21,6 +21,7 @@
 package org.apache.directory.studio.test.integration.ui;
 
 
+import static org.apache.directory.studio.test.integration.ui.utils.Constants.LOCALHOST;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -432,7 +433,7 @@ public class NewConnectionWizardTest extends AbstractTestBase
 
 
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds, reason = "SASL not configured for 389ds")
+    @LdapServersSource(mode = Mode.All)
     public void testCreateConnectionNoEncryptionSaslCramMd5OK( TestLdapServer server )
     {
         setConnectionParameters( server, EncryptionMethod.NONE );
@@ -452,7 +453,7 @@ public class NewConnectionWizardTest extends AbstractTestBase
 
 
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds, reason = "SASL not configured for 389ds")
+    @LdapServersSource(mode = Mode.All)
     public void testCreateConnectionNoEncryptionSaslDigestMd5OK( TestLdapServer server )
     {
         setConnectionParameters( server, EncryptionMethod.NONE );
@@ -460,10 +461,6 @@ public class NewConnectionWizardTest extends AbstractTestBase
         wizardBot.selectDigestMD5Authentication();
         wizardBot.typeUser( "user.1" );
         wizardBot.typePassword( "password" );
-        if ( server.getType() == LdapServerType.ApacheDS )
-        {
-            wizardBot.typeRealm( "EXAMPLE.ORG" );
-        }
         wizardBot.selectQualityOfProtection( SaslQoP.AUTH );
         wizardBot.selectProtectionStrength( SaslSecurityStrength.HIGH );
 
@@ -476,7 +473,7 @@ public class NewConnectionWizardTest extends AbstractTestBase
 
 
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds, reason = "SASL not configured for 389ds")
+    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds, reason = "Only secure binds configured for 389ds")
     public void testCreateConnectionNoEncryptionSaslDigestMd5ConfidentialityRequired( TestLdapServer server )
     {
         setConnectionParameters( server, EncryptionMethod.NONE );
@@ -494,6 +491,58 @@ public class NewConnectionWizardTest extends AbstractTestBase
 
 
     @ParameterizedTest
+    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
+    public void testCreateConnectionNoEncryptionSaslGssapiNativeTgtOK( TestLdapServer server ) throws Exception
+    {
+        // obtain native TGT
+        String[] cmd =
+            { "/bin/sh", "-c", "echo secret | /usr/bin/kinit hnelson" };
+        Process process = Runtime.getRuntime().exec( cmd );
+        int exitCode = process.waitFor();
+        assertEquals( 0, exitCode );
+
+        setConnectionParameters( server, EncryptionMethod.NONE );
+
+        wizardBot.selectGssApiAuthentication();
+        wizardBot.selectQualityOfProtection( SaslQoP.AUTH );
+        wizardBot.selectProtectionStrength( SaslSecurityStrength.HIGH );
+        wizardBot.selectUseNativeTgt();
+        wizardBot.selectUseNativeSystemConfiguration();
+
+        String result = wizardBot.clickCheckAuthenticationButton();
+        assertNull( result, "Expected OK" );
+
+        finishAndAssertConnection( server, EncryptionMethod.NONE, AuthenticationMethod.SASL_GSSAPI,
+            "", "" );
+    }
+
+
+    @ParameterizedTest
+    @LdapServersSource(mode = Mode.All, except = LdapServerType.ApacheDS, reason = "Missing OSGi import: org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntryModifier cannot be found by org.apache.directory.server.protocol.shared_2.0.0.AM26")
+    public void testCreateConnectionNoEncryptionSaslGssapiObtainOK( TestLdapServer server )
+    {
+        setConnectionParameters( server, EncryptionMethod.NONE );
+
+        wizardBot.selectGssApiAuthentication();
+        wizardBot.selectObtainTgtFromKdc();
+        wizardBot.typeUser( "hnelson" );
+        wizardBot.typePassword( "secret" );
+        wizardBot.selectQualityOfProtection( SaslQoP.AUTH );
+        wizardBot.selectProtectionStrength( SaslSecurityStrength.HIGH );
+        wizardBot.selectUseManualConfiguration();
+        wizardBot.typeKerberosRealm( "EXAMPLE.COM" );
+        wizardBot.typeKdcHost( LOCALHOST );
+        wizardBot.typeKdcPort( 60088 );
+
+        String result = wizardBot.clickCheckAuthenticationButton();
+        assertNull( result, "Expected OK" );
+
+        finishAndAssertConnection( server, EncryptionMethod.NONE, AuthenticationMethod.SASL_GSSAPI,
+            "hnelson", "secret" );
+    }
+
+
+    @ParameterizedTest
     @LdapServersSource(mode = Mode.All)
     public void testCreateConnectionLdapsEncryptionNoAuthOK( TestLdapServer server )
     {
@@ -537,7 +586,7 @@ public class NewConnectionWizardTest extends AbstractTestBase
 
 
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds, reason = "SASL not configured for 389ds")
+    @LdapServersSource(mode = Mode.All)
     public void testCreateConnectionLdapsEncryptionSaslDigestMd5Ok( TestLdapServer server )
     {
         setConnectionParameters( server, EncryptionMethod.LDAPS );
@@ -545,10 +594,6 @@ public class NewConnectionWizardTest extends AbstractTestBase
         wizardBot.selectDigestMD5Authentication();
         wizardBot.typeUser( "user.1" );
         wizardBot.typePassword( "password" );
-        if ( server.getType() == LdapServerType.ApacheDS )
-        {
-            wizardBot.typeRealm( "EXAMPLE.ORG" );
-        }
         wizardBot.selectQualityOfProtection( SaslQoP.AUTH );
         wizardBot.selectProtectionStrength( SaslSecurityStrength.HIGH );
 
@@ -558,7 +603,7 @@ public class NewConnectionWizardTest extends AbstractTestBase
 
 
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds, reason = "SASL not configured for 389ds")
+    @LdapServersSource(mode = Mode.All)
     public void testCreateConnectionLdapsEncryptionSaslDigestMd5InvalidCredentials( TestLdapServer server )
     {
         setConnectionParameters( server, EncryptionMethod.LDAPS );
@@ -566,10 +611,6 @@ public class NewConnectionWizardTest extends AbstractTestBase
         wizardBot.selectDigestMD5Authentication();
         wizardBot.typeUser( "user.1" );
         wizardBot.typePassword( "invalid" );
-        if ( server.getType() == LdapServerType.ApacheDS )
-        {
-            wizardBot.typeRealm( "EXAMPLE.ORG" );
-        }
         wizardBot.selectQualityOfProtection( SaslQoP.AUTH );
         wizardBot.selectProtectionStrength( SaslSecurityStrength.HIGH );
 
@@ -581,7 +622,7 @@ public class NewConnectionWizardTest extends AbstractTestBase
 
 
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds, reason = "SASL not configured for 389ds")
+    @LdapServersSource(mode = Mode.All)
     public void testCreateConnectionLdapsEncryptionSaslDigestMd5InvalidRealm( TestLdapServer server )
     {
         setConnectionParameters( server, EncryptionMethod.LDAPS );
@@ -644,7 +685,7 @@ public class NewConnectionWizardTest extends AbstractTestBase
 
 
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds, reason = "SASL not configured for 389ds")
+    @LdapServersSource(mode = Mode.All)
     public void testCreateConnectionStartTlsEncryptionSaslDigestMd5OK( TestLdapServer server )
     {
         setConnectionParameters( server, EncryptionMethod.START_TLS );
@@ -652,10 +693,6 @@ public class NewConnectionWizardTest extends AbstractTestBase
         wizardBot.selectDigestMD5Authentication();
         wizardBot.typeUser( "user.1" );
         wizardBot.typePassword( "password" );
-        if ( server.getType() == LdapServerType.ApacheDS )
-        {
-            wizardBot.typeRealm( "EXAMPLE.ORG" );
-        }
         wizardBot.selectQualityOfProtection( SaslQoP.AUTH );
         wizardBot.selectProtectionStrength( SaslSecurityStrength.HIGH );
 
@@ -665,7 +702,7 @@ public class NewConnectionWizardTest extends AbstractTestBase
 
 
     @ParameterizedTest
-    @LdapServersSource(mode = Mode.All, except = LdapServerType.Fedora389ds, reason = "SASL not configured for 389ds")
+    @LdapServersSource(mode = Mode.All)
     public void testCreateConnectionStartTlsEncryptionSaslDigestMd5InvalidCredentials( TestLdapServer server )
     {
         setConnectionParameters( server, EncryptionMethod.START_TLS );
@@ -673,10 +710,6 @@ public class NewConnectionWizardTest extends AbstractTestBase
         wizardBot.selectDigestMD5Authentication();
         wizardBot.typeUser( "user.1" );
         wizardBot.typePassword( "invalid" );
-        if ( server.getType() == LdapServerType.ApacheDS )
-        {
-            wizardBot.typeRealm( "EXAMPLE.ORG" );
-        }
         wizardBot.selectQualityOfProtection( SaslQoP.AUTH );
         wizardBot.selectProtectionStrength( SaslSecurityStrength.HIGH );
 
diff --git a/tools/testlab/README.md b/tools/testlab/README.md
new file mode 100644
index 0000000..288766f
--- /dev/null
+++ b/tools/testlab/README.md
@@ -0,0 +1,77 @@
+# Test Lab
+
+## Host preparation
+
+To be able to access the services with their FQDN add aliases to the `/etc/hosts`. 
+Note that this is a hack rather than a proper solution.
+
+```
+/etc/hosts:
+127.0.0.100     kerby.example.com
+127.0.0.101     openldap.example.com
+127.0.0.102     fedora389ds.example.com
+```
+
+## Apache Kerby as Kerberos KDC
+
+Uses Docker image `coheigea/kerby`, source at https://github.com/coheigea/testcases/tree/master/apache/docker/kerby.
+
+Start the Kerby server
+
+```
+docker run -it --rm --name kerby -h kerby.example.com -u $(id -u):$(id -g) -p 60088:60088/tcp -p 60088:60088/udp -v $(pwd)/tools/testlab/kerby-data:/kerby-data coheigea/kerby
+```
+
+Initial creation of service accounts and keytabs:
+
+```
+docker exec -it kerby bash
+stty rows 24 columns 80
+sh bin/kadmin.sh /kerby-data/conf/ -k /kerby-data/keytabs/admin.keytab
+
+addprinc -pw secret hnelson@EXAMPLE.COM
+
+addprinc -pw randall ldap/openldap.example.com@EXAMPLE.COM
+ktadd -k /kerby-data/keytabs/ldap.keytab ldap/openldap.example.com@EXAMPLE.COM
+addprinc -pw randall ldap/fedora389ds.example.com@EXAMPLE.COM
+ktadd -k /kerby-data/keytabs/ldap.keytab ldap/fedora389ds.example.com@EXAMPLE.COM
+```
+
+## OpenLDAP
+
+```
+docker run -it --rm --name openldap -h openldap.example.com -p 20389:389 -p 20636:636 -e LDAP_TLS_VERIFY_CLIENT=never -v $(pwd)/tools/testlab/ldap.keytab:/etc/krb5.keytab -v $(pwd)/tools/testlab/krb5.conf:/etc/krb5.conf osixia/openldap:1.5.0
+```
+
+## Fedora 389ds
+
+```
+docker run -it --rm --name fedora389ds -h fedora389ds.example.com -p 21389:3389 -p 21636:3636 -e DS_DM_PASSWORD=admin -v $(pwd)/tools/testlab/ldap.keytab:/etc/krb5.keytab -v $(pwd)/tools/testlab/krb5.conf:/etc/krb5.conf 389ds/dirsrv bash -c "zypper install -y cyrus-sasl-crammd5 cyrus-sasl-digestmd5 cyrus-sasl-gssapi; set -m; /usr/lib/dirsrv/dscontainer -r & while ! /usr/lib/dirsrv/dscontainer -H; do sleep 5; done; sleep 5; /usr/sbin/dsconf localhost backend create --suffix dc=example,dc= [...]
+```
+
+## Usage
+
+### GSSAPI authentication
+
+```
+export KRB5_CONFIG=$(pwd)/tools/testlab/krb5.conf
+echo "secret" | kinit hnelson
+ldapwhoami -H ldap://openldap.example.com:20389 -Y GSSAPI -N
+ldapwhoami -H ldap://fedora389ds.example.com:21389 -Y GSSAPI -N
+```
+
+### UI integration tests
+
+```
+docker run -it --rm --cpus="1.5" \
+    -u $(id -u):$(id -g) \
+    -v ~/.m2:/home/hnelson/.m2 \
+    -v $(pwd):/home/hnelson/project \
+    -v $(pwd)/tools/testlab/krb5.conf:/etc/krb5.conf \
+    --link=kerby:kerby.example.com \
+    --link=openldap:openldap.example.com -e OPENLDAP_HOST=openldap.example.com -e OPENLDAP_PORT=389 -e OPENLDAP_PORT_SSL=636 \
+    --link=fedora389ds:fedora389ds.example.com -e FEDORA_389DS_HOST=fedora389ds.example.com -e FEDORA_389DS_PORT=3389 -e FEDORA_389DS_PORT_SSL=3636 \
+    apachedirectory/maven-build:jdk-11 bash -c "mvn -V -f pom-first.xml clean install && mvn -V clean install -Denable-ui-tests"
+
+```
+
diff --git a/tools/testlab/kerby-data/backend/json-backend.json b/tools/testlab/kerby-data/backend/json-backend.json
new file mode 100644
index 0000000..0d47acd
--- /dev/null
+++ b/tools/testlab/kerby-data/backend/json-backend.json
@@ -0,0 +1,116 @@
+{
+  "adminprotocol/localhost@EXAMPLE.COM": {
+    "principal": "adminprotocol/localhost@EXAMPLE.COM",
+    "keyVersion": 1,
+    "kdcFlags": 0,
+    "disabled": false,
+    "locked": false,
+    "expireTime": "253402300799900",
+    "createdTime": "1528713224740",
+    "keys": {
+      "AES128_CTS_HMAC_SHA1_96": {
+        "kvno": 1,
+        "key": "3019A003020111A112041091CDC8A37ADE5268236C840523F87038"
+      },
+      "DES3_CBC_SHA1": {
+        "kvno": 1,
+        "key": "3021A003020110A11A0418F7AE8A9B6D7AF2E59D49DF193476CE511FFE1091C464A823"
+      }
+    }
+  },
+  "ldap/fedora389ds.example.com@EXAMPLE.COM": {
+    "principal": "ldap/fedora389ds.example.com@EXAMPLE.COM",
+    "keyVersion": 1,
+    "kdcFlags": 0,
+    "disabled": false,
+    "locked": false,
+    "expireTime": "253402300799900",
+    "createdTime": "1623646752731",
+    "keys": {
+      "DES3_CBC_SHA1": {
+        "kvno": 1,
+        "key": "3021A003020110A11A04182A1FF2DFC47AB0B0E068EA3D3476AE19511ACDE6970D5467"
+      },
+      "AES128_CTS_HMAC_SHA1_96": {
+        "kvno": 1,
+        "key": "3019A003020111A1120410B13B78BDE0374D463D877871A3C8B88F"
+      }
+    }
+  },
+  "krbtgt/EXAMPLE.COM@EXAMPLE.COM": {
+    "principal": "krbtgt/EXAMPLE.COM@EXAMPLE.COM",
+    "keyVersion": 1,
+    "kdcFlags": 0,
+    "disabled": false,
+    "locked": false,
+    "expireTime": "253402300799900",
+    "createdTime": "1528713224693",
+    "keys": {
+      "AES128_CTS_HMAC_SHA1_96": {
+        "kvno": 1,
+        "key": "3019A003020111A1120410BF87D478614F7A6D76C0462F2AD180AE"
+      },
+      "DES3_CBC_SHA1": {
+        "kvno": 1,
+        "key": "3021A003020110A11A0418627F34BF7F261964F1E55B5849B3D583BCD0795E34FB317A"
+      }
+    }
+  },
+  "ldap/openldap.example.com@EXAMPLE.COM": {
+    "principal": "ldap/openldap.example.com@EXAMPLE.COM",
+    "keyVersion": 1,
+    "kdcFlags": 0,
+    "disabled": false,
+    "locked": false,
+    "expireTime": "253402300799900",
+    "createdTime": "1623646740945",
+    "keys": {
+      "DES3_CBC_SHA1": {
+        "kvno": 1,
+        "key": "3021A003020110A11A0418DA0458C110D9435776F4BC32BFAE1C94624A7A402979D6F8"
+      },
+      "AES128_CTS_HMAC_SHA1_96": {
+        "kvno": 1,
+        "key": "3019A003020111A11204106A8382B60767C62E8766B741F92406A4"
+      }
+    }
+  },
+  "hnelson@EXAMPLE.COM": {
+    "principal": "hnelson@EXAMPLE.COM",
+    "keyVersion": 1,
+    "kdcFlags": 0,
+    "disabled": false,
+    "locked": false,
+    "expireTime": "253402300799900",
+    "createdTime": "1623569412700",
+    "keys": {
+      "DES3_CBC_SHA1": {
+        "kvno": 1,
+        "key": "3021A003020110A11A04185707CE2952922C1C8CBF43C23D8F8C5E9E8CF75D3E4A5E25"
+      },
+      "AES128_CTS_HMAC_SHA1_96": {
+        "kvno": 1,
+        "key": "3019A003020111A1120410AD214B38B69DFCCAACF15F346D417B90"
+      }
+    }
+  },
+  "kadmin/EXAMPLE.COM@EXAMPLE.COM": {
+    "principal": "kadmin/EXAMPLE.COM@EXAMPLE.COM",
+    "keyVersion": 1,
+    "kdcFlags": 0,
+    "disabled": false,
+    "locked": false,
+    "expireTime": "253402300799900",
+    "createdTime": "1528713224726",
+    "keys": {
+      "AES128_CTS_HMAC_SHA1_96": {
+        "kvno": 1,
+        "key": "3019A003020111A112041064F2D24DE79182AD88AC9A60ED6F9983"
+      },
+      "DES3_CBC_SHA1": {
+        "kvno": 1,
+        "key": "3021A003020110A11A04189BCBA454B3FB4A540746ECC1D3A283BCFBD307A15BE5EA1C"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/tools/testlab/kerby-data/conf/adminClient.conf b/tools/testlab/kerby-data/conf/adminClient.conf
new file mode 100644
index 0000000..7c6909b
--- /dev/null
+++ b/tools/testlab/kerby-data/conf/adminClient.conf
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+[libdefaults]
+default_realm = EXAMPLE.COM
+admin_port = 65417
+keytab_file = admin.keytab
+protocol = adminprotocol
+server_name = localhost
diff --git a/tools/testlab/kerby-data/conf/adminServer.conf b/tools/testlab/kerby-data/conf/adminServer.conf
new file mode 100644
index 0000000..08af51c
--- /dev/null
+++ b/tools/testlab/kerby-data/conf/adminServer.conf
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+[libdefaults]
+default_realm = EXAMPLE.COM
+admin_realm = EXAMPLE.COM
+admin_port = 65417
+keytab_file = protocol.keytab
+protocol = adminprotocol
+server_name = localhost
diff --git a/tools/testlab/kerby-data/conf/backend.conf b/tools/testlab/kerby-data/conf/backend.conf
new file mode 100644
index 0000000..3bbe481
--- /dev/null
+++ b/tools/testlab/kerby-data/conf/backend.conf
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+kdc_identity_backend = org.apache.kerby.kerberos.kdc.identitybackend.JsonIdentityBackend
+backend.json.dir = /kerby-data/backend
diff --git a/tools/testlab/kerby-data/conf/kdc.conf b/tools/testlab/kerby-data/conf/kdc.conf
new file mode 100644
index 0000000..34f2973
--- /dev/null
+++ b/tools/testlab/kerby-data/conf/kdc.conf
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[kdcdefaults]
+  kdc_host = kerby.example.com
+  kdc_udp_port = 60088
+  kdc_tcp_port = 60088
+  kdc_realm = EXAMPLE.COM
diff --git a/tools/testlab/kerby-data/conf/krb5.conf b/tools/testlab/kerby-data/conf/krb5.conf
new file mode 100644
index 0000000..6692859
--- /dev/null
+++ b/tools/testlab/kerby-data/conf/krb5.conf
@@ -0,0 +1,29 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[libdefaults]
+    kdc_realm = EXAMPLE.COM
+    default_realm = EXAMPLE.COM
+    udp_preference_limit = 4096
+    kdc_tcp_port = 60088
+    kdc_udp_port = 60088
+
+[realms]
+    EXAMPLE.COM = {
+        kdc = localhost:60088
+    }
\ No newline at end of file
diff --git a/tools/testlab/kerby-data/keytabs/admin.keytab b/tools/testlab/kerby-data/keytabs/admin.keytab
new file mode 100644
index 0000000..55e1add
Binary files /dev/null and b/tools/testlab/kerby-data/keytabs/admin.keytab differ
diff --git a/tools/testlab/krb5.conf b/tools/testlab/krb5.conf
new file mode 100644
index 0000000..cb007b8
--- /dev/null
+++ b/tools/testlab/krb5.conf
@@ -0,0 +1,29 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[libdefaults]
+    kdc_realm = EXAMPLE.COM
+    default_realm = EXAMPLE.COM
+    udp_preference_limit = 4096
+    kdc_tcp_port = 60088
+    kdc_udp_port = 60088
+
+[realms]
+    EXAMPLE.COM = {
+        kdc = kerby.example.com:60088
+    }
\ No newline at end of file
diff --git a/tools/testlab/ldap.keytab b/tools/testlab/ldap.keytab
new file mode 100644
index 0000000..11e2700
Binary files /dev/null and b/tools/testlab/ldap.keytab differ