-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
finagle-mysql: Add Support for SSL/TLS
Problem MySQL contains support for SSL/TLS via messages sent during the connection phase of the MySQL protocol. However, finagle-mysql does not support the sending of the messages in the correct sequence in order to negotiate SSL/TLS. Solution Add a `SecureHandshake` which is capable of sending the proper sequence of messages to negotiate SSL/TLS with MySQL. JIRA Issues: CSL-7939 Differential Revision: https://phabricator.twitter.biz/D328077
- Loading branch information
1 parent
99e9dd4
commit 0b6c20a
Showing
6 changed files
with
134 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
finagle-mysql/src/main/scala/com/twitter/finagle/mysql/SecureHandshake.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package com.twitter.finagle.mysql | ||
|
||
import com.twitter.finagle.mysql.transport.Packet | ||
import com.twitter.finagle.netty4.ssl.client.Netty4ClientSslChannelInitializer | ||
import com.twitter.finagle.netty4.ssl.client.Netty4ClientSslChannelInitializer.OnSslHandshakeComplete | ||
import com.twitter.finagle.netty4.transport.ChannelTransportContext | ||
import com.twitter.finagle.transport.{Transport, TransportContext} | ||
import com.twitter.finagle.Stack | ||
import com.twitter.util.{Future, Promise, Try} | ||
import io.netty.channel.Channel | ||
|
||
private[mysql] final class SecureHandshake( | ||
params: Stack.Params, | ||
transport: Transport[Packet, Packet]) | ||
extends Handshake(params, transport) { | ||
|
||
private[this] def onHandshakeComplete(p: Promise[Unit])(result: Try[Unit]): Unit = | ||
p.updateIfEmpty(result) | ||
|
||
// We purposefully do not set an interrupt handler on this promise, since | ||
// there is no meaningful way to gracefully interrupt an SSL/TLS handshake. | ||
// This is very similar to how SSL/TLS negotiation works in finagle-mux. | ||
// See `Negotiation` for more details. | ||
private[this] def negotiateTls(): Future[Unit] = { | ||
val p = new Promise[Unit] | ||
val sslParams = params + OnSslHandshakeComplete(onHandshakeComplete(p)) | ||
val context: TransportContext = transport.context | ||
context match { | ||
case ctContext: ChannelTransportContext => | ||
val channel: Channel = ctContext.ch | ||
channel.pipeline.addFirst("mysqlSslInit", new Netty4ClientSslChannelInitializer(sslParams)) | ||
p | ||
case other => | ||
Future.exception( | ||
new IllegalStateException( | ||
s"SecureHandshake requires a channel to negotiate SSL/TLS. Found: $other")) | ||
} | ||
} | ||
|
||
private[this] def writeSslConnectionRequest(handshakeInit: HandshakeInit): Future[Unit] = { | ||
val request = SslConnectionRequest( | ||
settings.sslCalculatedClientCap, | ||
settings.charset, | ||
settings.maxPacketSize.inBytes.toInt) | ||
transport.write(request.toPacket) | ||
} | ||
|
||
private[this] def makeSecureHandshakeResponse(handshakeInit: HandshakeInit): HandshakeResponse = | ||
SecureHandshakeResponse( | ||
settings.username, | ||
settings.password, | ||
settings.database, | ||
settings.sslCalculatedClientCap, | ||
handshakeInit.salt, | ||
handshakeInit.serverCap, | ||
settings.charset, | ||
settings.maxPacketSize.inBytes.toInt | ||
) | ||
|
||
// For the `SecureHandshake`, after the init, | ||
// we return an `SslConnectionRequest`, | ||
// neogtiate SSL/TLS, and then return a handshake response. | ||
def connectionPhase(): Future[Result] = { | ||
readHandshakeInit() | ||
.flatMap { handshakeInit => | ||
writeSslConnectionRequest(handshakeInit) | ||
.flatMap(_ => negotiateTls()) | ||
.map(_ => handshakeInit) | ||
} | ||
.map(makeSecureHandshakeResponse) | ||
.flatMap(messageDispatch) | ||
.onFailure(_ => transport.close()) | ||
} | ||
|
||
} |
36 changes: 36 additions & 0 deletions
36
finagle-mysql/src/test/scala/com/twitter/finagle/mysql/integration/MysqlSslTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.twitter.finagle.mysql.integration | ||
|
||
import com.twitter.finagle.Mysql | ||
import com.twitter.finagle.ssl.{Protocols, TrustCredentials} | ||
import com.twitter.finagle.ssl.client.SslClientConfiguration | ||
import com.twitter.util.{Await, Duration} | ||
import org.scalatest.FunSuite | ||
|
||
class MysqlSslTest extends FunSuite with IntegrationClient { | ||
|
||
override protected def configureClient( | ||
username: String, | ||
password: String, | ||
db: String | ||
): Mysql.Client = { | ||
// We care more about testing the SSL/TLS wiring throughout | ||
// MySQL here than verifying certificates and hostnames. | ||
// | ||
// The community edition of MySQL 5.7 isn't built by default | ||
// with OpenSSL, and so uses yaSSL, which only supports up to | ||
// TLSv1.1. So we use TLSv1.1 here. | ||
val sslClientConfig = SslClientConfiguration( | ||
trustCredentials = TrustCredentials.Insecure, | ||
protocols = Protocols.Enabled(Seq("TLSv1.1"))) | ||
super | ||
.configureClient(username, password, db) | ||
.withTransport.tls(sslClientConfig) | ||
} | ||
|
||
test("ping over ssl") { | ||
val theClient = client.orNull | ||
val result = Await.result(theClient.ping(), Duration.fromSeconds(2)) | ||
// If we get here, result is Unit, and all is good | ||
} | ||
|
||
} |