Advertisement
dev10

akka 2.0 ssl support

May 18th, 2012
96
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Diff 15.11 KB | None | 0 0
  1. diff --git a/akka-remote/src/main/resources/reference.conf b/akka-remote/src/main/resources/reference.conf
  2. index 4512ea3..c96ec95 100644
  3. --- a/akka-remote/src/main/resources/reference.conf
  4. +++ b/akka-remote/src/main/resources/reference.conf
  5. @@ -151,6 +151,33 @@ akka {
  6.  
  7.        # (O) Maximum time window that a client should try to reconnect for
  8.        reconnection-time-window = 600s
  9. +
  10. +      # (I&O) Enable SSL/TLS encryption.
  11. +      # This must be enabled on both the client and server to work.
  12. +      enable-ssl = off
  13. +
  14. +      # (I) This is the Java Key Store used by the server connection
  15. +      ssl-key-store = "keystore"
  16. +
  17. +      # This password is used for decrypting the key store
  18. +      ssl-key-store-password = "changeme"
  19. +
  20. +      # (O) This is the Java Key Store used by the client connection
  21. +      ssl-trust-store = "truststore"
  22. +
  23. +      # This password is used for decrypting the trust store
  24. +      ssl-trust-store-password = "changeme"
  25. +
  26. +      # (I&O) Protocol to use for SSL encryption, choose from:
  27. +      # Java 6 & 7:
  28. +      #   SSLv3, TLSv1,
  29. +      # Java 7:
  30. +      #   TLSv1.1, TLSv1.2
  31. +      ssl-protocol = "TLSv1"
  32. +
  33. +      # You need to install the JCE Unlimited Strength Jurisdiction Policy Files to use AES 256
  34. +      # More info here: http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunJCEProvider
  35. +      ssl-supported-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"]
  36.      }
  37.    }
  38.  }
  39. diff --git a/akka-remote/src/main/scala/akka/remote/netty/Client.scala b/akka-remote/src/main/scala/akka/remote/netty/Client.scala
  40. index 7baf301..36df8b4 100644
  41. --- a/akka-remote/src/main/scala/akka/remote/netty/Client.scala
  42. +++ b/akka-remote/src/main/scala/akka/remote/netty/Client.scala
  43. @@ -20,10 +20,15 @@ import akka.actor.ActorRef
  44.  import org.jboss.netty.channel.ChannelFutureListener
  45.  import akka.remote.RemoteClientWriteFailed
  46.  import java.net.InetAddress
  47. +import java.security.{ SecureRandom, KeyStore, GeneralSecurityException }
  48.  import org.jboss.netty.util.TimerTask
  49.  import org.jboss.netty.util.Timeout
  50.  import java.util.concurrent.TimeUnit
  51.  import org.jboss.netty.handler.timeout.{ IdleState, IdleStateEvent, IdleStateAwareChannelHandler, IdleStateHandler }
  52. +import java.security.cert.X509Certificate
  53. +import javax.net.ssl.{ SSLContext, X509TrustManager, TrustManagerFactory, TrustManager }
  54. +import org.jboss.netty.handler.ssl.SslHandler
  55. +import java.io.FileInputStream
  56.  
  57.  class RemoteClientMessageBufferException(message: String, cause: Throwable) extends AkkaException(message, cause) {
  58.    def this(msg: String) = this(msg, null)
  59. @@ -329,7 +334,53 @@ class ActiveRemoteClientPipelineFactory(
  60.  
  61.    import client.netty.settings
  62.  
  63. +  def initTLS(trustStorePath: String, trustStorePassword: String): Option[SSLContext] = {
  64. +    if (trustStorePath != null && trustStorePassword != null)
  65. +      try {
  66. +        val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
  67. +        val trustStore = KeyStore.getInstance(KeyStore.getDefaultType)
  68. +        val stream = new FileInputStream(trustStorePath)
  69. +        trustStore.load(stream, trustStorePassword.toCharArray)
  70. +        trustManagerFactory.init(trustStore);
  71. +        val trustManagers: Array[TrustManager] = trustManagerFactory.getTrustManagers
  72. +
  73. +        val sslContext = SSLContext.getInstance("TLS")
  74. +        sslContext.init(null, trustManagers, new SecureRandom())
  75. +        Some(sslContext)
  76. +      } catch {
  77. +        case e: GeneralSecurityException ⇒ {
  78. +          client.log.error(e, "TLS connection could not be established. TLS is not used!");
  79. +          None
  80. +        }
  81. +      }
  82. +    else {
  83. +      client.log.error("TLS connection could not be established because trust store details are missing")
  84. +      None
  85. +    }
  86. +  }
  87. +
  88. +  def getSSLHandler_? : Option[SslHandler] = {
  89. +    val sslContext: Option[SSLContext] = {
  90. +      if (settings.EnableSSL) {
  91. +        client.log.debug("Client SSL is enabled, initialising ...")
  92. +        initTLS(settings.SSLTrustStore.get, settings.SSLTrustStorePassword.get)
  93. +      } else {
  94. +        None
  95. +      }
  96. +    }
  97. +    if (sslContext.isDefined) {
  98. +      client.log.debug("Client Using SSL context to create SSLEngine ...")
  99. +      val sslEngine = sslContext.get.createSSLEngine
  100. +      sslEngine.setUseClientMode(true)
  101. +      sslEngine.setEnabledCipherSuites(settings.SSLSupportedAlgorithms.toArray.map(_.toString))
  102. +      Some(new SslHandler(sslEngine))
  103. +    } else {
  104. +      None
  105. +    }
  106. +  }
  107. +
  108.    def getPipeline: ChannelPipeline = {
  109. +    val sslHandler = getSSLHandler_?
  110.      val timeout = new IdleStateHandler(client.netty.timer,
  111.        settings.ReadTimeout.toSeconds.toInt,
  112.        settings.WriteTimeout.toSeconds.toInt,
  113. @@ -340,7 +391,14 @@ class ActiveRemoteClientPipelineFactory(
  114.      val messageEnc = new RemoteMessageEncoder(client.netty)
  115.      val remoteClient = new ActiveRemoteClientHandler(name, bootstrap, remoteAddress, localAddress, client.netty.timer, client)
  116.  
  117. -    new StaticChannelPipeline(timeout, lenDec, messageDec, lenPrep, messageEnc, executionHandler, remoteClient)
  118. +    val stages: List[ChannelHandler] = timeout :: lenDec :: messageDec :: lenPrep :: messageEnc :: executionHandler :: remoteClient :: Nil
  119. +    if (sslHandler.isDefined) {
  120. +      client.log.debug("Client creating pipeline with SSL handler...")
  121. +      new StaticChannelPipeline(sslHandler.get :: stages: _*)
  122. +    } else {
  123. +      client.log.debug("Client creating pipeline without SSL handler...")
  124. +      new StaticChannelPipeline(stages: _*)
  125. +    }
  126.    }
  127.  }
  128.  
  129. diff --git a/akka-remote/src/main/scala/akka/remote/netty/Server.scala b/akka-remote/src/main/scala/akka/remote/netty/Server.scala
  130. index 7e4d1ea..2f572ba 100644
  131. --- a/akka-remote/src/main/scala/akka/remote/netty/Server.scala
  132. +++ b/akka-remote/src/main/scala/akka/remote/netty/Server.scala
  133. @@ -5,6 +5,7 @@ package akka.remote.netty
  134.  
  135.  import java.net.InetSocketAddress
  136.  import java.util.concurrent.Executors
  137. +import java.io.FileNotFoundException
  138.  import scala.Option.option2Iterable
  139.  import org.jboss.netty.bootstrap.ServerBootstrap
  140.  import org.jboss.netty.channel.ChannelHandler.Sharable
  141. @@ -12,13 +13,17 @@ import org.jboss.netty.channel.group.ChannelGroup
  142.  import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory
  143.  import org.jboss.netty.handler.codec.frame.{ LengthFieldPrepender, LengthFieldBasedFrameDecoder }
  144.  import org.jboss.netty.handler.execution.ExecutionHandler
  145. -import akka.event.Logging
  146.  import akka.remote.RemoteProtocol.{ RemoteControlProtocol, CommandType, AkkaRemoteProtocol }
  147.  import akka.remote.{ RemoteServerShutdown, RemoteServerError, RemoteServerClientDisconnected, RemoteServerClientConnected, RemoteServerClientClosed, RemoteProtocol, RemoteMessage }
  148.  import akka.actor.Address
  149.  import java.net.InetAddress
  150.  import akka.actor.ActorSystemImpl
  151.  import org.jboss.netty.channel._
  152. +import org.jboss.netty.handler.ssl.SslHandler
  153. +import java.security.{ SecureRandom, KeyStore, GeneralSecurityException }
  154. +import javax.net.ssl.{ KeyManagerFactory, SSLContext }
  155. +import java.io.FileInputStream
  156. +import akka.event.{ LoggingAdapter, Logging }
  157.  
  158.  class NettyRemoteServer(val netty: NettyRemoteTransport) {
  159.  
  160. @@ -26,6 +31,8 @@ class NettyRemoteServer(val netty: NettyRemoteTransport) {
  161.  
  162.    val ip = InetAddress.getByName(settings.Hostname)
  163.  
  164. +  lazy val log = Logging(netty.system, "NettyRemoteServer(" + ip + ")")
  165. +
  166.    private val factory =
  167.      settings.UseDispatcherForIO match {
  168.        case Some(id)
  169. @@ -42,7 +49,7 @@ class NettyRemoteServer(val netty: NettyRemoteTransport) {
  170.  
  171.    private val bootstrap = {
  172.      val b = new ServerBootstrap(factory)
  173. -    b.setPipelineFactory(new RemoteServerPipelineFactory(openChannels, executionHandler, netty))
  174. +    b.setPipelineFactory(new RemoteServerPipelineFactory(openChannels, executionHandler, netty, log))
  175.      b.setOption("backlog", settings.Backlog)
  176.      b.setOption("tcpNoDelay", true)
  177.      b.setOption("child.keepAlive", true)
  178. @@ -85,11 +92,60 @@ class NettyRemoteServer(val netty: NettyRemoteTransport) {
  179.  class RemoteServerPipelineFactory(
  180.    val openChannels: ChannelGroup,
  181.    val executionHandler: ExecutionHandler,
  182. -  val netty: NettyRemoteTransport) extends ChannelPipelineFactory {
  183. +  val netty: NettyRemoteTransport,
  184. +  val log: LoggingAdapter) extends ChannelPipelineFactory {
  185.  
  186.    import netty.settings
  187.  
  188. +  def initTLS(keyStorePath: String, keyStorePassword: String): Option[SSLContext] = {
  189. +    if (keyStorePath != null && keyStorePassword != null) {
  190. +      try {
  191. +        val factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
  192. +        val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
  193. +        val stream = new FileInputStream(keyStorePath)
  194. +        keyStore.load(stream, keyStorePassword.toCharArray)
  195. +        factory.init(keyStore, keyStorePassword.toCharArray)
  196. +        val sslContext = SSLContext.getInstance(settings.SSLProtocol.get)
  197. +        sslContext.init(factory.getKeyManagers, null, new SecureRandom())
  198. +        Some(sslContext)
  199. +      } catch {
  200. +        case e: FileNotFoundException ⇒ {
  201. +          log.error(e, "TLS connection could not be established because keystore could not be loaded")
  202. +          None
  203. +        }
  204. +        case e: GeneralSecurityException ⇒ {
  205. +          log.error(e, "TLS connection could not be established")
  206. +          None
  207. +        }
  208. +      }
  209. +    } else {
  210. +      log.error("TLS connection could not be established because key store details are missing")
  211. +      None
  212. +    }
  213. +  }
  214. +
  215. +  def getSSLHandler_? : Option[SslHandler] = {
  216. +    val sslContext: Option[SSLContext] = {
  217. +      if (settings.EnableSSL) {
  218. +        log.debug("SSL is enabled, initialising...")
  219. +        initTLS(settings.SSLKeyStore.get, settings.SSLKeyStorePassword.get)
  220. +      } else {
  221. +        None
  222. +      }
  223. +    }
  224. +    if (sslContext.isDefined) {
  225. +      log.debug("Using SSL context to create SSLEngine...")
  226. +      val sslEngine = sslContext.get.createSSLEngine
  227. +      sslEngine.setUseClientMode(false)
  228. +      sslEngine.setEnabledCipherSuites(settings.SSLSupportedAlgorithms.toArray.map(_.toString))
  229. +      Some(new SslHandler(sslEngine))
  230. +    } else {
  231. +      None
  232. +    }
  233. +  }
  234. +
  235.    def getPipeline: ChannelPipeline = {
  236. +    val sslHandler = getSSLHandler_?
  237.      val lenDec = new LengthFieldBasedFrameDecoder(settings.MessageFrameSize, 0, 4, 0, 4)
  238.      val lenPrep = new LengthFieldPrepender(4)
  239.      val messageDec = new RemoteMessageDecoder
  240. @@ -98,7 +154,13 @@ class RemoteServerPipelineFactory(
  241.      val authenticator = if (settings.RequireCookie) new RemoteServerAuthenticationHandler(settings.SecureCookie) :: Nil else Nil
  242.      val remoteServer = new RemoteServerHandler(openChannels, netty)
  243.      val stages: List[ChannelHandler] = lenDec :: messageDec :: lenPrep :: messageEnc :: executionHandler :: authenticator ::: remoteServer :: Nil
  244. -    new StaticChannelPipeline(stages: _*)
  245. +    if (sslHandler.isDefined) {
  246. +      log.debug("Creating pipeline with SSL handler...")
  247. +      new StaticChannelPipeline(sslHandler.get :: stages: _*)
  248. +    } else {
  249. +      log.debug("Creating pipeline without SSL handler...")
  250. +      new StaticChannelPipeline(stages: _*)
  251. +    }
  252.    }
  253.  }
  254.  
  255. diff --git a/akka-remote/src/main/scala/akka/remote/netty/Settings.scala b/akka-remote/src/main/scala/akka/remote/netty/Settings.scala
  256. index e2f69d7..2105620 100644
  257. --- a/akka-remote/src/main/scala/akka/remote/netty/Settings.scala
  258. +++ b/akka-remote/src/main/scala/akka/remote/netty/Settings.scala
  259. @@ -73,4 +73,45 @@ class NettySettings(config: Config, val systemName: String) {
  260.      case sz           ⇒ sz
  261.    }
  262.  
  263. +  val SSLKeyStore = getString("ssl-key-store") match {
  264. +    case ""       ⇒ None
  265. +    case keyStore ⇒ Some(keyStore)
  266. +  }
  267. +
  268. +  val SSLTrustStore = getString("ssl-trust-store") match {
  269. +    case ""         ⇒ None
  270. +    case trustStore ⇒ Some(trustStore)
  271. +  }
  272. +
  273. +  val SSLKeyStorePassword = getString("ssl-key-store-password") match {
  274. +    case ""       ⇒ None
  275. +    case password ⇒ Some(password)
  276. +  }
  277. +
  278. +  val SSLTrustStorePassword = getString("ssl-trust-store-password") match {
  279. +    case ""       ⇒ None
  280. +    case password ⇒ Some(password)
  281. +  }
  282. +
  283. +  val SSLSupportedAlgorithms = getStringList("ssl-supported-algorithms")
  284. +
  285. +  val SSLProtocol = getString("ssl-protocol") match {
  286. +    case ""       ⇒ None
  287. +    case protocol ⇒ Some(protocol)
  288. +  }
  289. +
  290. +  val EnableSSL = {
  291. +    val enableSSL = getBoolean("enable-ssl")
  292. +    if (enableSSL) {
  293. +      if (SSLProtocol.isEmpty) throw new ConfigurationException(
  294. +        "Configuration option 'akka.remote.netty.enable-ssl is turned on but no protocol is defined in 'akka.remote.netty.ssl-protocol'.")
  295. +      if (SSLKeyStore.isEmpty && SSLTrustStore.isEmpty) throw new ConfigurationException(
  296. +        "Configuration option 'akka.remote.netty.enable-ssl is turned on but no key/trust store is defined in 'akka.remote.netty.ssl-key-store' / 'akka.remote.netty.ssl-trust-store'.")
  297. +      if (SSLKeyStore.isDefined && SSLKeyStorePassword.isEmpty) throw new ConfigurationException(
  298. +        "Configuration option 'akka.remote.netty.ssl-key-store' is defined but no key-store password is defined in 'akka.remote.netty.ssl-key-store-password'.")
  299. +      if (SSLTrustStore.isDefined && SSLTrustStorePassword.isEmpty) throw new ConfigurationException(
  300. +        "Configuration option 'akka.remote.netty.ssl-trust-store' is defined but no trust-store password is defined in 'akka.remote.netty.ssl-trust-store-password'.")
  301. +    }
  302. +    enableSSL
  303. +  }
  304.  }
  305. \ No newline at end of file
  306. diff --git a/ls.sbt b/ls.sbt
  307. index 83e5bab..c28c42a 100644
  308. --- a/ls.sbt
  309. +++ b/ls.sbt
  310. @@ -11,3 +11,12 @@ seq(lsSettings:_*)
  311.  (licenses in LsKeys.lsync) := Seq(("Apache 2", url("http://www.apache.org/licenses/LICENSE-2.0.html")))
  312.  
  313.  (externalResolvers in LsKeys.lsync) := Seq("Typesafe Releases" at "http://repo.typesafe.com/typesafe/releases")
  314. +
  315. +publishTo <<= version { (v: String) =>
  316. +  if (v.trim.endsWith("SNAPSHOT"))
  317. +    Some("snapshots" at "http://192.168.7.1:8081/nexus/content/repositories/snapshots/")
  318. +  else
  319. +    Some("releases" at "http://192.168.7.1:8081/nexus/content/repositories/releases/")
  320. +}
  321. +
  322. +credentials += Credentials("Sonatype Nexus Repository Manager", "192.168.7.1", "deployment", "12345")
  323. diff --git a/project/Publish.scala b/project/Publish.scala
  324. index 9cea85a..4ea3b09 100644
  325. --- a/project/Publish.scala
  326. +++ b/project/Publish.scala
  327. @@ -16,7 +16,17 @@ object Publish {
  328.      publishTo <<= akkaPublishTo,
  329.      credentials ++= akkaCredentials,
  330.      organizationName := "Typesafe Inc.",
  331. -    organizationHomepage := Some(url("http://www.typesafe.com"))
  332. +    organizationHomepage := Some(url("http://www.typesafe.com")),
  333. +
  334. +    publishMavenStyle := true,
  335. +    publishArtifact in Test := false,
  336. +    pomIncludeRepository := { _ => false },
  337. +    publishTo <<= version { (v: String) =>
  338. +      if (v.trim.endsWith("SNAPSHOT"))
  339. +        Some("snapshots" at "http://192.168.7.1:8081/nexus/content/repositories/snapshots/")
  340. +      else
  341. +        Some("releases" at "http://192.168.7.1:8081/nexus/content/repositories/releases/")
  342. +    }
  343.    )
  344.  
  345.    lazy val versionSettings = Seq(
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement