Skip to content

Commit

Permalink
Added 2-way ssl project example
Browse files Browse the repository at this point in the history
  • Loading branch information
jkerr5 committed Mar 15, 2018
1 parent a9adda5 commit 68faabb
Show file tree
Hide file tree
Showing 8 changed files with 488 additions and 0 deletions.
90 changes: 90 additions & 0 deletions examples/ssl-2way-project/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
This project shows how to create an SSLContext to support deploying modules when 2-way SSL is configured
for an app server.

2-way SSL requires a certificate template to be configured on the MarkLogic app server,
"ssl require client certificate" set to true and one or more "ssl client certificate authorities"
selected indicating which CAs the client certificates must be signed by.

Follow instructions here https://docs.marklogic.com/guide/security/SSL to setup SSL for
the MarkLogic app server.

To make this work, you will need a certificate authority (CA) that can sign certificates for you.
You can either use a known 3rd party or setup your own CA for testing. The rest of the instructions
assume that the same CA is used to sign both the client and the server certificates.

## Create a server certificate
If the server does not yet have a certificate signed by your CA, you will need to get one and imort
it to the server. If you already have server certificated signed by the CA, you can skip this step.

1) Using the certificate template created above, generate and download a certificate request (CSR). Use
that CSR to request a certificate from the CA (or generate one yourself if you have your own CA).

_Important: Your CSR will have the values for country, state/province, city/town, organization,
organizational unit and email address from the certificate template. Your CA will have requirements
for what must be in each of those fields. If using your own CA to sign certificates, you will need
to configure openssl to be able to process CSRs with the values from the template or set the template
values to the values needed for your openssl CA configuration before generating the server CSR._

The following are helpful resources if considering setting up your own CA for testing:
* https://www.area536.com/projects/be-your-own-certificate-authority-with-openssl/
* https://jamielinux.com/docs/openssl-certificate-authority/create-the-root-pair.html

1) Once you have the signed server certificate, import it into the server using the "Import" tab of
the certificate template used to generate the CSR.

_Important: The CN in the server cert will be the hostname of the MarkLogic host as seen from the "Hosts" list in
the admin UI. This will need to be the hostname you use to connect to MarkLogic if hostname verification is
being used._

## Create a client certificate
Since we are using ml-gradle which uses the MarkLogic Java Client under the covers, we need to use the
Java SSL libraries to setup the client SSL configuration. Java uses a "keystore" to manage client and
CA certificates. We will use the Java _keytool_ commandline tool to setup a keystore for the client.

The follow instructions were guided by the following helpful resources:
* https://docs.oracle.com/cd/E19509-01/820-3503/ggfen/index.html
* https://www.digitalocean.com/community/tutorials/java-keytool-essentials-working-with-java-keystores

1) Create a keystore
```
keytool -keystore clientkeystore -genkey -alias client
```

You will be prompted for a password for the keystore. Remember this and use it as the `mlKeystorePassword`
in the `gradle.properties` file.

You will be propted to enter values for your name (CN), country, state/province, city/town, organization and
organizational unit. Enter values as required by your CA but make sure to use the MarkLogic username that will
be using the certificate when propted for "your first and last name". This is the CN stored in the certificate.

_Important: The CN in client certificate needs to match the user name that you will use to connect to
MarkLogic or authentication will fail._

If you want a different password on the client certificate, enter one when prompted and use this as the
`mlKeystoreCertPassword` in the `gradle.properties` file. Otherwise, hit enter to use the same password as the
keystore.

When complete, this will create a file called `clientkeystore` (you can name this file whatever you want though).
Use this as the value for `mlKeystore` in the `gradle.properties` file.

1) Create a client CSR
```
keytool -keystore clientkeystore -certreq -alias client -keyalg rsa -file client.csr
```

1) Use the CSR to have a CA to generate a signed client certificate (or generate one using your own CA)

1) Import the CA certificate
```
keytool -import -keystore clientkeystore -file ca.crt -alias theCARoot
```

1) Import the signed client certificate
```
keytool -import -keystore clientkeystore -file client.crt -alias client
```




s
94 changes: 94 additions & 0 deletions examples/ssl-2way-project/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
plugins {
id "com.marklogic.ml-gradle" version "3.2.1"
}


/*
You can use the Java keytool utility to import a MarkLogic certificate into a keystore.
See the Java JSSE documentation for details on the use of the keytool and your keystore options.
You can explicitly specify a keystore, as shown in this example, or you can specify a null
keystore. Specifying a null keystore causes the TrustManagerFactory to locate your default
keystore, as described in the Java Secure Socket Extension (JSSE) Reference Guide.
*/

import java.io.FileInputStream;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.sslContext;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import com.marklogic.client.DatabaseClientFactory;

if (project.hasProperty("mlKeystore")) {

ext {
// mlAppConfig is an instance of com.marklogic.appdeployer.AppConfig
mlAppConfig {

// This uses the same keystore for server cert validation as well as the client cert
// These could be different though
KeyStore trustedKeyStore = KeyStore.getInstance("JKS")
trustedKeyStore.load(new FileInputStream(mlKeystore), mlKeystorePassword.toCharArray())

TrustManager[] trust = null

// This doesn't validate the server cert unless you set mlValidateServerCert=true
if (project.hasProperty("mlValidateServerCert") && mlValidateServerCert.toBoolean()) {
// Build trust manager to validate server certificates using the specified key store.
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509")
trustManagerFactory.init(trustedKeyStore)

trust = trustManagerFactory.getTrustManagers()
} else {
trust = [
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return [];
}

public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
}
]
}

// Load key store with client certificates.
KeyStore clientKeyStore = KeyStore.getInstance("JKS")
clientKeyStore.load(new FileInputStream(mlKeystore), mlKeystorePassword.toCharArray())

if (! project.hasProperty("mlKeystoreCertPassword")) {
mlKeystoreCertPassword = mlKeystorePassword
}

// Get key manager to provide client certificate
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(clientKeyStore, mlKeystoreCertPassword.toCharArray())

KeyManager[] key = keyManagerFactory.getKeyManagers()

// Initialize the SSL context with key and trust managers.
SSLContext sslContext = SSLContext.getInstance("SSLv3")
sslContext.init(key, trust, null)

restSslContext = sslContext

// This turns off hostname verification unless mlVerifyServerHostname=true
if (project.hasProperty("mlVerifyServerHostname") && mlVerifyServerHostname.toBoolean()) {
restSslHostnameVerifier = DatabaseClientFactory.SSLHostnameVerifier.COMMON // change to STRICT if needed
} else {
restSslHostnameVerifier = DatabaseClientFactory.SSLHostnameVerifier.ANY
}
}
}

}
26 changes: 26 additions & 0 deletions examples/ssl-2way-project/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
mlAppName=2way-ssl

mlHost=localhost
mlRestPort=8180

# This username must be the CN in the client certificate
mlUsername=<client user>
mlPassword=<client password>

# Use this for 1-way SSL with no server cert validation and no hostname verification
#mlSimpleSsl=true

# If this is set, additional SSL configuration will be done
# This needs to be a JKS created with the keytool utility
mlKeystore=<keystore file>
mlKeystorePassword=<password>

# This is the password on the client cert. If not set, the mlKeystorePassword will be used
#mlKeystoreCertPassword=<password>

# If this is true, the CA that signed the server cert must be in the specified keystore
mlValidateServerCert=false

# If this is true, the CN in server cert needs to match the hostname used to connect
# (the mlHost parameter above)
mlVerifyServerHostname=false
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#Mon Oct 30 21:26:19 EDT 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-bin.zip
172 changes: 172 additions & 0 deletions examples/ssl-2way-project/gradlew
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/usr/bin/env sh

##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
echo "$*"
}

die ( ) {
echo
echo "$*"
echo
exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`

# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option

if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi

# Escape application args
save ( ) {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi

exec "$JAVACMD" "$@"
Loading

0 comments on commit 68faabb

Please sign in to comment.