Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions core/src/main/scala/org/apache/spark/SSLOptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

package org.apache.spark

import java.io.File
import java.io.{File, FileInputStream}
import java.security.{KeyStore, NoSuchAlgorithmException}
import javax.net.ssl.{KeyManager, KeyManagerFactory, SSLContext, TrustManager, TrustManagerFactory}

import com.typesafe.config.{Config, ConfigFactory, ConfigValueFactory}
import org.eclipse.jetty.util.ssl.SslContextFactory
Expand All @@ -38,7 +40,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory
* @param trustStore a path to the trust-store file
* @param trustStorePassword a password to access the trust-store file
* @param protocol SSL protocol (remember that SSLv3 was compromised) supported by Java
* @param enabledAlgorithms a set of encryption algorithms to use
* @param enabledAlgorithms a set of encryption algorithms that may be used
*/
private[spark] case class SSLOptions(
enabled: Boolean = false,
Expand All @@ -48,7 +50,8 @@ private[spark] case class SSLOptions(
trustStore: Option[File] = None,
trustStorePassword: Option[String] = None,
protocol: Option[String] = None,
enabledAlgorithms: Set[String] = Set.empty) {
enabledAlgorithms: Set[String] = Set.empty)
extends Logging {

/**
* Creates a Jetty SSL context factory according to the SSL settings represented by this object.
Expand All @@ -63,7 +66,7 @@ private[spark] case class SSLOptions(
trustStorePassword.foreach(sslContextFactory.setTrustStorePassword)
keyPassword.foreach(sslContextFactory.setKeyManagerPassword)
protocol.foreach(sslContextFactory.setProtocol)
sslContextFactory.setIncludeCipherSuites(enabledAlgorithms.toSeq: _*)
sslContextFactory.setIncludeCipherSuites(supportedAlgorithms.toSeq: _*)

Some(sslContextFactory)
} else {
Expand Down Expand Up @@ -94,14 +97,44 @@ private[spark] case class SSLOptions(
.withValue("akka.remote.netty.tcp.security.protocol",
ConfigValueFactory.fromAnyRef(protocol.getOrElse("")))
.withValue("akka.remote.netty.tcp.security.enabled-algorithms",
ConfigValueFactory.fromIterable(enabledAlgorithms.toSeq))
ConfigValueFactory.fromIterable(supportedAlgorithms.toSeq))
.withValue("akka.remote.netty.tcp.enable-ssl",
ConfigValueFactory.fromAnyRef(true)))
} else {
None
}
}

/*
* The supportedAlgorithms set is a subset of the enabledAlgorithms that
* are supported by the current Java security provider for this protocol.
*/
private val supportedAlgorithms: Set[String] = {
var context: SSLContext = null
try {
context = SSLContext.getInstance(protocol.orNull)
/* The set of supported algorithms does not depend upon the keys, trust, or
rng, although they will influence which algorithms are eventually used. */
context.init(null, null, null)
} catch {
case npe: NullPointerException =>
logDebug("No SSL protocol specified")
context = SSLContext.getDefault
case nsa: NoSuchAlgorithmException =>
logDebug(s"No support for requested SSL protocol ${protocol.get}")
context = SSLContext.getDefault
}

val providerAlgorithms = context.getServerSocketFactory.getSupportedCipherSuites.toSet

// Log which algorithms we are discarding
(enabledAlgorithms &~ providerAlgorithms).foreach { cipher =>
logDebug(s"Discarding unsupported cipher $cipher")
}

enabledAlgorithms & providerAlgorithms
}

/** Returns a string representation of this SSLOptions with all the passwords masked. */
override def toString: String = s"SSLOptions{enabled=$enabled, " +
s"keyStore=$keyStore, keyStorePassword=${keyStorePassword.map(_ => "xxx")}, " +
Expand Down
20 changes: 14 additions & 6 deletions core/src/test/scala/org/apache/spark/SSLOptionsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.spark

import java.io.File
import javax.net.ssl.SSLContext

import com.google.common.io.Files
import org.apache.spark.util.Utils
Expand All @@ -29,16 +30,24 @@ class SSLOptionsSuite extends SparkFunSuite with BeforeAndAfterAll {
val keyStorePath = new File(this.getClass.getResource("/keystore").toURI).getAbsolutePath
val trustStorePath = new File(this.getClass.getResource("/truststore").toURI).getAbsolutePath

// Pick two cipher suites that the provider knows about
val sslContext = SSLContext.getInstance("TLSv1.2")
sslContext.init(null, null, null)
val algorithms = sslContext
.getServerSocketFactory
.getDefaultCipherSuites
.take(2)
.toSet

val conf = new SparkConf
conf.set("spark.ssl.enabled", "true")
conf.set("spark.ssl.keyStore", keyStorePath)
conf.set("spark.ssl.keyStorePassword", "password")
conf.set("spark.ssl.keyPassword", "password")
conf.set("spark.ssl.trustStore", trustStorePath)
conf.set("spark.ssl.trustStorePassword", "password")
conf.set("spark.ssl.enabledAlgorithms",
"TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA")
conf.set("spark.ssl.protocol", "SSLv3")
conf.set("spark.ssl.enabledAlgorithms", algorithms.mkString(","))
conf.set("spark.ssl.protocol", "TLSv1.2")

val opts = SSLOptions.parse(conf, "spark.ssl")

Expand All @@ -52,9 +61,8 @@ class SSLOptionsSuite extends SparkFunSuite with BeforeAndAfterAll {
assert(opts.trustStorePassword === Some("password"))
assert(opts.keyStorePassword === Some("password"))
assert(opts.keyPassword === Some("password"))
assert(opts.protocol === Some("SSLv3"))
assert(opts.enabledAlgorithms ===
Set("TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"))
assert(opts.protocol === Some("TLSv1.2"))
assert(opts.enabledAlgorithms === algorithms)
}

test("test resolving property with defaults specified ") {
Expand Down
24 changes: 18 additions & 6 deletions core/src/test/scala/org/apache/spark/SSLSampleConfigs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ object SSLSampleConfigs {
this.getClass.getResource("/untrusted-keystore").toURI).getAbsolutePath
val trustStorePath = new File(this.getClass.getResource("/truststore").toURI).getAbsolutePath

val enabledAlgorithms =
// A reasonable set of TLSv1.2 Oracle security provider suites
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, " +
"TLS_RSA_WITH_AES_256_CBC_SHA256, " +
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, " +
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, " +
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, " +
// and their equivalent names in the IBM Security provider
"SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA384, " +
"SSL_RSA_WITH_AES_256_CBC_SHA256, " +
"SSL_DHE_RSA_WITH_AES_256_CBC_SHA256, " +
"SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA256, " +
"SSL_DHE_RSA_WITH_AES_128_CBC_SHA256"

def sparkSSLConfig(): SparkConf = {
val conf = new SparkConf(loadDefaults = false)
conf.set("spark.ssl.enabled", "true")
Expand All @@ -33,9 +47,8 @@ object SSLSampleConfigs {
conf.set("spark.ssl.keyPassword", "password")
conf.set("spark.ssl.trustStore", trustStorePath)
conf.set("spark.ssl.trustStorePassword", "password")
conf.set("spark.ssl.enabledAlgorithms",
"SSL_RSA_WITH_RC4_128_SHA, SSL_RSA_WITH_DES_CBC_SHA")
conf.set("spark.ssl.protocol", "TLSv1")
conf.set("spark.ssl.enabledAlgorithms", enabledAlgorithms)
conf.set("spark.ssl.protocol", "TLSv1.2")
conf
}

Expand All @@ -47,9 +60,8 @@ object SSLSampleConfigs {
conf.set("spark.ssl.keyPassword", "password")
conf.set("spark.ssl.trustStore", trustStorePath)
conf.set("spark.ssl.trustStorePassword", "password")
conf.set("spark.ssl.enabledAlgorithms",
"SSL_RSA_WITH_RC4_128_SHA, SSL_RSA_WITH_DES_CBC_SHA")
conf.set("spark.ssl.protocol", "TLSv1")
conf.set("spark.ssl.enabledAlgorithms", enabledAlgorithms)
conf.set("spark.ssl.protocol", "TLSv1.2")
conf
}

Expand Down
21 changes: 15 additions & 6 deletions core/src/test/scala/org/apache/spark/SecurityManagerSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ class SecurityManagerSuite extends SparkFunSuite {

test("ssl on setup") {
val conf = SSLSampleConfigs.sparkSSLConfig()
val expectedAlgorithms = Set(
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"SSL_RSA_WITH_AES_256_CBC_SHA256",
"SSL_DHE_RSA_WITH_AES_256_CBC_SHA256",
"SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"SSL_DHE_RSA_WITH_AES_128_CBC_SHA256")

val securityManager = new SecurityManager(conf)

Expand All @@ -143,9 +154,8 @@ class SecurityManagerSuite extends SparkFunSuite {
assert(securityManager.fileServerSSLOptions.trustStorePassword === Some("password"))
assert(securityManager.fileServerSSLOptions.keyStorePassword === Some("password"))
assert(securityManager.fileServerSSLOptions.keyPassword === Some("password"))
assert(securityManager.fileServerSSLOptions.protocol === Some("TLSv1"))
assert(securityManager.fileServerSSLOptions.enabledAlgorithms ===
Set("SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_DES_CBC_SHA"))
assert(securityManager.fileServerSSLOptions.protocol === Some("TLSv1.2"))
assert(securityManager.fileServerSSLOptions.enabledAlgorithms === expectedAlgorithms)

assert(securityManager.akkaSSLOptions.trustStore.isDefined === true)
assert(securityManager.akkaSSLOptions.trustStore.get.getName === "truststore")
Expand All @@ -154,9 +164,8 @@ class SecurityManagerSuite extends SparkFunSuite {
assert(securityManager.akkaSSLOptions.trustStorePassword === Some("password"))
assert(securityManager.akkaSSLOptions.keyStorePassword === Some("password"))
assert(securityManager.akkaSSLOptions.keyPassword === Some("password"))
assert(securityManager.akkaSSLOptions.protocol === Some("TLSv1"))
assert(securityManager.akkaSSLOptions.enabledAlgorithms ===
Set("SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_DES_CBC_SHA"))
assert(securityManager.akkaSSLOptions.protocol === Some("TLSv1.2"))
assert(securityManager.akkaSSLOptions.enabledAlgorithms === expectedAlgorithms)
}

test("ssl off setup") {
Expand Down