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);
+ }
+ }
}