Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

Commit

Permalink
! can, http: Add SSLSessionInfo header to requests on server and resp…
Browse files Browse the repository at this point in the history
…onses on client
  • Loading branch information
mpilquist committed Sep 9, 2013
1 parent 80982d4 commit e486900
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 66 deletions.
5 changes: 5 additions & 0 deletions spray-can/src/main/resources/reference.conf
Expand Up @@ -283,6 +283,11 @@ spray.can {
# in chunks regardless of whether chunking is actually used on the wire.
# Set to infinite to disable auto chunking.
incoming-auto-chunking-threshold-size = infinite

# Enables/disables inclusion of an SSL-Session-Info header in parsed
# messages over SSL transports (i.e., HttpRequest on server side and
# HttpResponse on client side).
ssl-session-info-header = off
}

# Fully qualified config path which holds the dispatcher configuration
Expand Down
Expand Up @@ -20,6 +20,7 @@ package client
import scala.concurrent.duration.Duration
import akka.actor.{ SupervisorStrategy, ReceiveTimeout, ActorRef }
import akka.io.{ Tcp, IO }
import spray.can.parsing.SSLSessionInfoSupport
import spray.http.{ SetRequestTimeout, Confirmed, HttpRequestPart }
import spray.io._

Expand Down Expand Up @@ -81,10 +82,11 @@ private[can] object HttpClientConnection {
import settings._
ClientFrontend(requestTimeout) >>
ResponseChunkAggregation(responseChunkAggregationLimit) ? (responseChunkAggregationLimit > 0) >>
SSLSessionInfoSupport ? parserSettings.sslSessionInfoHeader >>
ResponseParsing(parserSettings) >>
RequestRendering(settings) >>
ConnectionTimeouts(idleTimeout) ? (reapingCycle.isFinite && idleTimeout.isFinite) >>
SslTlsSupport(false) >>
SslTlsSupport(parserSettings.sslSessionInfoHeader) >>
TickGenerator(reapingCycle) ? (idleTimeout.isFinite || requestTimeout.isFinite)
}

Expand Down
Expand Up @@ -33,6 +33,7 @@ case class ParserSettings(
autoChunkingThreshold: Long,
uriParsingMode: Uri.ParsingMode,
illegalHeaderWarnings: Boolean,
sslSessionInfoHeader: Boolean,
headerValueCacheLimits: Map[String, Int]) {

require(maxUriLength > 0, "max-uri-length must be > 0")
Expand Down Expand Up @@ -67,6 +68,7 @@ object ParserSettings extends SettingsCompanion[ParserSettings]("spray.can.parsi
c getPossiblyInfiniteLongBytes "incoming-auto-chunking-threshold-size",
Uri.ParsingMode(c getString "uri-parsing-mode"),
c getBoolean "illegal-header-warnings",
c getBoolean "ssl-session-info-header",
cacheConfig.entrySet.asScala.map(kvp kvp.getKey -> cacheConfig.getInt(kvp.getKey))(collection.breakOut))
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2011-2013 spray.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package spray.can
package parsing

import spray.can.server.RequestParsing
import spray.io._
import spray.http.HttpMessageStart
import spray.http.HttpHeaders.`SSL-Session-Info`
import spray.util.SSLSessionInfo

/** Pipeline stage that adds the `SSL-Session-Info` header to incoming HTTP messages. */
object SSLSessionInfoSupport extends PipelineStage {

def apply(context: PipelineContext, commandPL: CPL, eventPL: EPL): Pipelines =
new Pipelines {
var sslSessionInfo: Option[`SSL-Session-Info`] = None

def addSessionInfoHeader(message: HttpMessageStart): HttpMessageStart =
sslSessionInfo.fold(message) { info message.mapHeaders { hdrs info :: hdrs } }

val commandPipeline: CPL = commandPL

val eventPipeline: EPL = {
case SslTlsSupport.SSLSessionEstablished(info)
sslSessionInfo = Some(`SSL-Session-Info`(info))

case Http.MessageEvent(msg: HttpMessageStart) if sslSessionInfo.isDefined
eventPL(Http.MessageEvent(addSessionInfoHeader(msg)))

case RequestParsing.HttpMessageStartEvent(part, closeAfterResponseCompletion) if sslSessionInfo.isDefined
eventPL(RequestParsing.HttpMessageStartEvent(addSessionInfoHeader(part), closeAfterResponseCompletion))

case ev eventPL(ev)
}
}
}
143 changes: 78 additions & 65 deletions spray-can/src/main/scala/spray/can/server/HttpServerConnection.scala
Expand Up @@ -23,6 +23,7 @@ import akka.io.Tcp
import spray.can.server.StatsSupport.StatsHolder
import spray.io._
import spray.can.Http
import spray.can.parsing.SSLSessionInfoSupport
import spray.http.{ SetTimeoutTimeout, SetRequestTimeout }

private[can] class HttpServerConnection(tcpConnection: ActorRef,
Expand Down Expand Up @@ -91,104 +92,115 @@ private[can] object HttpServerConnection {
* | MessageHandlerDispatch.DispatchCommand,
* | generates HttpResponsePartRenderingContext
* |------------------------------------------------------------------------------------------
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOk |
* | TickGenerator.Tick |
* | \/
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOK |
* | TickGenerator.Tick |
* | \/
* |------------------------------------------------------------------------------------------
* | RequestChunkAggregation: listens to HttpMessagePart events, generates HttpRequest events
* |------------------------------------------------------------------------------------------
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOk |
* | TickGenerator.Tick |
* | \/
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOK |
* | TickGenerator.Tick |
* | \/
* |------------------------------------------------------------------------------------------
* | PipeliningLimiter: throttles incoming requests according to the PipeliningLimit, listens
* | to HttpResponsePartRenderingContext commands and HttpRequestPart events,
* | generates StopReading and ResumeReading commands
* |------------------------------------------------------------------------------------------
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOk | IOServer.StopReading
* | TickGenerator.Tick | IOServer.ResumeReading
* | \/
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOK | IOServer.StopReading
* | TickGenerator.Tick | IOServer.ResumeReading
* | \/
* |------------------------------------------------------------------------------------------
* | StatsSupport: listens to most commands and events to collect statistics
* |------------------------------------------------------------------------------------------
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOk | IOServer.StopReading
* | TickGenerator.Tick | IOServer.ResumeReading
* | \/
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOK | IOServer.StopReading
* | TickGenerator.Tick | IOServer.ResumeReading
* | \/
* |------------------------------------------------------------------------------------------
* | RemoteAddressHeaderSupport: add `Remote-Address` headers to incoming requests
* |------------------------------------------------------------------------------------------
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOk | IOServer.StopReading
* | TickGenerator.Tick | IOServer.ResumeReading
* | \/
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOK | IOServer.StopReading
* | TickGenerator.Tick | IOServer.ResumeReading
* | \/
* |------------------------------------------------------------------------------------------
* | SSLSessionInfoSupport: add `SSL-Session-Info` header to incoming requests
* |------------------------------------------------------------------------------------------
* /\ |
* | HttpMessagePart | HttpResponsePartRenderingContext
* | IOServer.Closed | IOServer.Tell
* | IOServer.SentOK | IOServer.StopReading
* | TickGenerator.Tick | IOServer.ResumeReading
* | SslTlsSupport.SSLSessionEstablished |
* | \/
* |------------------------------------------------------------------------------------------
* | RequestParsing: converts Received events to HttpMessagePart,
* | generates HttpResponsePartRenderingContext (in case of errors)
* |------------------------------------------------------------------------------------------
* /\ |
* | IOServer.Closed | HttpResponsePartRenderingContext
* | IOServer.SentOk | IOServer.Tell
* | IOServer.Received | IOServer.StopReading
* | TickGenerator.Tick | IOServer.ResumeReading
* | \/
* /\ |
* | IOServer.Closed | HttpResponsePartRenderingContext
* | IOServer.SentOK | IOServer.Tell
* | IOServer.Received | IOServer.StopReading
* | TickGenerator.Tick | IOServer.ResumeReading
* | SslTlsSupport.SSLSessionEstablished |
* | \/
* |------------------------------------------------------------------------------------------
* | ResponseRendering: converts HttpResponsePartRenderingContext
* | to Send and Close commands
* |------------------------------------------------------------------------------------------
* /\ |
* | IOServer.Closed | IOServer.Send
* | IOServer.SentOk | IOServer.Close
* | IOServer.Received | IOServer.Tell
* | TickGenerator.Tick | IOServer.StopReading
* | | IOServer.ResumeReading
* | \/
* /\ |
* | IOServer.Closed | IOServer.Send
* | IOServer.SentOK | IOServer.Close
* | IOServer.Received | IOServer.Tell
* | TickGenerator.Tick | IOServer.StopReading
* | SslTlsSupport.SSLSessionEstablished | IOServer.ResumeReading
* | \/
* |------------------------------------------------------------------------------------------
* | ConnectionTimeouts: listens to Received events and Send commands and
* | TickGenerator.Tick, generates Close commands
* |------------------------------------------------------------------------------------------
* /\ |
* | IOServer.Closed | IOServer.Send
* | IOServer.SentOk | IOServer.Close
* | IOServer.Received | IOServer.Tell
* | TickGenerator.Tick | IOServer.StopReading
* | | IOServer.ResumeReading
* | \/
* /\ |
* | IOServer.Closed | IOServer.Send
* | IOServer.SentOK | IOServer.Close
* | IOServer.Received | IOServer.Tell
* | TickGenerator.Tick | IOServer.StopReading
* | SslTlsSupport.SSLSessionEstablished | IOServer.ResumeReading
* | \/
* |------------------------------------------------------------------------------------------
* | SslTlsSupport: listens to event Send and Close commands and Received events,
* | provides transparent encryption/decryption in both directions
* |------------------------------------------------------------------------------------------
* /\ |
* | IOServer.Closed | IOServer.Send
* | IOServer.SentOk | IOServer.Close
* | IOServer.Received | IOServer.Tell
* | TickGenerator.Tick | IOServer.StopReading
* | | IOServer.ResumeReading
* | \/
* /\ |
* | IOServer.Closed | IOServer.Send
* | IOServer.SentOK | IOServer.Close
* | IOServer.Received | IOServer.Tell
* | TickGenerator.Tick | IOServer.StopReading
* | | IOServer.ResumeReading
* | \/
* |------------------------------------------------------------------------------------------
* | TickGenerator: listens to Closed events,
* | dispatches TickGenerator.Tick events to the head of the event PL
* |------------------------------------------------------------------------------------------
* /\ |
* | IOServer.Closed | IOServer.Send
* | IOServer.SentOk | IOServer.Close
* | IOServer.Received | IOServer.Tell
* | TickGenerator.Tick | IOServer.StopReading
* | | IOServer.ResumeReading
* | \/
* /\ |
* | IOServer.Closed | IOServer.Send
* | IOServer.SentOK | IOServer.Close
* | IOServer.Received | IOServer.Tell
* | TickGenerator.Tick | IOServer.StopReading
* | | IOServer.ResumeReading
* | \/
*/
def pipelineStage(settings: ServerSettings, statsHolder: Option[StatsHolder]) = {
import settings._
Expand All @@ -197,10 +209,11 @@ private[can] object HttpServerConnection {
PipeliningLimiter(pipeliningLimit) ? (pipeliningLimit > 0) >>
StatsSupport(statsHolder.get) ? statsSupport >>
RemoteAddressHeaderSupport ? remoteAddressHeader >>
SSLSessionInfoSupport ? parserSettings.sslSessionInfoHeader >>
RequestParsing(settings) >>
ResponseRendering(settings) >>
ConnectionTimeouts(idleTimeout) ? (reapingCycle.isFinite && idleTimeout.isFinite) >>
SslTlsSupport(false) ? sslEncryption >>
SslTlsSupport(parserSettings.sslSessionInfoHeader) ? sslEncryption >>
TickGenerator(reapingCycle) ? (reapingCycle.isFinite && (idleTimeout.isFinite || requestTimeout.isFinite)) >>
BackPressureHandling(backpressureSettings.get.noAckRate, backpressureSettings.get.readingLowWatermark) ? backpressureSettings.isDefined
}
Expand Down
13 changes: 13 additions & 0 deletions spray-http/src/main/scala/spray/http/HttpHeader.scala
Expand Up @@ -19,6 +19,7 @@ package spray.http

import scala.annotation.{ implicitNotFound, tailrec }
import java.net.InetSocketAddress
import spray.util.SSLSessionInfo

abstract class HttpHeader extends ToStringRenderable {
def name: String
Expand Down Expand Up @@ -378,6 +379,18 @@ object HttpHeaders {
protected def companion = `X-Forwarded-For`
}

/**
* Provides information about the SSL session the message was received over.
*
* For non-certificate based cipher suites (e.g., Kerberos), `localCertificates` and `peerCertificates` are both empty lists.
*/
object `SSL-Session-Info` extends ModeledCompanion
case class `SSL-Session-Info`(info: SSLSessionInfo) extends ModeledHeader {
def renderValue[R <: Rendering](r: R): r.type = r ~~ "peer = " ~~ info.peerPrincipal.map { _.toString }.getOrElse("none")
protected def companion = `SSL-Session-Info`
override def toString = s"$name($info)"
}

case class RawHeader(name: String, value: String) extends HttpHeader {
val lowercaseName = name.toLowerCase
def render[R <: Rendering](r: R): r.type = r ~~ name ~~ ':' ~~ ' ' ~~ value
Expand Down
8 changes: 8 additions & 0 deletions spray-http/src/main/scala/spray/http/HttpMessage.scala
Expand Up @@ -64,6 +64,8 @@ object HttpResponsePart {

sealed trait HttpMessageStart extends HttpMessagePart {
def message: HttpMessage

def mapHeaders(f: List[HttpHeader] List[HttpHeader]): HttpMessageStart
}

object HttpMessageStart {
Expand Down Expand Up @@ -295,10 +297,16 @@ object MessageChunk {

case class ChunkedRequestStart(request: HttpRequest) extends HttpMessageStart with HttpRequestPart {
def message = request

def mapHeaders(f: List[HttpHeader] List[HttpHeader]): ChunkedRequestStart =
ChunkedRequestStart(request mapHeaders f)
}

case class ChunkedResponseStart(response: HttpResponse) extends HttpMessageStart with HttpResponsePart {
def message = response

def mapHeaders(f: List[HttpHeader] List[HttpHeader]): ChunkedResponseStart =
ChunkedResponseStart(response mapHeaders f)
}

object ChunkedMessageEnd extends ChunkedMessageEnd("", Nil)
Expand Down

0 comments on commit e486900

Please sign in to comment.