You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2020/05/28 18:17:08 UTC

[tinkerpop] 01/09: Moves KdcFixture to gremlin-test to allow for access by GLV's

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

spmallette pushed a commit to branch TINKERPOP-1641
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit c88557df1a4edbd88a4916a374a07501a103ce63
Author: HadoopMarc <vt...@xs4all.nl>
AuthorDate: Sat Apr 4 10:07:43 2020 +0200

    Moves KdcFixture to gremlin-test to allow for access by GLV's
    
    Makes availeble KdcFixture in the docker container for testing GLV's
    
    Adds kerberos authentication to gremlin-python
    
    Adds tests for gremlin-python with kerberos
    
    Adds audit logging to bytecode traversals (remnant from TINKERPOP-1566)
    
    Updates the documentation
    
    Updates the changelog
---
 CHANGELOG.asciidoc                                 |   3 +
 docker/Dockerfile                                  |   4 +-
 docker/gremlin-server.sh                           |   3 +-
 docker/gremlin-server/Dockerfile.template          |   2 +-
 docker/gremlin-server/docker-entrypoint.sh         |  63 ++++++++++++-
 .../gremlin-server/gremlin-console-jaas.conf       |  18 +---
 .../gremlin-server-integration-krb5.yaml           |  66 +++++++++++++
 docker/gremlin-server/krb5.conf                    |  29 ++++++
 docs/src/reference/gremlin-applications.asciidoc   |  40 +++++---
 docs/src/reference/gremlin-variants.asciidoc       |  31 +++++-
 gremlin-dotnet/pom.xml                             |   5 +
 gremlin-javascript/pom.xml                         |   5 +
 gremlin-python/pom.xml                             |   9 +-
 .../main/python/gremlin_python/driver/client.py    |   5 +-
 .../driver/driver_remote_connection.py             |   7 +-
 .../main/python/gremlin_python/driver/protocol.py  | 105 +++++++++++++++++----
 gremlin-python/src/main/python/setup.py            |   5 +-
 gremlin-python/src/main/python/tests/conftest.py   |  57 ++++++++---
 .../src/main/python/tests/driver/test_client.py    |  16 ++--
 .../tests/driver/test_driver_remote_connection.py  |  14 +--
 .../python/tests/structure/io/test_graphsonV2d0.py |  12 +--
 gremlin-server/pom.xml                             |   7 --
 .../gremlin/server/auth/Krb5Authenticator.java     |   4 +-
 .../server/op/traversal/TraversalOpProcessor.java  |   7 ++
 .../server/GremlinServerAuditLogIntegrateTest.java |  66 ++++++++++---
 .../server/GremlinServerAuthKrb5IntegrateTest.java |  32 ++++---
 .../src/test/scripts/test-server-start.groovy      |  37 +++++++-
 .../src/test/scripts/test-server-stop.groovy       |   8 ++
 gremlin-test/pom.xml                               |  23 +++++
 .../tinkerpop/gremlin/server/KdcFixture.java       |  56 +++++++----
 30 files changed, 585 insertions(+), 154 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 07b7c35..07f6a97 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -50,6 +50,9 @@ This release also includes changes from <<release-3-4-3, 3.4.3>>.
 * Bump to Neo4j 3.4.11.
 * Added a parameterized `TypeTranslator` for use with `GroovyTranslator` that should produce more cache hits.
 * Added support for `TextP` in Neo4j using its string search functions.
+* Added a kerberos KDC to the docker container for testing GLV's.
+* Added kerberos authentication to Gremlin-Python.
+* Added audit logging to bytecode-based traversals.
 * Changed `TraversalStrategy` application methodology to apply each strategy in turn to each level of the traversal hierarchy starting from root down to children.
 * Prevented more than one `Client` from connecting to the same Gremlin Server session.
 * Bumped to Jackson 2.10.x.
diff --git a/docker/Dockerfile b/docker/Dockerfile
index d997be6..fe657e7 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -38,7 +38,9 @@ RUN apt-get install -y --force-yes dotnet-sdk-3.1 mono-devel
 # custom build and install 3.5.3 and upgrade pip along the way. this could be resolved by using bionic
 # but trying to keep all of our release branches on the same docker image and the older versions sorta
 # suit 3.3.x and 3.4.x
-RUN apt-get install -y --force-yes python python-dev python-pip build-essential checkinstall zlib1g-dev libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get install -y python python-dev python-pip build-essential checkinstall zlib1g-dev libreadline-gplv2-dev \
+    libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libkrb5-dev krb5-user
 RUN cd /opt && wget https://www.python.org/ftp/python/3.5.3/Python-3.5.3.tgz && tar -xvf Python-3.5.3.tgz
 RUN cd /opt/Python-3.5.3 && ./configure && make && make install
 RUN ln -sf /usr/bin/python3.5.3 /usr/bin/python3
diff --git a/docker/gremlin-server.sh b/docker/gremlin-server.sh
index 540bfbc..fc43f56 100755
--- a/docker/gremlin-server.sh
+++ b/docker/gremlin-server.sh
@@ -57,7 +57,8 @@ echo "Using Gremlin Server $GREMLIN_SERVER_VERSION"
 sed -e "s/GREMLIN_SERVER_VERSION\$/${GREMLIN_SERVER_VERSION}/" docker/gremlin-server/Dockerfile.template > Dockerfile
 
 docker build -t tinkerpop:${BUILD_TAG} .
-docker run ${TINKERPOP_TEST_DOCKER_OPTS} ${REMOVE_CONTAINER} -ti -v "${HOME}"/.groovy:/root/.groovy -v "${HOME}"/.m2:/root/.m2 tinkerpop:${BUILD_TAG} ${@}
+docker run ${TINKERPOP_TEST_DOCKER_OPTS} ${REMOVE_CONTAINER} -h gremlin-server-test -v "${HOME}"/.groovy:/root/.groovy \
+    -v "${HOME}"/.m2:/root/.m2 -v ${PROJECT_HOME}gremlin-test/target:/opt/gremlin-test -ti tinkerpop:${BUILD_TAG} ${@}
 
 status=$?
 popd > /dev/null
diff --git a/docker/gremlin-server/Dockerfile.template b/docker/gremlin-server/Dockerfile.template
index 07522ba..cbb0611 100644
--- a/docker/gremlin-server/Dockerfile.template
+++ b/docker/gremlin-server/Dockerfile.template
@@ -25,7 +25,7 @@ COPY docker/gremlin-server/docker-entrypoint.sh docker/gremlin-server/*.yaml /op
 RUN chmod 755 /opt/docker-entrypoint.sh
 ENV GREMLIN_SERVER_VERSION=GREMLIN_SERVER_VERSION
 
-EXPOSE 45940
+EXPOSE 45940-45942 4588
 
 ENTRYPOINT ["/opt/docker-entrypoint.sh"]
 CMD []
diff --git a/docker/gremlin-server/docker-entrypoint.sh b/docker/gremlin-server/docker-entrypoint.sh
index ae75116..83bf84a 100644
--- a/docker/gremlin-server/docker-entrypoint.sh
+++ b/docker/gremlin-server/docker-entrypoint.sh
@@ -19,18 +19,71 @@
 #
 
 TINKERPOP_HOME=/opt/gremlin-server
-
 cp /opt/test/scripts/* ${TINKERPOP_HOME}/scripts
 
 IP=$(hostname -i)
-echo "#######################"
+
+echo "#############################################################################"
 echo IP is $IP
-echo "#######################"
+echo
+echo Available Gremlin Server instances:
+echo "ws://${IP}:45940/gremlin with anonymous access"
+echo "ws://${IP}:45941/gremlin with basic authentication (stephen/password)"
+echo "ws://${IP}:45942/gremlin with kerberos authentication (stephen/password)"
+echo
+echo "See docker/gremlin-server/docker-entrypoints.sh for transcripts per GLV."
+echo "#############################################################################"
 
 cp *.yaml ${TINKERPOP_HOME}/conf/
 
 java -version
 
-/opt/gremlin-server/bin/gremlin-server.sh install org.apache.tinkerpop gremlin-python ${GREMLIN_SERVER_VERSION}
 /opt/gremlin-server/bin/gremlin-server.sh conf/gremlin-server-integration.yaml &
-exec /opt/gremlin-server/bin/gremlin-server.sh conf/gremlin-server-integration-secure.yaml
+
+/opt/gremlin-server/bin/gremlin-server.sh conf/gremlin-server-integration-secure.yaml &
+
+java -cp /opt/gremlin-test/gremlin-test-3.5.0-SNAPSHOT-jar-with-dependencies.jar \
+     -Dlog4j.configuration="file:/opt/gremlin-server/conf/log4j-server.properties" \
+     org.apache.tinkerpop.gremlin.server.KdcFixture /opt/gremlin-server &
+
+export JAVA_OPTIONS="-Xms512m -Xmx4096m -Djava.security.krb5.conf=/opt/gremlin-server/target/kdc/krb5.conf"
+exec /opt/gremlin-server/bin/gremlin-server.sh conf/gremlin-server-integration-krb5.yaml
+
+
+#######################################################################
+# Transcripts for connecting to gremlin-server-test using various GLV's
+#######################################################################
+#
+# cd ${APACHE_TINKERPOP}                              # first terminal: location of cloned gitrepo
+# docker/gremlin-server.sh
+
+# cd ${APACHE_TINKERPOP}                              # second terminal
+# export KRB5_CONFIG=`pwd`/docker/gremlin-server/krb5.conf
+# echo 'password' | kinit stephen
+# klist
+
+# Gremlin-Groovy
+# --------------
+# KRB5_OPTION="-Djava.security.krb5.conf=`pwd`/docker/gremlin-server/krb5.conf"
+# JAAS_OPTION="-Djava.security.auth.login.config=`pwd`/docker/gremlin-server/gremlin-console-jaas.conf"
+# export JAVA_OPTIONS="${KRB5_OPTION} ${JAAS_OPTION}"
+# cd gremlin-console/target/apache-tinkerpop-gremlin-console-3.x.y-SNAPSHOT-standalone
+# If necessary (versions 3.4.2-3.4.6): in bin/gremlin.sh replace JVM_OPTS+=( "${JAVA_OPTIONS}" ) by JVM_OPTS+=( ${JAVA_OPTIONS} )
+# bin/gremlin.sh
+# gremlin> cluster = Cluster.build("172.17.0.2").port(45940).create()
+# gremlin> cluster = Cluster.build("172.17.0.2").port(45941).credentials("stephen", "password").create()
+# gremlin> cluster = Cluster.build("172.17.0.2").port(45942).addContactPoint("gremlin-server-test").protocol("test-service").jaasEntry("GremlinConsole").create()
+# gremlin> g = traversal().withRemote(DriverRemoteConnection.using(cluster, "gmodern"))
+# gremlin> g.V()
+
+# Gremlin-Python
+# --------------
+# cd gremlin-python/target/python3
+# source env/bin/activate
+# python
+# >>> from gremlin_python.process.anonymous_traversal import traversal
+# >>> from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
+# >>> g = traversal().withRemote(DriverRemoteConnection('ws://172.17.0.2:45940/gremlin','gmodern'))
+# >>> g = traversal().withRemote(DriverRemoteConnection('ws://172.17.0.2:45941/gremlin','gmodern', username='stephen', password='password'))
+# >>> g = traversal().withRemote(DriverRemoteConnection('ws://172.17.0.2:45942/gremlin','gmodern', kerberized_service='test-service@gremlin-server-test'))
+# >>> g.V().toList()
diff --git a/gremlin-server/src/test/scripts/test-server-stop.groovy b/docker/gremlin-server/gremlin-console-jaas.conf
similarity index 65%
copy from gremlin-server/src/test/scripts/test-server-stop.groovy
copy to docker/gremlin-server/gremlin-console-jaas.conf
index 595d9bd..13b92bb 100644
--- a/gremlin-server/src/test/scripts/test-server-stop.groovy
+++ b/docker/gremlin-server/gremlin-console-jaas.conf
@@ -17,16 +17,8 @@
  * under the License.
  */
 
-if (Boolean.parseBoolean(skipTests)) return
-
-log.info("Tests for native ${executionName} complete")
-
-def server = project.getContextValue("gremlin.server")
-log.info("Shutting down $server")
-server.stop().join()
-
-def serverSecure = project.getContextValue("gremlin.server.secure")
-log.info("Shutting down $serverSecure")
-serverSecure.stop().join()
-
-log.info("All Gremlin Server instances are shutdown for ${executionName}")
\ No newline at end of file
+GremlinConsole {
+  com.sun.security.auth.module.Krb5LoginModule required
+  doNotPrompt=true
+  useTicketCache=true;
+};
diff --git a/docker/gremlin-server/gremlin-server-integration-krb5.yaml b/docker/gremlin-server/gremlin-server-integration-krb5.yaml
new file mode 100644
index 0000000..16bad46
--- /dev/null
+++ b/docker/gremlin-server/gremlin-server-integration-krb5.yaml
@@ -0,0 +1,66 @@
+# 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.
+
+host: 0.0.0.0
+port: 45942
+evaluationTimeout: 30000
+graphs: {
+  graph: conf/tinkergraph-empty.properties,
+  classic: conf/tinkergraph-empty.properties,
+  modern: conf/tinkergraph-empty.properties,
+  crew: conf/tinkergraph-empty.properties,
+  grateful: conf/tinkergraph-empty.properties,
+  sink: conf/tinkergraph-empty.properties}
+scriptEngines: {
+  gremlin-groovy: {
+    plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
+               org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
+               org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: {expectedCompilationTime: 30000},
+               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
+               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-all.groovy]}}}}
+serializers:
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV1d0], custom: [groovy.json.JsonBuilder;org.apache.tinkerpop.gremlin.driver.ser.JsonBuilderGryoSerializer]}}
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3d0], custom: [groovy.json.JsonBuilder;org.apache.tinkerpop.gremlin.driver.ser.JsonBuilderGryoSerializer]}}
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoLiteMessageSerializerV1d0, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV1d0], custom: [groovy.json.JsonBuilder;org.apache.tinkerpop.gremlin.driver.ser.JsonBuilderGryoSerializer]}}
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0, config: { serializeResultToString: true}}
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { serializeResultToString: true}}
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3d0] }}
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV2d0, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV2d0] }}
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerGremlinV1d0, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV1d0] }}
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1 }
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }}
+processors:
+  - { className: org.apache.tinkerpop.gremlin.server.op.session.SessionOpProcessor, config: { sessionTimeout: 28800000 }}
+  - { className: org.apache.tinkerpop.gremlin.server.op.standard.StandardOpProcessor, config: {}}
+metrics: {
+  slf4jReporter: {enabled: true, interval: 180000}}
+strictTransactionManagement: false
+idleConnectionTimeout: 0
+keepAliveInterval: 0
+maxInitialLineLength: 4096
+maxHeaderSize: 8192
+maxChunkSize: 8192
+maxContentLength: 1000000
+maxAccumulationBufferComponents: 1024
+resultIterationBatchSize: 64
+writeBufferLowWaterMark: 32768
+writeBufferHighWaterMark: 65536
+authentication: {
+  authenticator: org.apache.tinkerpop.gremlin.server.auth.Krb5Authenticator,
+  config: {
+    principal: test-service/gremlin-server-test@TEST.COM,
+    keytab: /opt/gremlin-server/target/kdc/test-service.keytab}}
diff --git a/docker/gremlin-server/krb5.conf b/docker/gremlin-server/krb5.conf
new file mode 100644
index 0000000..3f9c322
--- /dev/null
+++ b/docker/gremlin-server/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 = TEST.COM
+    default_realm = TEST.COM
+    udp_preference_limit = 4096
+    kdc_tcp_port = 4588
+    kdc_udp_port = 4588
+
+[realms]
+    TEST.COM = {
+        kdc = 172.17.0.2:4588
+    }
diff --git a/docs/src/reference/gremlin-applications.asciidoc b/docs/src/reference/gremlin-applications.asciidoc
index e1df173..2bad34f 100644
--- a/docs/src/reference/gremlin-applications.asciidoc
+++ b/docs/src/reference/gremlin-applications.asciidoc
@@ -1077,7 +1077,8 @@ Gremlin-Console |PLAIN SASL (username/password) |3.0.0-incubating
 |Pluggable SASL |3.0.0-incubating
 |GSSAPI SASL (Kerberos) |3.3.0
 |Gremlin.NET |PLAIN SASL |3.3.0
-|Gremlin-Python |PLAIN SASL |3.2.2
+1.2+v|Gremlin-Python |PLAIN SASL |3.2.2
+|GSSAPI SASL (Kerberos) |3.4.7
 |Gremlin.Net |PLAIN SASL |3.2.7
 |Gremlin-Javascript |PLAIN SASL |3.3.0
 |=========================================================
@@ -1257,14 +1258,22 @@ authentication: {
     principal: gremlinserver/hostname.your.org@YOUR.REALM,
     keytab: /etc/security/keytabs/gremlinserver.service.keytab}}
 
-Krb5Authenticator needs a Kerberos service principal and a keytab that holds the secret key for that principal. The keytab
-location and service name, e.g. gremlinserver, are free to be chosen, but Gremlin clients have to specify this service name
-as the `protocol`. For Gremlin-Console the `protocol` is an entry in the remote.yaml file, for Gremlin-java the client builder
-has a `protocol()` method.
+`Krb5Authenticator` needs a Kerberos service principal and a keytab that holds the secret key for that principal. The keytab
+location and service name, e.g. gremlinserver, are free to be chosen. In addition, if the krb5.conf kerberos
+configuration file is not available from the
+https://web.mit.edu/kerberos/krb5-devel/doc/mitK5defaults.html[default location], `Krb5Authenticator` needs the location
+of the krb5.conf configuration file, to be specified as a system property in the JAVA_OPTIONS environment variable
+of Gremlin Server:
+
+[source, bash]
+export JAVA_OPTIONS="${JAVA_OPTIONS} -Xms512m -Xmx4096m -Djava.security.krb5.conf=/etc/krb5.conf"
+
+Gremlin clients have to specify the service name as the `protocol` connection parameter. For Gremlin-Console the
+`protocol` is an entry in the remote.yaml file, for Gremlin-java the client builder has a `protocol()` method.
 
 In addition to the `protocol`, the Gremlin client needs to specify a `jaasEntry`, an entry in the
-link:https://en.wikipedia.org/wiki/Java_Authentication_and_Authorization_Service[JAAS] configuration file. Gremlin-Console
-comes with a sample gremlin-jaas.conf file with a `GremlinConsole` jaasEntry:
+link:https://en.wikipedia.org/wiki/Java_Authentication_and_Authorization_Service[JAAS] configuration file. As a
+start one can define a conf/gremlin-jaas.conf file with a `GremlinConsole` jaasEntry:
 
 [source, jaas]
 GremlinConsole {
@@ -1273,15 +1282,20 @@ GremlinConsole {
   useTicketCache=true;
 };
 
-This configuration tells Gremlin-Console to pass authentication requests from gremlin-server to the Krb5LoginModule, which is
-part of the java standard library.  The Krb5LoginModule does not prompt the user for a username and password but uses the ticket cache that
-is normally refreshed when a user logs in to a host within the Kerberos realm.
+This configuration tells Gremlin Console to pass authentication requests from Gremlin Server to the Krb5LoginModule, which is
+part of the java standard library. The Krb5LoginModule does not prompt the user for a username and password but uses the
+ticket cache that is normally refreshed when a user logs in to a host within the Kerberos realm.
 
-Finally, the Gremlin client needs the location of the JAAS configuration file to be passed as a system property to the JVM. For
-Gremlin-Console the easiest way to do this is to pass it to the run script via the JAVA_OPTIONS environment property:
+The Gremlin client needs the location of the JAAS configuration file to be passed as a system property to the JVM. For
+Gremlin-Console the easiest way to do this is to pass it to the run script via the JAVA_OPTIONS environment property.
+If the krb5.conf kerberos configuration file is not available from the
+https://web.mit.edu/kerberos/krb5-devel/doc/mitK5defaults.html[default location] it has to be provided as a system
+property as well:
 
 [source, bash]
-export JAVA_OPTIONS="$JAVA_OPTIONS -Djava.security.auth.login.config=conf/gremlin-jaas.conf"
+JAAS_OPTION="-Djava.security.auth.login.config=conf/gremlin-jaas.conf"
+KRB5_OPTION="-Djava.security.krb5.conf=/etc/krb5.conf"
+export JAVA_OPTIONS="${JAVA_OPTIONS} ${KRB5_OPTION} ${JAAS_OPTION}"
 
 [[script-execution]]
 ===== Protecting Script Execution
diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc
index 92cb327..96bc942 100644
--- a/docs/src/reference/gremlin-variants.asciidoc
+++ b/docs/src/reference/gremlin-variants.asciidoc
@@ -642,6 +642,7 @@ To install Gremlin-Python, use Python's link:https://en.wikipedia.org/wiki/Pip_(
 [source,bash]
 ----
 pip install gremlinpython
+pip install gremlinpython[kerberos]     # Optional, not available on Microsoft Windows
 ----
 
 === Connecting
@@ -660,9 +661,36 @@ to the `DriverRemoteConnection` constructor.
 
 [source,python]
 ----
-g = traversal().withRemote(DriverRemoteConnection('ws://localhost:8182/gremlin','g',headers={'Header':'Value'}))
+g = traversal().withRemote(DriverRemoteConnection(
+    'ws://localhost:8182/gremlin', 'g', headers={'Header':'Value'}))
 ----
 
+Gremlin-Python supports plain text and Kerberos SASL authentication, you can set it on the connection options.
+
+[source,python]
+----
+# Plain text authentication
+g = traversal().withRemote(DriverRemoteConnection(
+    'ws://localhost:8182/gremlin', 'g', username='stephen', password='password'))
+
+# Kerberos authentication
+g = traversal().withRemote(DriverRemoteConnection(
+    'ws://localhost:8182/gremlin', 'g', kerberized_service='gremlin'))
+----
+
+The value specified for the kerberized_service should correspond to the first part of the principal name configured for
+the gremlin service. The Gremlin-Python client reads the kerberos configurations from your system.
+It finds the KDC's hostname and port from the krb5.conf file at the
+https://web.mit.edu/kerberos/krb5-devel/doc/mitK5defaults.html[default location] or as indicated in the KRB5_CONFIG
+environment variable. It finds credentials from the credential cache or a keytab file at the
+https://web.mit.edu/kerberos/krb5-devel/doc/mitK5defaults.html[default locations] or as indicated
+in the KRB5CCNAME or KRB5_KTNAME environment variables.
+
+If you authenticate to a remote <<connecting-gremlin-server,Gremlin Server>> or
+<<connecting-rgp,Remote Gremlin Provider>>, this server normally has SSL activated and the websockets url will start
+with 'wss://'. If Gremlin-Server uses a self-signed certificate for SSL, Gremlin-Python needs access to a local copy of
+the CA certificate file (in openssl .pem format), to be specified in the SSL_CERT_FILE environment variable.
+
 [[python-imports]]
 === Common Imports
 
@@ -737,6 +765,7 @@ can be passed to the `Client` or `DriverRemoteConnection` instance as keyword ar
 |message_serializer |The message serializer implementation.|`gremlin_python.driver.serializer.GraphSONMessageSerializer`
 |password |The password to submit on requests that require authentication. |""
 |username |The username to submit on requests that require authentication. |""
+|kerberized_service |the first part of the principal name configured for the gremlin service|"""
 |session | A unique string-based identifier (typically a UUID) to enable a <<sessions,session-based connection>>. This is not a valid configuration for `DriverRemoteConnection`. |None
 |=========================================================
 
diff --git a/gremlin-dotnet/pom.xml b/gremlin-dotnet/pom.xml
index 02f6ce8..75b6c52 100644
--- a/gremlin-dotnet/pom.xml
+++ b/gremlin-dotnet/pom.xml
@@ -69,6 +69,11 @@ limitations under the License.
                         <version>${project.version}</version>
                     </dependency>
                     <dependency>
+                        <groupId>org.apache.tinkerpop</groupId>
+                        <artifactId>gremlin-test</artifactId>
+                        <version>${project.version}</version>
+                    </dependency>
+                    <dependency>
                         <groupId>org.codehaus.groovy</groupId>
                         <artifactId>groovy-all</artifactId>
                         <version>${groovy.version}</version>
diff --git a/gremlin-javascript/pom.xml b/gremlin-javascript/pom.xml
index 199af86..1e64cd3 100644
--- a/gremlin-javascript/pom.xml
+++ b/gremlin-javascript/pom.xml
@@ -49,6 +49,11 @@ limitations under the License.
                         <version>${project.version}</version>
                     </dependency>
                     <dependency>
+                        <groupId>org.apache.tinkerpop</groupId>
+                        <artifactId>gremlin-test</artifactId>
+                        <version>${project.version}</version>
+                    </dependency>
+                    <dependency>
                         <groupId>log4j</groupId>
                         <artifactId>log4j</artifactId>
                         <version>${log4j.version}</version>
diff --git a/gremlin-python/pom.xml b/gremlin-python/pom.xml
index 89b9e6c..4715e64 100644
--- a/gremlin-python/pom.xml
+++ b/gremlin-python/pom.xml
@@ -135,7 +135,7 @@ limitations under the License.
                                         </exec>
                                         <exec dir="${project.build.directory}/python3" executable="env/bin/pip"
                                               failonerror="true">
-                                            <arg line="install wheel radish-bdd PyHamcrest aenum isodate"/>
+                                            <arg line="install wheel radish-bdd PyHamcrest aenum isodate kerberos six tornado"/>
                                         </exec>
                                         <copy todir="${project.build.directory}/python-packaged">
                                             <fileset dir="src/main/python"/>
@@ -208,6 +208,8 @@ limitations under the License.
                                         <exec executable="env/bin/python" dir="${project.build.directory}/python3"
                                               failonerror="true">
                                             <env key="PYTHONPATH" value=""/>
+                                            <env key="KRB5_CONFIG" value="${project.build.directory}/kdc/krb5.conf"/>
+                                            <env key="KRB5CCNAME" value="${project.build.directory}/kdc/test-tkt.cc"/>
                                             <arg line="setup.py test"/>
                                         </exec>
                                         <!-- radish seems to like all dependencies in place -->
@@ -245,6 +247,11 @@ limitations under the License.
                                 <version>${project.version}</version>
                             </dependency>
                             <dependency>
+                                <groupId>org.apache.tinkerpop</groupId>
+                                <artifactId>gremlin-test</artifactId>
+                                <version>${project.version}</version>
+                            </dependency>
+                            <dependency>
                                 <groupId>org.codehaus.groovy</groupId>
                                 <artifactId>groovy-all</artifactId>
                                 <version>${groovy.version}</version>
diff --git a/gremlin-python/src/main/python/gremlin_python/driver/client.py b/gremlin-python/src/main/python/gremlin_python/driver/client.py
index 15590bc..c1c2f35 100644
--- a/gremlin-python/src/main/python/gremlin_python/driver/client.py
+++ b/gremlin-python/src/main/python/gremlin_python/driver/client.py
@@ -39,7 +39,7 @@ class Client:
     def __init__(self, url, traversal_source, protocol_factory=None,
                  transport_factory=None, pool_size=None, max_workers=None,
                  message_serializer=None, username="", password="",
-                 headers=None, session=""):
+                 kerberized_service="", headers=None, session=""):
         self._url = url
         self._headers = headers
         self._traversal_source = traversal_source
@@ -64,7 +64,8 @@ class Client:
             protocol_factory = lambda: protocol.GremlinServerWSProtocol(
                 self._message_serializer,
                 username=self._username,
-                password=self._password)
+                password=self._password,
+                kerberized_service=kerberized_service)
         self._protocol_factory = protocol_factory
         if self._sessionEnabled:
             if pool_size is None:
diff --git a/gremlin-python/src/main/python/gremlin_python/driver/driver_remote_connection.py b/gremlin-python/src/main/python/gremlin_python/driver/driver_remote_connection.py
index 9192835..2b617b3 100644
--- a/gremlin-python/src/main/python/gremlin_python/driver/driver_remote_connection.py
+++ b/gremlin-python/src/main/python/gremlin_python/driver/driver_remote_connection.py
@@ -29,9 +29,9 @@ class DriverRemoteConnection(RemoteConnection):
 
     def __init__(self, url, traversal_source, protocol_factory=None,
                  transport_factory=None, pool_size=None, max_workers=None,
-                 username="", password="", message_serializer=None,
-                 graphson_reader=None, graphson_writer=None,
-                 headers=None):
+                 username="", password="", kerberized_service='',
+                 message_serializer=None, graphson_reader=None,
+                 graphson_writer=None, headers=None):
         if message_serializer is None:
             message_serializer = serializer.GraphSONMessageSerializer(
                 reader=graphson_reader,
@@ -44,6 +44,7 @@ class DriverRemoteConnection(RemoteConnection):
                                      message_serializer=message_serializer,
                                      username=username,
                                      password=password,
+                                     kerberized_service=kerberized_service,
                                      headers=headers)
         self._url = self._client._url
         self._traversal_source = self._client._traversal_source
diff --git a/gremlin-python/src/main/python/gremlin_python/driver/protocol.py b/gremlin-python/src/main/python/gremlin_python/driver/protocol.py
index ad0d467..ea8f625 100644
--- a/gremlin-python/src/main/python/gremlin_python/driver/protocol.py
+++ b/gremlin-python/src/main/python/gremlin_python/driver/protocol.py
@@ -18,7 +18,9 @@
 #
 import abc
 import base64
+import struct
 
+# import kerberos    Optional dependency imported in relevant codeblock
 import six
 
 try:
@@ -26,7 +28,7 @@ try:
 except ImportError:
     import json
 
-from gremlin_python.driver import serializer, request
+from gremlin_python.driver import request
 from gremlin_python.driver.resultset import ResultSet
 
 __author__ = 'David M. Brown (davebshow@gmail.com)'
@@ -34,15 +36,19 @@ __author__ = 'David M. Brown (davebshow@gmail.com)'
 
 class GremlinServerError(Exception):
     def __init__(self, status):
-        super(GremlinServerError, self).__init__("{0}: {1}".format(status["code"], status["message"]))
-        self._status_attributes = status["attributes"]
-        self.status_code = status["code"]
+        super(GremlinServerError, self).__init__('{0}: {1}'.format(status['code'], status['message']))
+        self._status_attributes = status['attributes']
+        self.status_code = status['code']
 
     @property
     def status_attributes(self):
         return self._status_attributes
 
 
+class ConfigurationError(Exception):
+    pass
+
+
 @six.add_metaclass(abc.ABCMeta)
 class AbstractBaseProtocol:
 
@@ -51,7 +57,7 @@ class AbstractBaseProtocol:
         self._transport = transport
 
     @abc.abstractmethod
-    def data_received(self, message):
+    def data_received(self, message, results_dict):
         pass
 
     @abc.abstractmethod
@@ -61,10 +67,15 @@ class AbstractBaseProtocol:
 
 class GremlinServerWSProtocol(AbstractBaseProtocol):
 
-    def __init__(self, message_serializer, username='', password=''):
+    MAX_CONTENT_LENGTH = 65536
+    QOP_AUTH_BIT = 1
+    _kerberos_context = None
+
+    def __init__(self, message_serializer, username='', password='', kerberized_service=''):
         self._message_serializer = message_serializer
         self._username = username
         self._password = password
+        self._kerberized_service = kerberized_service
 
     def connection_made(self, transport):
         super(GremlinServerWSProtocol, self).connection_made(transport)
@@ -77,9 +88,9 @@ class GremlinServerWSProtocol(AbstractBaseProtocol):
     def data_received(self, message, results_dict):
         # if Gremlin Server cuts off then we get a None for the message
         if message is None:
-            raise GremlinServerError({'code': 500, 
-                                      'message': 'Server disconnected - please try to reconnect', 'attributes': {}})
-
+            raise GremlinServerError(
+                {'code': 500, 'message': 'Server disconnected - please try to reconnect',
+                 'attributes': {}})
         message = self._message_serializer.deserialize_message(message)
         request_id = message['requestId']
         result_set = results_dict[request_id] if request_id in results_dict else ResultSet(None, None)
@@ -88,14 +99,22 @@ class GremlinServerWSProtocol(AbstractBaseProtocol):
         data = message['result']['data']
         result_set.aggregate_to = aggregate_to
         if status_code == 407:
-            auth = b''.join([b'\x00', self._username.encode('utf-8'),
-                             b'\x00', self._password.encode('utf-8')])
-            request_message = request.RequestMessage(
-                'traversal', 'authentication',
-                {'sasl': base64.b64encode(auth).decode()})
+            if self._username and self._password:
+                auth_bytes = b''.join([b'\x00', self._username.encode('utf-8'),
+                                       b'\x00', self._password.encode('utf-8')])
+                auth = base64.b64encode(auth_bytes)
+                request_message = request.RequestMessage(
+                    'traversal', 'authentication', {'sasl': auth.decode()})
+            elif self._kerberized_service:
+                request_message = self._kerberos_received(message)
+            else:
+                raise ConfigurationError(
+                    'Gremlin server requires authentication credentials in DriverRemoteConnection.'
+                    'For basic authentication provide username and password. '
+                    'For kerberos authentication provide the kerberized_service parameter.')
             self.write(request_id, request_message)
             data = self._transport.read()
-            # Allow recursive call for auth
+            # Allow for auth handshake with multiple steps
             return self.data_received(data, results_dict)
         elif status_code == 204:
             result_set.stream.put_nowait([])
@@ -109,4 +128,58 @@ class GremlinServerWSProtocol(AbstractBaseProtocol):
             return status_code
         else:
             del results_dict[request_id]
-            raise GremlinServerError(message["status"])
+            raise GremlinServerError(message['status'])
+
+    def _kerberos_received(self, message):
+        # Inspired by: https://github.com/thobbs/pure-sasl/blob/0.6.2/puresasl/mechanisms.py
+        #              https://github.com/thobbs/pure-sasl/blob/0.6.2/LICENSE
+        try:
+            import kerberos
+        except ImportError:
+            raise ImportError('Please install gremlinpython[kerberos].')
+
+        # First pass: get service granting ticket and return it to gremlin-server
+        if not self._kerberos_context:
+            try:
+                _, kerberos_context = kerberos.authGSSClientInit(
+                    self._kerberized_service, gssflags=kerberos.GSS_C_MUTUAL_FLAG)
+                kerberos.authGSSClientStep(kerberos_context, '')
+                auth = kerberos.authGSSClientResponse(kerberos_context)
+                self._kerberos_context = kerberos_context
+            except kerberos.KrbError as e:
+                raise ConfigurationError(
+                    'Kerberos authentication requires a valid service name in DriverRemoteConnection, '
+                    'as well as a valid tgt (export KRB5CCNAME) or keytab (export KRB5_KTNAME): ' + str(e))
+            return request.RequestMessage('', 'authentication', {'sasl': auth})
+
+        # Second pass: completion of authentication
+        sasl_response = message['status']['attributes']['sasl']
+        if not self._username:
+            result_code = kerberos.authGSSClientStep(self._kerberos_context, sasl_response)
+            if result_code == kerberos.AUTH_GSS_COMPLETE:
+                self._username = kerberos.authGSSClientUserName(self._kerberos_context)
+            return request.RequestMessage('', 'authentication', {'sasl': ''})
+
+        # Third pass: sasl quality of protection (qop) handshake
+
+        # Gremlin-server Krb5Authenticator only supports qop=QOP_AUTH; use ssl for confidentiality.
+        # Handshake content format:
+        # byte 0: the selected qop. 1==auth, 2==auth-int, 4==auth-conf
+        # byte 1-3: the max length for any buffer sent back and forth on this connection. (big endian)
+        # the rest of the buffer: the authorization user name in UTF-8 - not null terminated.
+        kerberos.authGSSClientUnwrap(self._kerberos_context, sasl_response)
+        data = kerberos.authGSSClientResponse(self._kerberos_context)
+        plaintext_data = base64.b64decode(data)
+        assert len(plaintext_data) == 4, "Unexpected response from gremlin server sasl handshake"
+        word, = struct.unpack('!I', plaintext_data)
+        qop_bits = word >> 24
+        assert self.QOP_AUTH_BIT & qop_bits, "Unexpected sasl qop level received from gremlin server"
+
+        name_length = len(self._username)
+        fmt = '!I' + str(name_length) + 's'
+        word = self.QOP_AUTH_BIT << 24 | self.MAX_CONTENT_LENGTH
+        out = struct.pack(fmt, word, self._username.encode("utf-8"),)
+        encoded = base64.b64encode(out).decode('ascii')
+        kerberos.authGSSClientWrap(self._kerberos_context, encoded)
+        auth = kerberos.authGSSClientResponse(self._kerberos_context)
+        return request.RequestMessage('', 'authentication', {'sasl': auth})
diff --git a/gremlin-python/src/main/python/setup.py b/gremlin-python/src/main/python/setup.py
index c53bca3..b7b57de 100644
--- a/gremlin-python/src/main/python/setup.py
+++ b/gremlin-python/src/main/python/setup.py
@@ -48,7 +48,7 @@ install_requires = [
     'aenum>=1.4.5,<3.0.0',
     'tornado>=4.4.1,<6.0',
     'six>=1.10.0,<2.0.0',
-    'isodate>=0.6.0,<1.0.0'
+    'isodate>=0.6.0,<1.0.0',
 ]
 
 if sys.version_info < (3, 2):
@@ -80,6 +80,9 @@ setup(
         'PyHamcrest>=1.9.0,<2.0.0'
     ],
     install_requires=install_requires,
+    extra_require={
+        'kerberos': 'kerberos>=1.3.0,<2.0.0'    # Does not install in Microsoft Windows
+    },
     classifiers=[
         "Intended Audience :: Developers",
         "License :: OSI Approved :: Apache Software License",
diff --git a/gremlin-python/src/main/python/tests/conftest.py b/gremlin-python/src/main/python/tests/conftest.py
index cc3855e..a5bbbcc 100644
--- a/gremlin-python/src/main/python/tests/conftest.py
+++ b/gremlin-python/src/main/python/tests/conftest.py
@@ -16,8 +16,10 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+
 import concurrent.futures
 import pytest
+import socket
 
 from six.moves import queue
 
@@ -32,9 +34,11 @@ from gremlin_python.driver.serializer import (
     GraphBinarySerializersV1)
 from gremlin_python.driver.tornado.transport import TornadoTransport
 
-# docker Gremlin Server = 172.17.0.2
-gremlin_server_host = "localhost"
-gremlin_server_url = 'ws://' + gremlin_server_host + ':45940/gremlin'
+gremlin_server_url = 'ws://localhost:{}/gremlin'
+anonymous_url = gremlin_server_url.format(45940)
+basic_url = gremlin_server_url.format(45941)
+kerberos_url = gremlin_server_url.format(45942)
+kerberized_service = 'test-service@{}'.format(socket.gethostname())
 
 
 @pytest.fixture
@@ -45,7 +49,7 @@ def connection(request):
     executor = concurrent.futures.ThreadPoolExecutor(5)
     pool = queue.Queue()
     try:
-        conn = Connection(gremlin_server_url, 'gmodern', protocol,
+        conn = Connection(anonymous_url, 'gmodern', protocol,
                           lambda: TornadoTransport(), executor, pool)
     except OSError:
         executor.shutdown()
@@ -61,7 +65,7 @@ def connection(request):
 @pytest.fixture
 def client(request):
     try:
-        client = Client(gremlin_server_url, 'gmodern')
+        client = Client(anonymous_url, 'gmodern')
     except OSError:
         pytest.skip('Gremlin Server is not running')
     else:
@@ -71,11 +75,15 @@ def client(request):
         return client
 
 
-@pytest.fixture
-def secure_client(request):
+@pytest.fixture(params=['basic', 'kerberos'])
+def authenticated_client(request):
     try:
-        client = Client('ws://' + gremlin_server_host + ':45941/gremlin', 'gmodern',
-                        username='stephen', password='password')
+        if request.param == 'basic':
+            client = Client(basic_url, 'gmodern', username='stephen', password='password')
+        elif request.param == 'kerberos':
+            client = Client(kerberos_url, 'gmodern', kerberized_service=kerberized_service)
+        else:
+            raise ValueError("Invalid authentication option - " + request.param)
     except OSError:
         pytest.skip('Gremlin Server is not running')
     else:
@@ -89,13 +97,13 @@ def secure_client(request):
 def remote_connection(request):
     try:
         if request.param == 'graphbinaryv1':
-            remote_conn = DriverRemoteConnection(gremlin_server_url, 'gmodern',
+            remote_conn = DriverRemoteConnection(anonymous_url, 'gmodern',
                                                  message_serializer=serializer.GraphBinarySerializersV1())
         elif request.param == 'graphsonv2':
-            remote_conn = DriverRemoteConnection(gremlin_server_url, 'gmodern',
+            remote_conn = DriverRemoteConnection(anonymous_url, 'gmodern',
                                                  message_serializer=serializer.GraphSONSerializersV2d0())
         elif request.param == 'graphsonv3':
-            remote_conn = DriverRemoteConnection(gremlin_server_url, 'gmodern',
+            remote_conn = DriverRemoteConnection(anonymous_url, 'gmodern',
                                                  message_serializer=serializer.GraphSONSerializersV3d0())
         else:
             raise ValueError("Invalid serializer option - " + request.param)
@@ -108,10 +116,31 @@ def remote_connection(request):
         return remote_conn
 
 
+@pytest.fixture(params=['basic', 'kerberos'])
+def remote_connection_authenticated(request):
+    try:
+        if request.param == 'basic':
+            remote_conn = DriverRemoteConnection(basic_url, 'gmodern',
+                                                 username='stephen', password='password',
+                                                 message_serializer=serializer.GraphSONSerializersV2d0())
+        elif request.param == 'kerberos':
+            remote_conn = DriverRemoteConnection(kerberos_url, 'gmodern', kerberized_service=kerberized_service,
+                                                 message_serializer=serializer.GraphSONSerializersV2d0())
+        else:
+            raise ValueError("Invalid authentication option - " + request.param)
+    except OSError:
+        pytest.skip('Gremlin Server is not running')
+    else:
+        def fin():
+            remote_conn.close()
+        request.addfinalizer(fin)
+        return remote_conn
+
+
 @pytest.fixture
-def remote_connection_v2(request):
+def remote_connection_graphsonV2(request):
     try:
-        remote_conn = DriverRemoteConnection(gremlin_server_url, 'g',
+        remote_conn = DriverRemoteConnection(anonymous_url, 'g',
                                              message_serializer=serializer.GraphSONSerializersV2d0())
     except OSError:
         pytest.skip('Gremlin Server is not running')
diff --git a/gremlin-python/src/main/python/tests/driver/test_client.py b/gremlin-python/src/main/python/tests/driver/test_client.py
index 46eb6e0..2d951cf 100644
--- a/gremlin-python/src/main/python/tests/driver/test_client.py
+++ b/gremlin-python/src/main/python/tests/driver/test_client.py
@@ -16,10 +16,8 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-import pytest
-
 import uuid
-from gremlin_python.driver.protocol import GremlinServerError
+
 from gremlin_python.driver.client import Client
 from gremlin_python.driver.protocol import GremlinServerError
 from gremlin_python.driver.request import RequestMessage
@@ -242,11 +240,11 @@ def test_big_result_set(client):
     assert len(results) == 10000
 
 
-def test_big_result_set_secure(secure_client):
+def test_big_result_set_secure(authenticated_client):
     g = Graph().traversal()
     t = g.inject(1).repeat(__.addV('person').property('name', __.loops())).times(20000).count()
     message = RequestMessage('traversal', 'bytecode', {'gremlin': t.bytecode, 'aliases': {'g': 'g'}})
-    result_set = secure_client.submit(message)
+    result_set = authenticated_client.submit(message)
     results = []
     for result in result_set:
         results += result
@@ -254,7 +252,7 @@ def test_big_result_set_secure(secure_client):
 
     t = g.V().limit(10)
     message = RequestMessage('traversal', 'bytecode', {'gremlin': t.bytecode, 'aliases': {'g': 'g'}})
-    result_set = secure_client.submit(message)
+    result_set = authenticated_client.submit(message)
     results = []
     for result in result_set:
         results += result
@@ -262,7 +260,7 @@ def test_big_result_set_secure(secure_client):
 
     t = g.V().limit(100)
     message = RequestMessage('traversal', 'bytecode', {'gremlin': t.bytecode, 'aliases': {'g': 'g'}})
-    result_set = secure_client.submit(message)
+    result_set = authenticated_client.submit(message)
     results = []
     for result in result_set:
         results += result
@@ -270,7 +268,7 @@ def test_big_result_set_secure(secure_client):
 
     t = g.V().limit(1000)
     message = RequestMessage('traversal', 'bytecode', {'gremlin': t.bytecode, 'aliases': {'g': 'g'}})
-    result_set = secure_client.submit(message)
+    result_set = authenticated_client.submit(message)
     results = []
     for result in result_set:
         results += result
@@ -278,7 +276,7 @@ def test_big_result_set_secure(secure_client):
 
     t = g.V().limit(10000)
     message = RequestMessage('traversal', 'bytecode', {'gremlin': t.bytecode, 'aliases': {'g': 'g'}})
-    result_set = secure_client.submit(message)
+    result_set = authenticated_client.submit(message)
     results = []
     for result in result_set:
         results += result
diff --git a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
index 1350b1d..45194de 100644
--- a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
+++ b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
@@ -16,15 +16,10 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-import pytest
-
-from tornado import ioloop, gen
 
 from gremlin_python import statics
 from gremlin_python.driver.protocol import GremlinServerError
 from gremlin_python.statics import long
-from gremlin_python.driver.driver_remote_connection import (
-    DriverRemoteConnection)
 from gremlin_python.process.traversal import Traverser
 from gremlin_python.process.traversal import TraversalStrategy
 from gremlin_python.process.traversal import Bindings
@@ -100,7 +95,6 @@ class TestDriverRemoteConnection(object):
         results = g.V().has('person', 'age', Bindings.of('x', lt(30))).count().next()
         assert 2 == results
 
-
     def test_lambda_traversals(self, remote_connection):
         statics.load_statics(globals())
         assert "remoteconnection[ws://localhost:45940/gremlin,gmodern]" == str(remote_connection)
@@ -200,4 +194,10 @@ class TestDriverRemoteConnection(object):
         t = g.V().count()
         assert 6 == t.next()
         assert 6 == t.clone().next()
-        assert 6 == t.clone().next()
\ No newline at end of file
+        assert 6 == t.clone().next()
+
+    def test_authenticated(self, remote_connection_authenticated):
+        statics.load_statics(globals())
+        g = traversal().withRemote(remote_connection_authenticated)
+
+        assert long(6) == g.V().count().toList()[0]
diff --git a/gremlin-python/src/main/python/tests/structure/io/test_graphsonV2d0.py b/gremlin-python/src/main/python/tests/structure/io/test_graphsonV2d0.py
index 32d2c04..83ec3d7 100644
--- a/gremlin-python/src/main/python/tests/structure/io/test_graphsonV2d0.py
+++ b/gremlin-python/src/main/python/tests/structure/io/test_graphsonV2d0.py
@@ -475,8 +475,8 @@ class TestGraphSONWriter(object):
 class TestFunctionalGraphSONIO(object):
     """Functional IO tests"""
 
-    def test_timestamp(self, remote_connection_v2):
-        g = Graph().traversal().withRemote(remote_connection_v2)
+    def test_timestamp(self, remote_connection_graphsonV2):
+        g = Graph().traversal().withRemote(remote_connection_graphsonV2)
         ts = timestamp(1481750076295 / 1000)
         resp = g.addV('test_vertex').property('ts', ts)
         resp = resp.toList()
@@ -490,8 +490,8 @@ class TestFunctionalGraphSONIO(object):
         finally:
             g.V(vid).drop().iterate()
 
-    def test_datetime(self, remote_connection_v2):
-        g = Graph().traversal().withRemote(remote_connection_v2)
+    def test_datetime(self, remote_connection_graphsonV2):
+        g = Graph().traversal().withRemote(remote_connection_graphsonV2)
         dt = datetime.datetime.utcfromtimestamp(1481750076295 / 1000)
         resp = g.addV('test_vertex').property('dt', dt).toList()
         vid = resp[0].id
@@ -504,8 +504,8 @@ class TestFunctionalGraphSONIO(object):
         finally:
             g.V(vid).drop().iterate()
 
-    def test_uuid(self, remote_connection_v2):
-        g = Graph().traversal().withRemote(remote_connection_v2)
+    def test_uuid(self, remote_connection_graphsonV2):
+        g = Graph().traversal().withRemote(remote_connection_graphsonV2)
         uid = uuid.UUID("41d2e28a-20a4-4ab0-b379-d810dede3786")
         resp = g.addV('test_vertex').property('uuid', uid).toList()
         vid = resp[0].id
diff --git a/gremlin-server/pom.xml b/gremlin-server/pom.xml
index 7fe9bdb..194c09d 100644
--- a/gremlin-server/pom.xml
+++ b/gremlin-server/pom.xml
@@ -100,12 +100,6 @@ limitations under the License.
             <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>org.apache.kerby</groupId>
-            <artifactId>kerb-simplekdc</artifactId>
-            <version>2.0.0</version>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
     <build>
         <directory>${basedir}/target</directory>
@@ -156,7 +150,6 @@ limitations under the License.
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
             </plugin>
-
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-failsafe-plugin</artifactId>
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/auth/Krb5Authenticator.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/auth/Krb5Authenticator.java
index 0cadc99..f62c121 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/auth/Krb5Authenticator.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/auth/Krb5Authenticator.java
@@ -72,7 +72,7 @@ public class Krb5Authenticator implements Authenticator {
             principalName = (String) config.get(PRINCIPAL_KEY);
             subject = JaasKrbUtil.loginUsingKeytab(principalName, keytabFile);
         } catch (Exception e) {
-            logger.warn("Failed to login to kdc");
+            logger.warn("Failed to login to kdc:" + e.getMessage());
         }
         
         logger.debug("Done logging in to kdc");
@@ -110,7 +110,7 @@ public class Krb5Authenticator implements Authenticator {
                 //   https://docs.oracle.com/javase/8/docs/technotes/guides/security/sasl/sasl-refguide.html#SERVER
                 // Rely on GSSAPI defaults for Sasl.MAX_BUFFER and Sasl.QOP. Note, however, that gremlin-driver has
                 // Sasl.SERVER_AUTH fixed to true (mutual authentication) and one can configure SSL for enhanced confidentiality,
-                // Sasl policy properties for negotiating the authenticatin mechanism are not relevant here, because
+                // Sasl policy properties for negotiating the authentication mechanism are not relevant here, because
                 // GSSAPI is the only available mechanism for this authenticator
                 final Map props = new HashMap<String, Object>();
                 final String[] principalParts = principalName.split("/|@");
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
index 5114aab..0d4b797 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
@@ -72,6 +72,7 @@ import static com.codahale.metrics.MetricRegistry.name;
  */
 public class TraversalOpProcessor extends AbstractOpProcessor {
     private static final Logger logger = LoggerFactory.getLogger(TraversalOpProcessor.class);
+    private static final Logger auditLogger = LoggerFactory.getLogger(GremlinServer.AUDIT_LOGGER_NAME);
     private static final ObjectMapper mapper = GraphSONMapper.build().version(GraphSONVersion.V2_0).create().createMapper();
     public static final String OP_PROCESSOR_NAME = "traversal";
     public static final Timer traversalOpTimer = MetricManager.INSTANCE.getTimer(name(GremlinServer.class, "op", "traversal"));
@@ -149,6 +150,7 @@ public class TraversalOpProcessor extends AbstractOpProcessor {
 
     private void iterateBytecodeTraversal(final Context context) throws Exception {
         final RequestMessage msg = context.getRequestMessage();
+        final Settings settings = context.getSettings();
         logger.debug("Traversal request {} for in thread {}", msg.getRequestId(), Thread.currentThread().getName());
 
         // right now the TraversalOpProcessor can take a direct GraphSON representation of Bytecode or directly take
@@ -190,6 +192,11 @@ public class TraversalOpProcessor extends AbstractOpProcessor {
                             .statusMessage(ex.getMessage())
                             .statusAttributeException(ex).create());
         }
+        if (settings.authentication.enableAuditLog) {
+            String address = context.getChannelHandlerContext().channel().remoteAddress().toString();
+            if (address.startsWith("/") && address.length() > 1) address = address.substring(1);
+            auditLogger.info("User with address {} requested: {}", address, bytecode);
+        }
 
         final Timer.Context timerContext = traversalOpTimer.time();
         final FutureTask<Void> evalFuture = new FutureTask<>(() -> {
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuditLogIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuditLogIntegrateTest.java
index 0394c39..4843c6c 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuditLogIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuditLogIntegrateTest.java
@@ -25,9 +25,11 @@ import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
 import org.apache.log4j.Logger;
 import org.apache.log4j.spi.LoggingEvent;
-import org.apache.tinkerpop.gremlin.driver.Channelizer;
 import org.apache.tinkerpop.gremlin.driver.Client;
 import org.apache.tinkerpop.gremlin.driver.Cluster;
+import org.apache.tinkerpop.gremlin.driver.remote.DriverRemoteConnection;
+import org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
 import org.apache.tinkerpop.gremlin.server.auth.AllowAllAuthenticator;
 import org.apache.tinkerpop.gremlin.server.auth.Krb5Authenticator;
 import org.apache.tinkerpop.gremlin.server.auth.SimpleAuthenticator;
@@ -36,8 +38,6 @@ import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONTokens;
 import org.apache.tinkerpop.gremlin.util.Log4jRecordingAppender;
 import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
 import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 import org.slf4j.LoggerFactory;
 
@@ -73,7 +73,6 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
 
     private KdcFixture kdcServer;
 
-    @Before
     @Override
     public void setUp() throws Exception {
         recordingAppender = new Log4jRecordingAppender();
@@ -81,9 +80,10 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
         rootLogger.addAppender(recordingAppender);
 
         try {
-            final String buildDir = System.getProperty("build.dir");
-            kdcServer = new KdcFixture(buildDir +
-                    "/test-classes/org/apache/tinkerpop/gremlin/server/gremlin-console-jaas.conf");
+            final String moduleBaseDir = System.getProperty("basedir");
+            final String authConfigName = moduleBaseDir + "/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-console-jaas.conf";
+            System.setProperty("java.security.auth.login.config", authConfigName);
+            kdcServer = new KdcFixture(moduleBaseDir);
             kdcServer.setUp();
         } catch(Exception e)  {
             logger.warn(e.getMessage());
@@ -91,13 +91,14 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
         super.setUp();
     }
 
-    @After
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
         final Logger rootLogger = Logger.getRootLogger();
         rootLogger.removeAppender(recordingAppender);
         kdcServer.close();
+        System.clearProperty("java.security.auth.login.config");
+        super.tearDown();
     }
 
     /**
@@ -105,7 +106,7 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
      */
     @Override
     public Settings overrideSettings(final Settings settings) {
-        settings.host = kdcServer.hostname;
+        settings.host = kdcServer.gremlinHostname;
         final Settings.SslSettings sslConfig = new Settings.SslSettings();
         sslConfig.enabled = false;
         settings.ssl = sslConfig;
@@ -121,6 +122,7 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
             case "shouldAuditLogWithAllowAllAuthenticator":
                 authSettings.authenticator = AllowAllAuthenticator.class.getName();
                 break;
+            case "shouldAuditLogWithTraversalOp":
             case "shouldAuditLogWithSimpleAuthenticator":
                 authSettings.authenticator = SimpleAuthenticator.class.getName();
                 authConfig.put(SimpleAuthenticator.CONFIG_CREDENTIALS_DB, "conf/tinkergraph-credentials.properties");
@@ -145,7 +147,7 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
     @Test
     public void shouldAuditLogWithAllowAllAuthenticator() throws Exception {
 
-        final Cluster cluster = TestClientFactory.build().addContactPoint(kdcServer.hostname).create();
+        final Cluster cluster = TestClientFactory.build().addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
 
         try {
@@ -173,7 +175,7 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
         final String password = "password";
 
         final Cluster cluster = TestClientFactory.build().credentials(username, password)
-                .addContactPoint(kdcServer.hostname).create();
+                .addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
 
         try {
@@ -209,7 +211,7 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
     @Test
     public void shouldAuditLogWithKrb5Authenticator() throws Exception {
         final Cluster cluster = TestClientFactory.build().jaasEntry(TESTCONSOLE)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
         try {
             assertEquals(2, client.submit("1+1").all().get().get(0).getInt());
@@ -241,7 +243,7 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
     @Test
     public void shouldNotAuditLogWhenDisabled() throws Exception {
         final Cluster cluster = TestClientFactory.build().jaasEntry(TESTCONSOLE)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
         try {
             assertEquals(2, client.submit("1+1").all().get().get(0).getInt());
@@ -298,12 +300,46 @@ public class GremlinServerAuditLogIntegrateTest extends AbstractGremlinServerInt
     }
 
     @Test
+    public void shouldAuditLogWithTraversalOp() throws Exception {
+        final String username = "stephen";
+        final String password = "password";
+
+        final Cluster cluster = TestClientFactory.build().credentials(username, password)
+                .addContactPoint(kdcServer.gremlinHostname).create();
+        final Client client = cluster.connect();
+        final GraphTraversalSource g = AnonymousTraversalSource.traversal().
+                withRemote(DriverRemoteConnection.using(cluster, "gmodern"));
+
+        try {
+            assertEquals(6, g.V().count().next().intValue());
+        } finally {
+            cluster.close();
+        }
+
+        // wait for logger to flush - (don't think there is a way to detect this)
+        stopServer();
+        Thread.sleep(1000);
+
+        final String simpleAuthenticatorName = SimpleAuthenticator.class.getSimpleName();
+
+        final List<LoggingEvent> log = recordingAppender.getEvents();
+        final Stream<LoggingEvent> auditEvents = log.stream().filter(event -> event.getLoggerName().equals(AUDIT_LOGGER_NAME));
+        final LoggingEvent authEvent = auditEvents
+                .filter(event -> event.getMessage().toString().contains(simpleAuthenticatorName)).iterator().next();
+        final String authMsg = authEvent.getMessage().toString();
+        assertTrue(authEvent.getLevel() == INFO &&
+                authMsg.matches(String.format("User %s with address .*? authenticated by %s", username, simpleAuthenticatorName)));
+        assertTrue(log.stream().anyMatch(item -> item.getLevel() == INFO &&
+                item.getMessage().toString().matches("User with address .*? requested: \\[\\[], \\[V\\(\\), count\\(\\)]]")));
+    }
+
+    @Test
     public void shouldAuditLogTwoClientsWithKrb5Authenticator() throws Exception {
         final Cluster cluster = TestClientFactory.build().jaasEntry(TESTCONSOLE)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
         final Cluster cluster2 = TestClientFactory.build().jaasEntry(TESTCONSOLE2)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).create();
         final Client client2 = cluster2.connect();
         try {
             assertEquals(2, client.submit("1+1").all().get().get(0).getInt());
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuthKrb5IntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuthKrb5IntegrateTest.java
index 029a408..476e962 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuthKrb5IntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerAuthKrb5IntegrateTest.java
@@ -29,7 +29,6 @@ import org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0;
 import org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0;
 import org.apache.tinkerpop.gremlin.server.auth.Krb5Authenticator;
 import org.ietf.jgss.GSSException;
-import org.junit.Before;
 import org.junit.Test;
 import org.slf4j.LoggerFactory;
 
@@ -54,13 +53,13 @@ public class GremlinServerAuthKrb5IntegrateTest extends AbstractGremlinServerInt
 
     private KdcFixture kdcServer;
 
-    @Before
     @Override
     public void setUp() throws Exception {
         try {
-            final String buildDir = System.getProperty("build.dir");
-            kdcServer = new KdcFixture(buildDir +
-                    "/test-classes/org/apache/tinkerpop/gremlin/server/gremlin-console-jaas.conf");
+            final String projectBaseDir = System.getProperty("basedir");
+            final String authConfigName = projectBaseDir + "/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-console-jaas.conf";
+            System.setProperty("java.security.auth.login.config", authConfigName);
+            kdcServer = new KdcFixture(projectBaseDir);
             kdcServer.setUp();
         } catch(Exception e)  {
             logger.warn(e.getMessage());
@@ -68,12 +67,19 @@ public class GremlinServerAuthKrb5IntegrateTest extends AbstractGremlinServerInt
         super.setUp();
     }
 
+    @Override
+    public void tearDown() throws Exception {
+        kdcServer.close();
+        System.clearProperty("java.security.auth.login.config");
+        super.tearDown();
+    }
+
     /**
      * Configure specific Gremlin Server settings for specific tests.
      */
     @Override
     public Settings overrideSettings(final Settings settings) {
-        settings.host = kdcServer.hostname;
+        settings.host = kdcServer.gremlinHostname;
         final Settings.SslSettings sslConfig = new Settings.SslSettings();
         sslConfig.enabled = false;
         settings.ssl = sslConfig;
@@ -120,7 +126,7 @@ public class GremlinServerAuthKrb5IntegrateTest extends AbstractGremlinServerInt
     @Test
     public void shouldAuthenticateWithDefaults() throws Exception {
         final Cluster cluster = TestClientFactory.build().jaasEntry(TESTCONSOLE)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
         assertConnection(cluster, client);
     }
@@ -128,7 +134,7 @@ public class GremlinServerAuthKrb5IntegrateTest extends AbstractGremlinServerInt
     @Test
     public void shouldFailWithoutClientJaasEntry() throws Exception {
         final Cluster cluster = TestClientFactory.build().protocol(kdcServer.serverPrincipalName)
-                .addContactPoint(kdcServer.hostname).create();
+                .addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
         try {
             client.submit("1+1").all().get();
@@ -144,7 +150,7 @@ public class GremlinServerAuthKrb5IntegrateTest extends AbstractGremlinServerInt
     @Test
     public void shouldFailWithoutClientTicketCache() throws Exception {
         final Cluster cluster = TestClientFactory.build().jaasEntry(TESTCONSOLE_NOT_LOGGED_IN)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
         try {
             client.submit("1+1").all().get();
@@ -177,7 +183,7 @@ public class GremlinServerAuthKrb5IntegrateTest extends AbstractGremlinServerInt
         final String oldQop = System.getProperty("javax.security.sasl.qop", "");
         System.setProperty("javax.security.sasl.qop", "auth-conf");
         final Cluster cluster = TestClientFactory.build().jaasEntry(TESTCONSOLE)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
         try {
             assertEquals(2, client.submit("1+1").all().get().get(0).getInt());
@@ -192,7 +198,7 @@ public class GremlinServerAuthKrb5IntegrateTest extends AbstractGremlinServerInt
     @Test
     public void shouldAuthenticateWithSsl() throws Exception {
         final Cluster cluster = TestClientFactory.build().jaasEntry(TESTCONSOLE).enableSsl(true).sslSkipCertValidation(true)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
         assertConnection(cluster, client);
     }
@@ -217,7 +223,7 @@ public class GremlinServerAuthKrb5IntegrateTest extends AbstractGremlinServerInt
         config.put("serializeResultToString", true);
         serializer.configure(config, null);
         final Cluster cluster = TestClientFactory.build().jaasEntry(TESTCONSOLE)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).serializer(serializer).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).serializer(serializer).create();
         final Client client = cluster.connect();
         assertConnection(cluster, client);
     }
@@ -237,7 +243,7 @@ public class GremlinServerAuthKrb5IntegrateTest extends AbstractGremlinServerInt
      */
     private void assertFailedLogin() throws Exception {
         final Cluster cluster = TestClientFactory.build().jaasEntry(TESTCONSOLE)
-                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.hostname).create();
+                .protocol(kdcServer.serverPrincipalName).addContactPoint(kdcServer.gremlinHostname).create();
         final Client client = cluster.connect();
         try {
             client.submit("1+1").all().get();
diff --git a/gremlin-server/src/test/scripts/test-server-start.groovy b/gremlin-server/src/test/scripts/test-server-start.groovy
index 597673b..324492e 100644
--- a/gremlin-server/src/test/scripts/test-server-start.groovy
+++ b/gremlin-server/src/test/scripts/test-server-start.groovy
@@ -18,6 +18,7 @@
  */
 
 import org.apache.tinkerpop.gremlin.server.GremlinServer
+import org.apache.tinkerpop.gremlin.server.KdcFixture
 import org.apache.tinkerpop.gremlin.server.Settings
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -25,8 +26,8 @@ import org.apache.tinkerpop.gremlin.server.Settings
 ////////////////////////////////////////////////////////////////////////////////
 // Changes to this file need to be appropriately replicated to
 //
-// - docker/gremlin-server/gremlin-server-integration.yaml
-// - docker/gremlin-server/gremlin-server-integration-secure.yaml
+// - docker/gremlin-server/*
+// - docker/gremlin-server.sh
 //
 // Without such changes, the test docker server can't be started for independent
 // testing with Gremlin Language Variants.
@@ -49,7 +50,8 @@ def server = new GremlinServer(settings)
 server.start().join()
 
 project.setContextValue("gremlin.server", server)
-log.info("Gremlin Server with no authentication started on port 45940")
+log.info("Gremlin Server without authentication started on port 45940")
+
 
 def securePropsFile = new File("${projectBaseDir}/target/tinkergraph-credentials.properties")
 if (!securePropsFile.exists()) {
@@ -76,4 +78,31 @@ def serverSecure = new GremlinServer(settingsSecure)
 serverSecure.start().join()
 
 project.setContextValue("gremlin.server.secure", serverSecure)
-log.info("Gremlin Server with authentication started on port 45941")
\ No newline at end of file
+log.info("Gremlin Server with password authentication started on port 45941")
+
+
+def kdcServer = new KdcFixture(projectBaseDir)
+kdcServer.setUp()
+
+project.setContextValue("gremlin.server.kdcserver", kdcServer)
+log.info("KDC started with configuration ${projectBaseDir}/target/kdc/krb5.conf")
+
+def settingsKrb5 = Settings.read("${settingsFile}")
+settingsKrb5.graphs.graph = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
+settingsKrb5.graphs.classic = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
+settingsKrb5.graphs.modern = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
+settingsKrb5.graphs.crew = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
+settingsKrb5.graphs.grateful = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
+settingsKrb5.graphs.sink = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
+settingsKrb5.scriptEngines["gremlin-groovy"].plugins["org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin"].files = [gremlinServerDir + "/src/test/scripts/generate-all.groovy"]
+settingsKrb5.port = 45942
+settingsKrb5.authentication.authenticator = "org.apache.tinkerpop.gremlin.server.auth.Krb5Authenticator"
+settingsKrb5.authentication.config = [
+        "principal": kdcServer.serverPrincipal,
+        "keytab": kdcServer.serviceKeytabFile.getAbsolutePath()]
+
+def serverKrb5 = new GremlinServer(settingsKrb5)
+serverKrb5.start().join()
+
+project.setContextValue("gremlin.server.krb5", serverKrb5)
+log.info("Gremlin Server with kerberos authentication started on port 45942")
diff --git a/gremlin-server/src/test/scripts/test-server-stop.groovy b/gremlin-server/src/test/scripts/test-server-stop.groovy
index 595d9bd..d9fc629 100644
--- a/gremlin-server/src/test/scripts/test-server-stop.groovy
+++ b/gremlin-server/src/test/scripts/test-server-stop.groovy
@@ -29,4 +29,12 @@ def serverSecure = project.getContextValue("gremlin.server.secure")
 log.info("Shutting down $serverSecure")
 serverSecure.stop().join()
 
+def kdcServer = project.getContextValue("gremlin.server.kdcserver")
+log.info("Shutting down $kdcServer")
+kdcServer.close()
+
+def serverKrb5 = project.getContextValue("gremlin.server.krb5")
+log.info("Shutting down $serverKrb5")
+serverKrb5.stop().join()
+
 log.info("All Gremlin Server instances are shutdown for ${executionName}")
\ No newline at end of file
diff --git a/gremlin-test/pom.xml b/gremlin-test/pom.xml
index efb406d..ff480f5 100644
--- a/gremlin-test/pom.xml
+++ b/gremlin-test/pom.xml
@@ -52,6 +52,11 @@ limitations under the License.
             <artifactId>slf4j-log4j12</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.apache.kerby</groupId>
+            <artifactId>kerb-simplekdc</artifactId>
+            <version>2.0.0</version>
+        </dependency>
     </dependencies>
     <build>
         <directory>${basedir}/target</directory>
@@ -77,6 +82,24 @@ limitations under the License.
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-failsafe-plugin</artifactId>
             </plugin>
+            <plugin>
+                <!--Needed for docker/gremlin-server.sh-->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/KdcFixture.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/server/KdcFixture.java
similarity index 78%
rename from gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/KdcFixture.java
rename to gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/server/KdcFixture.java
index 0234e24..aaa5e00 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/KdcFixture.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/server/KdcFixture.java
@@ -24,7 +24,6 @@ import org.apache.kerby.kerberos.kerb.client.KrbConfig;
 import org.apache.kerby.kerberos.kerb.client.KrbConfigKey;
 import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
 import org.apache.kerby.kerberos.kerb.type.ticket.TgtTicket;
-import org.apache.kerby.util.NetworkUtil;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
@@ -47,8 +46,10 @@ public class KdcFixture {
 
     final String clientPassword = "123456";
     final String clientPassword2 = "1234562";
+    final String userPassword = "password";
     final String clientPrincipalName = "drankye";
     final String clientPrincipalName2 = "drankye2";
+    final String userPrincipalName = "stephen";
     final String serverPrincipalName = "test-service";
     final String ticketCacheFileName = "test-tkt.cc";
     final String ticketCacheFileName2 = "test-tkt2.cc";
@@ -57,21 +58,31 @@ public class KdcFixture {
     final String clientPrincipal;
     final String clientPrincipal2;
     final String serverPrincipal;
+    final String userPrincipal;
     final File testDir;
     final File ticketCacheFile;
     final File ticketCacheFile2;
     final File serviceKeytabFile;
 
-    final String hostname;
+    final String gremlinHostname;
+    final String kdcHostname;
     private SimpleKdcServer kdcServer;
 
-    public KdcFixture(final String authConfigName) {
-        System.setProperty("java.security.auth.login.config", authConfigName);
-        hostname = findHostname();
-        serverPrincipal = serverPrincipalName + "/" + hostname + "@" + KdcFixture.TestKdcServer.KDC_REALM;
+    public KdcFixture(final String moduleBaseDir) {
+        this(moduleBaseDir, "localhost");
+    }
+
+    public KdcFixture(final String moduleBaseDir, final String kdcHostName) {
+        this.kdcHostname = kdcHostName;
+        gremlinHostname = findHostname();
+        serverPrincipal = serverPrincipalName + "/" + gremlinHostname + "@" + KdcFixture.TestKdcServer.KDC_REALM;
         clientPrincipal = clientPrincipalName + "@" + KdcFixture.TestKdcServer.KDC_REALM;
         clientPrincipal2 = clientPrincipalName2 + "@" + KdcFixture.TestKdcServer.KDC_REALM;
-        testDir = createTestDir();
+        userPrincipal = userPrincipalName  + "@" + KdcFixture.TestKdcServer.KDC_REALM;
+
+        final File targetDir = new File(moduleBaseDir, "target");
+        testDir = new File(targetDir, "kdc");
+        testDir.mkdirs();
         ticketCacheFile = new File(testDir, ticketCacheFileName);
         ticketCacheFile2 = new File(testDir, ticketCacheFileName2);
         serviceKeytabFile = new File(testDir, serviceKeytabFileName);
@@ -90,29 +101,24 @@ public class KdcFixture {
         return hostname;
     }
 
-    private File createTestDir() {
-        final String basedir = System.getProperty("basedir");
-        final File targetdir = new File(basedir, "target");
-        final File testDir = new File(targetdir, "kdc");
-        testDir.mkdirs();
-        return testDir;
-    }
-
     private class TestKdcServer extends SimpleKdcServer {
         public static final String KDC_REALM = "TEST.COM";
-        public static final String HOSTNAME = "localhost";
+        public final String HOSTNAME = kdcHostname;
+        public static final int KDC_PORT = 4588;
 
         TestKdcServer() throws KrbException {
             setKdcRealm(KDC_REALM);
             setKdcHost(HOSTNAME);
             setAllowTcp(true);
-            setAllowUdp(false);    // There are still udp issues in Apache Directory-Kerby 1.0.0-RC2
-            setKdcTcpPort(NetworkUtil.getServerPort());
+            setAllowUdp(true);
+            setKdcTcpPort(KDC_PORT);
+            setKdcUdpPort(KDC_PORT);
 
             final KrbClient krbClnt = getKrbClient();
             final KrbConfig krbConfig = krbClnt.getKrbConfig();
             krbConfig.setString(KrbConfigKey.PERMITTED_ENCTYPES,
                     "aes128-cts-hmac-sha1-96 des-cbc-crc des-cbc-md5 des3-cbc-sha1");
+            krbConfig.setString(KrbConfigKey.DEFAULT_REALM, KDC_REALM);
             krbClnt.setTimeout(10 * 1000);
         }
     }
@@ -140,6 +146,8 @@ public class KdcFixture {
         kdcServer.createPrincipal(clientPrincipal2, clientPassword2);
         final TgtTicket tgt2 = kdcServer.getKrbClient().requestTgt(clientPrincipal2, clientPassword2);
         kdcServer.getKrbClient().storeTicket(tgt2, ticketCacheFile2);
+
+        kdcServer.createPrincipal(userPrincipal, userPassword);
     }
 
     public void close() throws Exception {
@@ -149,7 +157,6 @@ public class KdcFixture {
         ticketCacheFile2.delete();
         serviceKeytabFile.delete();
         testDir.delete();
-        System.clearProperty("java.security.auth.login.config");
     }
 
     private void deletePrincipals() throws KrbException {
@@ -162,4 +169,15 @@ public class KdcFixture {
     public void createPrincipal(final String principal) throws KrbException {
         kdcServer.createPrincipal(principal);
     }
+
+    public static void main(final String[] args) throws Exception{
+        final String projectBaseDir = args[0];
+        // The KDC in docker/gremlin-server.sh needs to be exposed to both the container and the host
+        final KdcFixture kdcFixture = new KdcFixture(projectBaseDir, "0.0.0.0");
+        kdcFixture.setUp();
+        logger.info("KDC started with configuration {}/target/kdc/krb5.conf", projectBaseDir);
+        while (true) {
+            Thread.sleep(1000);
+        }
+    }
 }