Skip to content

Commit 035dcd8

Browse files
marcoboivelvia
authored andcommitted
feat(job-server): Client authentication (spark-jobserver#920)
* Support for certificate based client authentication * Updated docs
1 parent 8c524d6 commit 035dcd8

File tree

4 files changed

+73
-41
lines changed

4 files changed

+73
-41
lines changed

README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ Also see [Chinese docs / 中文](doc/chinese/job-server.md).
2828
- [Creating a project from scratch using giter8 template](#creating-a-project-from-scratch-using-giter8-template)
2929
- [Creating a project manually assuming that you already have sbt project structure](#creating-a-project-manually-assuming-that-you-already-have-sbt-project-structure)
3030
- [NEW SparkJob API](#new-sparkjob-api)
31+
- [NEW SparkJob API with Spark v2.1](#new-sparkjob-api-with-spark-v21)
3132
- [Dependency jars](#dependency-jars)
3233
- [Named Objects](#named-objects)
3334
- [Using Named RDDs](#using-named-rdds)
3435
- [Using Named Objects](#using-named-objects)
3536
- [HTTPS / SSL Configuration](#https--ssl-configuration)
36-
- [Authentication](#authentication)
37+
- [Server authentication](#server-authentication)
38+
- [Client authentication](#client-authentication)
39+
- [Basic authentication](#basic-authentication)
3740
- [Deployment](#deployment)
3841
- [Manual steps](#manual-steps)
3942
- [Context per JVM](#context-per-jvm)
@@ -497,7 +500,8 @@ def validate(sc:SparkContext, config: Config): SparkJobValidation = {
497500
```
498501
499502
### HTTPS / SSL Configuration
500-
To activate ssl communication, set these flags in your application.conf file (Section 'spray.can.server'):
503+
#### Server authentication
504+
To activate server authentication and ssl communication, set these flags in your application.conf file (Section 'spray.can.server'):
501505
```
502506
ssl-encryption = on
503507
# absolute path to keystore file
@@ -516,9 +520,19 @@ curl -k https://localhost:8090/contexts
516520
```
517521
The ```-k``` flag tells curl to "Allow connections to SSL sites without certs". Export your server certificate and import it into the client's truststore to fully utilize ssl security.
518522
519-
### Authentication
523+
#### Client authentication
524+
Client authentication can be enabled by simply pointing Job Server to a valid Trust Store.
525+
As for server authentication, this is done by setting appropriate values in the application.conf.
526+
The minimum set of parameters to enable client authentication consists of:
527+
```
528+
# truststore = "/some/path/server-truststore.jks"
529+
# truststorePW = "changeit"
530+
```
531+
Note, client authentication implies server authentication, therefore client authentication will only be enabled once server authentication is activated.
520532
521-
Authentication uses the [Apache Shiro](http://shiro.apache.org/index.html) framework. Authentication is activated by setting this flag (Section 'shiro'):
533+
### Basic authentication
534+
Basic authentication (username and password) in Job Server relies on the [Apache Shiro](http://shiro.apache.org/index.html) framework.
535+
Basic authentication is activated by setting this flag (Section 'shiro'):
522536
```
523537
authentication = on
524538
# absolute path to shiro config file, including file name

job-server/src/main/resources/application.conf

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -202,18 +202,23 @@ akka {
202202

203203
# check the reference.conf in spray-can/src/main/resources for all defined settings
204204
spray.can.server {
205-
# uncomment the next lines for making this an HTTPS example
205+
## uncomment the next lines for making this an HTTPS example
206206
# ssl-encryption = on
207-
# path to keystore
208-
#keystore = "/some/path/sjs.jks"
209-
#keystorePW = "changeit"
210-
211-
# see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext for more examples
212-
# typical are either SSL or TLS
207+
# keystore = "/some/path/server-keystore.jks"
208+
# keystorePW = "changeit"
213209
encryptionType = "SSL"
214210
keystoreType = "JKS"
215-
# key manager factory provider
216211
provider = "SunX509"
212+
#
213+
## Client Authentication (optional): activated upon providing a truststore file
214+
# truststore = "/some/path/server-truststore.jks"
215+
# truststorePW = "changeit"
216+
truststore-type = "JKS"
217+
truststore-provider = "SunX509"
218+
#
219+
# see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext for more examples
220+
# typical are either SSL or TLS
221+
# key manager factory provider
217222
# ssl engine provider protocols
218223
enabledProtocols = ["SSLv3", "TLSv1"]
219224
idle-timeout = 60 s

job-server/src/main/scala/spark/jobserver/WebApi.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ class WebApi(system: ActorSystem,
182182
ServerSSLEngineProvider { engine =>
183183
val protocols = config.getStringList("spray.can.server.enabledProtocols")
184184
engine.setEnabledProtocols(protocols.toArray(Array[String]()))
185+
val sprayConfig = config.getConfig("spray.can.server")
186+
if(sprayConfig.hasPath("truststore")) {
187+
engine.setNeedClientAuth(true)
188+
logger.info("Client authentication activated.")
189+
}
185190
engine
186191
}
187192
}

job-server/src/main/scala/spark/jobserver/util/SSLContextFactory.scala

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,55 @@
11
package spark.jobserver.util
22

3-
import javax.net.ssl.SSLContext
4-
import javax.net.ssl.KeyManagerFactory
5-
import java.security.KeyStore
63
import java.io.FileInputStream
4+
import java.security.KeyStore
5+
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManager, TrustManagerFactory}
6+
77
import com.typesafe.config.Config
88
import org.slf4j.LoggerFactory
99

1010
/**
11-
* creates SSLContext based on configuration, the SSLContext is used for communication between
12-
* the SJS web server and its clients
13-
*
14-
* if encryption is not activated in the configuration, then the default ssl context is used which
15-
* falls back to un-encrypted communication
16-
*/
11+
* Creates SSLContext based on configuration, the SSLContext is used for communication between
12+
* the SJS web server and its clients.
13+
*
14+
* If encryption is not activated in the configuration, then the default ssl context is used which
15+
* falls back to un-encrypted communication.
16+
*/
1717
object SSLContextFactory {
18+
1819
val logger = LoggerFactory.getLogger(getClass)
1920

20-
/**
21-
* based on
22-
* https://github.com/spray/spray/blob/v1.2-M8/examples/
23-
* spray-can/simple-http-server/src/main/scala/spray/examples/MySslConfiguration.scala
24-
*/
2521
def createContext(config: Config): SSLContext = {
26-
if (config.hasPath("ssl-encryption") &&
27-
config.getBoolean("ssl-encryption")) {
2822

29-
checkRequiredParamsSet(config)
30-
val sslContext = SSLContext.getInstance(config.getString("encryptionType"))
23+
if (config.hasPath("ssl-encryption") && config.getBoolean("ssl-encryption")) {
3124

32-
val ksName = config.getString("keystore")
33-
val ksPassphrase = config.getString("keystorePW").toCharArray()
34-
val keystoreType = config.getString("keystoreType")
25+
checkRequiredParamsSet(config)
3526
val encryptionType = config.getString("encryptionType")
27+
val sslContext = SSLContext.getInstance(encryptionType)
3628
logger.info(encryptionType + " encryption activated.")
37-
val ks = KeyStore.getInstance(keystoreType)
38-
//throws exception if keystore cannot be found or accessed
39-
// and prevents start-up
40-
ks.load(new FileInputStream(ksName), ksPassphrase)
41-
val kmf = KeyManagerFactory.getInstance(config.getString("provider"))
42-
kmf.init(ks, ksPassphrase)
43-
sslContext.init(kmf.getKeyManagers(), null, null)
29+
30+
val keyStoreFileName = config.getString("keystore")
31+
val keyStorePassword = config.getString("keystorePW").toCharArray()
32+
val keyStoreType = config.getString("keystoreType")
33+
val keyStore = KeyStore.getInstance(keyStoreType)
34+
keyStore.load(new FileInputStream(keyStoreFileName), keyStorePassword)
35+
val keyManagerFactory = KeyManagerFactory.getInstance(config.getString("provider"))
36+
keyManagerFactory.init(keyStore, keyStorePassword)
37+
logger.info("Using KeyStore: " + keyStoreFileName)
38+
39+
var trustManagers: Array[TrustManager] = null
40+
if (config.hasPath("truststore")) {
41+
val trustStoreFileName = config.getString("truststore")
42+
val trustStorePassword = config.getString("truststorePW").toCharArray()
43+
val trustStoreType = config.getString("truststore-type")
44+
val trustStore = KeyStore.getInstance(trustStoreType)
45+
trustStore.load(new FileInputStream(trustStoreFileName), trustStorePassword)
46+
val trustManagerFactory = TrustManagerFactory.getInstance(config.getString("truststore-provider"))
47+
trustManagerFactory.init(trustStore)
48+
trustManagers = trustManagerFactory.getTrustManagers
49+
logger.info("Using TrustStore: " + trustStoreFileName)
50+
}
51+
52+
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagers, null)
4453

4554
sslContext
4655
} else {
@@ -54,7 +63,6 @@ object SSLContextFactory {
5463
"activated, but keystore password is not configured."
5564

5665
def checkRequiredParamsSet(config: Config) {
57-
//all other parameters have default values in application.conf
5866
if (!config.hasPath("keystore")) {
5967
throw new RuntimeException(MISSING_KEYSTORE_MSG)
6068
}

0 commit comments

Comments
 (0)