forked from linkerd/linkerd
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce maxCallDepth param to terminate requests that have hopped t…
…oo much (linkerd#2300) This PR introduces `maxCallDepth` param that defaults to 1000 at the moment. This parameter, combined with a `MaxCallDepthFilter` allows us to detect requests that have done too many hops and return a proper `400` status and message. Fixes linkerd#1411 Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>
- Loading branch information
1 parent
9026195
commit d87f193
Showing
14 changed files
with
190 additions
and
9 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
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
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
50 changes: 50 additions & 0 deletions
50
router/base-http/src/main/scala/io/buoyant/router/http/MaxCallDepthFilter.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,50 @@ | ||
package io.buoyant.router.http | ||
|
||
import com.twitter.finagle._ | ||
import com.twitter.finagle.http.Fields.Via | ||
import com.twitter.finagle.http.{Request, Response} | ||
import com.twitter.util.Future | ||
import io.buoyant.router.http.ForwardClientCertFilter.Enabled | ||
import io.buoyant.router.http.MaxCallDepthFilter.MaxCallDepthExceeded | ||
import scala.util.control.NoStackTrace | ||
|
||
class MaxCallDepthFilter[Req, H: HeadersLike, Rep](maxCalls: Int, headerKey: String) | ||
(implicit requestLike: RequestLike[Req, H]) extends SimpleFilter[Req, Rep] { | ||
|
||
def numCalls(viaValue: String) = viaValue.split(",").length | ||
|
||
def apply(req: Req, svc: Service[Req, Rep]): Future[Rep] = { | ||
val headersLike = implicitly[HeadersLike[H]] | ||
|
||
headersLike.get(requestLike.headers(req), headerKey) match { | ||
case Some(v) if numCalls(v) > maxCalls => Future.exception(MaxCallDepthExceeded(maxCalls)) | ||
case _ => svc(req) | ||
} | ||
} | ||
} | ||
|
||
object MaxCallDepthFilter { | ||
|
||
final case class MaxCallDepthExceeded(calls: Int) | ||
extends Exception(s"Maximum number of calls ($calls) has been exceeded. Please check for proxy loops.") | ||
with NoStackTrace | ||
|
||
final case class Param(value: Int) extends AnyVal { | ||
def mk(): (Param, Stack.Param[Param]) = (this, Param.param) | ||
} | ||
|
||
object Param { | ||
implicit val param = Stack.Param(Param(1000)) | ||
} | ||
|
||
def module[Req, H: HeadersLike, Rep](headerKey: String) | ||
(implicit requestLike: RequestLike[Req, H]): Stackable[ServiceFactory[Req, Rep]] = | ||
new Stack.Module1[Param, ServiceFactory[Req, Rep]] { | ||
val role = Stack.Role("MaxCallDepthFilter") | ||
val description = "Limits the number of hops by looking at the Via header of a request" | ||
|
||
def make(param: Param, next: ServiceFactory[Req, Rep]) = | ||
new MaxCallDepthFilter(param.value, headerKey) andThen next | ||
|
||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
router/base-http/src/test/scala/io/buoyant/router/http/MaxCallDepthFilterTest.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,52 @@ | ||
package io.buoyant.router.http | ||
|
||
import com.twitter.finagle.Service | ||
import com.twitter.finagle.http.Fields.Via | ||
import com.twitter.finagle.http.{HeaderMap, Request, Response, Status} | ||
import com.twitter.util.Future | ||
import io.buoyant.test.{Awaits, FunSuite} | ||
|
||
|
||
class MaxCallDepthFilterTest extends FunSuite with Awaits { | ||
|
||
implicit object HttpHeadersLike extends HeadersLike[HeaderMap] { | ||
override def toSeq(headers: HeaderMap): Seq[(String, String)] = ??? | ||
override def contains(headers: HeaderMap, k: String): Boolean = ??? | ||
override def get(headers: HeaderMap, k: String): Option[String] = headers.get(k) | ||
override def getAll(headers: HeaderMap, k: String): Seq[String] = ??? | ||
override def add(headers: HeaderMap, k: String, v: String): Unit = ??? | ||
override def set(headers: HeaderMap, k: String, v: String): Unit = ??? | ||
override def remove(headers: HeaderMap, key: String): Seq[String] = ??? | ||
} | ||
|
||
implicit object HttpRequestLike extends RequestLike[Request, HeaderMap] { | ||
override def headers(request: Request): HeaderMap = request.headerMap | ||
} | ||
|
||
|
||
def service(maxCallDepth: Int) = new MaxCallDepthFilter[Request, HeaderMap, Response]( | ||
maxCallDepth, | ||
Via | ||
).andThen(Service.mk[Request, Response](_ => Future.value(Response()))) | ||
|
||
test("passes through requests not exceeding max hops") { | ||
val viaHeader = (1 to 10).map(v => s"hop $v").mkString(", ") | ||
val req = Request() | ||
req.headerMap.add(Via, viaHeader) | ||
assert(await(service(10)(req)).status == Status.Ok) | ||
} | ||
|
||
test("stops requests exceeding max hops") { | ||
val expectedMessage = "Maximum number of calls (9) has been exceeded. Please check for proxy loops." | ||
val viaHeader = (1 to 10).map(v => s"hop $v").mkString(", ") | ||
val req = Request() | ||
req.headerMap.add(Via, viaHeader) | ||
|
||
val exception = intercept[MaxCallDepthFilter.MaxCallDepthExceeded] { | ||
await(service(9)(req)) | ||
} | ||
assert(exception.getMessage == expectedMessage) | ||
} | ||
|
||
|
||
} |
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