From 1e129761927f1ff14055b201d65c35ccd47bd864 Mon Sep 17 00:00:00 2001 From: idawda Date: Thu, 6 Jun 2024 09:50:05 +0530 Subject: [PATCH] Fix for NR-251467 Add new module to support akka-http-core_2.13 version 10.1.8 and above Co-authored-by: Lovesh Baya --- .../akka-http-core-2.11_10.0.11/build.gradle | 8 +- .../akka-http-core-2.13_10.1.8/build.gradle | 32 +++ .../scaladsl/AkkaAsyncRequestHandler.scala | 39 +++ .../akka/http/scaladsl/AkkaCoreUtils.java | 216 +++++++++++++++ .../scaladsl/AkkaSyncRequestHandler.scala | 39 +++ .../scaladsl/HttpExt_Instrumentation.java | 178 +++++++++++++ .../http/scaladsl/Http_Instrumentation.java | 56 ++++ .../IncomingConnection_Instrumentation.java | 38 +++ .../http/scaladsl/ResponseFutureHelper.scala | 81 ++++++ .../scala/Serializable_Instrumentation.scala | 8 + .../akka/http/core_10/AkkaHttpCoreTest.scala | 247 ++++++++++++++++++ .../akka/http/core_10/AkkaServer.scala | 82 ++++++ .../akka/http/core_10/PlayServer.scala | 73 ++++++ settings.gradle | 1 + 14 files changed, 1094 insertions(+), 4 deletions(-) create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/build.gradle create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaAsyncRequestHandler.scala create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaCoreUtils.java create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaSyncRequestHandler.scala create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/HttpExt_Instrumentation.java create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/Http_Instrumentation.java create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/IncomingConnection_Instrumentation.java create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/ResponseFutureHelper.scala create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/scala/Serializable_Instrumentation.scala create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/AkkaHttpCoreTest.scala create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/AkkaServer.scala create mode 100644 instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/PlayServer.scala diff --git a/instrumentation-security/akka-http-core-2.11_10.0.11/build.gradle b/instrumentation-security/akka-http-core-2.11_10.0.11/build.gradle index 9f6be648e..afeb8e816 100644 --- a/instrumentation-security/akka-http-core-2.11_10.0.11/build.gradle +++ b/instrumentation-security/akka-http-core-2.11_10.0.11/build.gradle @@ -1,6 +1,6 @@ apply plugin: 'scala' -isScalaProjectEnabled(project, "scala-2.12") +isScalaProjectEnabled(project, "scala-2.11") jar { manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.akka-http-core-10.0.11' } @@ -10,14 +10,14 @@ dependencies { implementation(project(":newrelic-security-api")) implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") - implementation("com.typesafe.akka:akka-http-core_2.12:10.0.11") - implementation("com.typesafe.akka:akka-stream_2.12:2.5.23") + implementation("com.typesafe.akka:akka-http-core_2.11:10.0.11") + implementation("com.typesafe.akka:akka-stream_2.11:2.5.23") } verifyInstrumentation { fails('com.typesafe.akka:akka-http-core-experimental_2.11:[1.0,10.0.11)') fails('com.typesafe.akka:akka-http-core-experimental_2.10:[1.0,10.0.11)') - passesOnly('com.typesafe.akka:akka-http-core_2.13:[10.1.8,10.2.0-RC1)') { + fails('com.typesafe.akka:akka-http-core_2.13:[10.1.8,10.2.0-RC1)') { implementation("com.typesafe.akka:akka-stream_2.13:2.5.23") } passesOnly('com.typesafe.akka:akka-http-core_2.11:[10.0.11,)') { diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/build.gradle b/instrumentation-security/akka-http-core-2.13_10.1.8/build.gradle new file mode 100644 index 000000000..1988a5be0 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.13") + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.akka-http-core-2.13_10.1.8' } +} + +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("com.typesafe.akka:akka-http-core_2.13:10.1.8") + implementation("com.typesafe.akka:akka-stream_2.13:2.5.23") +} + +verifyInstrumentation { + passesOnly('com.typesafe.akka:akka-http-core_2.13:[10.1.8,10.2.0-RC1)') { + implementation("com.typesafe.akka:akka-stream_2.13:2.5.23") + } + fails('com.typesafe.akka:akka-http-core_2.11:[10.0.11,)') { + implementation("com.typesafe.akka:akka-stream_2.11:2.5.11") + } + fails('com.typesafe.akka:akka-http-core_2.12:[10.0.11,10.2.0-RC1)') { + implementation("com.typesafe.akka:akka-stream_2.12:2.5.11") + } +} + +site { + title 'Akka Http Core' + type 'Framework' +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaAsyncRequestHandler.scala b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaAsyncRequestHandler.scala new file mode 100644 index 000000000..f1d3534c5 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaAsyncRequestHandler.scala @@ -0,0 +1,39 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package akka.http.scaladsl + +import akka.Done +import akka.http.scaladsl.model.{HttpRequest, HttpResponse} +import akka.stream.Materializer +import akka.stream.javadsl.Source +import akka.stream.scaladsl.Sink +import akka.util.ByteString +import com.newrelic.api.agent.{NewRelic, Trace} + +import java.lang +import scala.concurrent.{ExecutionContext, Future} +import scala.runtime.AbstractFunction1 + +class AkkaAsyncRequestHandler(handler: HttpRequest ⇒ Future[HttpResponse])(implicit ec: ExecutionContext, materializer: Materializer) extends AbstractFunction1[HttpRequest, Future[HttpResponse]] { + + @Trace(dispatcher = true) + override def apply(param: HttpRequest): Future[HttpResponse] = { + val body: lang.StringBuilder = new lang.StringBuilder(); + val dataBytes: Source[ByteString, AnyRef] = param.entity.getDataBytes() + val isLockAquired = AkkaCoreUtils.acquireServletLockIfPossible(); + val sink: Sink[ByteString, Future[Done]] = Sink.foreach[ByteString] { byteString => + val chunk = byteString.utf8String + body.append(chunk) + } + val processingResult: Future[Done] = dataBytes.runWith(sink, materializer) + AkkaCoreUtils.preProcessHttpRequest(isLockAquired, param, body, NewRelic.getAgent.getTransaction.getToken); + val futureResponse: Future[HttpResponse] = handler.apply(param) + futureResponse.flatMap(ResponseFutureHelper.wrapResponseAsync(NewRelic.getAgent.getTransaction.getToken, materializer)) + futureResponse + } +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaCoreUtils.java b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaCoreUtils.java new file mode 100644 index 000000000..0a0d4fb68 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaCoreUtils.java @@ -0,0 +1,216 @@ +package akka.http.scaladsl; + +import akka.http.javadsl.model.HttpHeader; +import akka.http.scaladsl.model.HttpRequest; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ICsecApiConstants; +import com.newrelic.api.agent.security.instrumentation.helpers.LowSeverityHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AgentMetaData; +import com.newrelic.api.agent.security.schema.SecurityMetaData; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.RXSSOperation; +import com.newrelic.api.agent.security.schema.policy.AgentPolicy; +import com.newrelic.api.agent.security.utils.logging.LogLevel; + +import java.util.*; + +public class AkkaCoreUtils { + + public static final String METHOD_SINGLE_REQUEST_IMPL = "singleRequestImpl"; + + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "HTTPREQUEST_OPERATION_LOCK_AKKA-"; + + public static final String AKKA_HTTP_CORE_10_0_11 = "AKKA_HTTP_CORE_10.0.11"; + + private static final String X_FORWARDED_FOR = "x-forwarded-for"; + private static final String EMPTY = ""; + public static final String QUESTION_MARK = "?"; + + public static boolean isServletLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) {} + return false; + } + + public static void releaseServletLock() { + try { + if(NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored){} + } + + private static String getNrSecCustomAttribName() { + return NR_SEC_CUSTOM_ATTRIB_NAME; + } + + public static boolean acquireServletLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && + !isServletLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored){} + return false; + } + + public static void postProcessHttpRequest(Boolean isServletLockAcquired, StringBuilder responseBody, String contentType, String className, String methodName, Token token) { + try { + token.linkAndExpire(); + if(!isServletLockAcquired || !NewRelicSecurity.isHookProcessingActive()){ + return; + } + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseContentType(contentType); + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseBody(responseBody); + LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); + + if(!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().getResponseContentType())) { + RXSSOperation rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(), + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse(), + className, methodName); + NewRelicSecurity.getAgent().registerOperation(rxssOperation); + } + ServletHelper.tmpFileCleanUp(NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getTempFiles()); + } catch (Throwable e) { + if(e instanceof NewRelicSecurityException){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, AkkaCoreUtils.class.getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, AkkaCoreUtils.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, AkkaCoreUtils.class.getName()); + } finally { + if(isServletLockAcquired){ + releaseServletLock(); + } + } + } + + public static void preProcessHttpRequest (Boolean isServletLockAcquired, HttpRequest httpRequest, StringBuilder requestBody, Token token) { + if(!isServletLockAcquired) { + return; + } + + try { + token.linkAndExpire(); + if (!NewRelicSecurity.isHookProcessingActive()) { + return; + } + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + + com.newrelic.api.agent.security.schema.HttpRequest securityRequest = securityMetaData.getRequest(); + if (securityRequest.isRequestParsed()) { + return; + } + + AgentMetaData securityAgentMetaData = securityMetaData.getMetaData(); + + securityRequest.setMethod(httpRequest.method().value()); + //TODO Client IP and PORT extraction is pending + +// securityRequest.setClientIP(); + securityRequest.setServerPort(httpRequest.getUri().getPort()); + + processHttpRequestHeader(httpRequest, securityRequest); + + securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders())); + + securityRequest.setProtocol(getProtocol(httpRequest.protocol().value())); + + securityRequest.setUrl(httpRequest.getUri().path()); + String queryString = null; + Optional rawQueryString = httpRequest.getUri().rawQueryString(); + if(rawQueryString.isPresent()) { + queryString = rawQueryString.get(); + } + if (queryString != null && !queryString.trim().isEmpty()) { + securityRequest.setUrl(securityRequest.getUrl() + QUESTION_MARK + queryString); + } + + securityRequest.setContentType(httpRequest.entity().getContentType().toString()); + + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length)); + securityRequest.setBody(requestBody); + securityRequest.setRequestParsed(true); + } catch (Throwable ignored){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, AKKA_HTTP_CORE_10_0_11, ignored.getMessage()), ignored, AkkaCoreUtils.class.getName()); + } + finally { + if(isServletLockAcquired()){ + releaseServletLock(); + } + } + } + + public static String getTraceHeader(Map headers) { + String data = EMPTY; + if (headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER) || headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase())) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER); + if (data == null || data.trim().isEmpty()) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase()); + } + } + return data; + } + + public static void processHttpRequestHeader(HttpRequest request, com.newrelic.api.agent.security.schema.HttpRequest securityRequest){ + Iterator headers = request.getHeaders().iterator(); + while (headers.hasNext()) { + boolean takeNextValue = false; + HttpHeader nextHeader = headers.next(); + String headerKey = nextHeader.name(); + if(headerKey != null){ + headerKey = headerKey.toLowerCase(); + } + AgentPolicy agentPolicy = NewRelicSecurity.getAgent().getCurrentPolicy(); + AgentMetaData agentMetaData = NewRelicSecurity.getAgent().getSecurityMetaData().getMetaData(); + if (agentPolicy != null + && agentPolicy.getProtectionMode().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getIpDetectViaXFF() + && X_FORWARDED_FOR.equals(headerKey)) { + takeNextValue = true; + } else if (ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID.equals(headerKey)) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent().getSecurityMetaData() + .setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(nextHeader.value())); + } else if(GenericHelper.CSEC_PARENT_ID.equals(headerKey)) { + NewRelicSecurity.getAgent().getSecurityMetaData() + .addCustomAttribute(GenericHelper.CSEC_PARENT_ID, request.getHeader(headerKey).get().value()); + } else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST.equals(headerKey)) { + NewRelicSecurity.getAgent().getSecurityMetaData() + .addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true); + } + String headerFullValue = nextHeader.value(); + if (headerFullValue != null && !headerFullValue.trim().isEmpty()) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true); + securityRequest.setClientIP(headerFullValue); + agentMetaData.getIps() + .add(securityRequest.getClientIP()); + securityRequest.setClientPort(EMPTY); + takeNextValue = false; + } + } + securityRequest.getHeaders().put(headerKey, headerFullValue); + } + + } + + private static String getProtocol(String value) { + if(StringUtils.containsIgnoreCase(value, "https")){ + return "https"; + } else if (StringUtils.containsIgnoreCase(value, "http")) { + return "http"; + } else { + return value; + } + } +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaSyncRequestHandler.scala b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaSyncRequestHandler.scala new file mode 100644 index 000000000..18dbe8b67 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/AkkaSyncRequestHandler.scala @@ -0,0 +1,39 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package akka.http.scaladsl + +import akka.Done +import akka.http.scaladsl.model.{HttpRequest, HttpResponse} +import akka.stream.Materializer +import akka.stream.javadsl.Source +import akka.stream.scaladsl.Sink +import akka.util.ByteString +import com.newrelic.api.agent.{NewRelic, Trace} + +import java.lang +import scala.concurrent.Future +import scala.runtime.AbstractFunction1 + +class AkkaSyncRequestHandler(handler: HttpRequest ⇒ HttpResponse)(implicit materializer: Materializer) extends AbstractFunction1[HttpRequest, HttpResponse] { + + @Trace(dispatcher = true) + override def apply(param: HttpRequest): HttpResponse = { + val body: lang.StringBuilder = new lang.StringBuilder(); + val dataBytes: Source[ByteString, AnyRef] = param.entity.getDataBytes() + val isLockAquired = AkkaCoreUtils.acquireServletLockIfPossible(); + val sink: Sink[ByteString, Future[Done]] = Sink.foreach[ByteString] { byteString => + val chunk = byteString.utf8String + body.append(chunk) + } + val processingResult: Future[Done] = dataBytes.runWith(sink, materializer) + AkkaCoreUtils.preProcessHttpRequest(isLockAquired, param, body, NewRelic.getAgent.getTransaction.getToken); + val response: HttpResponse = handler.apply(param) + ResponseFutureHelper.wrapResponseSync(response, materializer) + response + } +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/HttpExt_Instrumentation.java b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/HttpExt_Instrumentation.java new file mode 100644 index 000000000..ea8efb5ba --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/HttpExt_Instrumentation.java @@ -0,0 +1,178 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package akka.http.scaladsl; + +import akka.event.LoggingAdapter; +import akka.http.scaladsl.model.HttpRequest; +import akka.http.scaladsl.model.HttpResponse; +import akka.http.scaladsl.model.headers.RawHeader; +import akka.http.scaladsl.settings.ConnectionPoolSettings; +import akka.http.scaladsl.settings.ServerSettings; +import akka.stream.Materializer; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.SecurityMetaData; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.SSRFOperation; +import com.newrelic.api.agent.security.utils.SSRFUtils; +import com.newrelic.api.agent.security.utils.logging.LogLevel; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import scala.Function1; +import scala.concurrent.Future; + +import java.net.URI; + +@Weave(type = MatchType.ExactClass, originalName = "akka.http.scaladsl.HttpExt") +public class HttpExt_Instrumentation { + + public Future bindAndHandleAsync( + Function1> handler, + String interfaceString, int port, + ConnectionContext connectionContext, + ServerSettings settings, int parallelism, + LoggingAdapter adapter, Materializer mat) { + + AkkaAsyncRequestHandler wrapperHandler = new AkkaAsyncRequestHandler(handler, mat.executionContext(), mat); + handler = wrapperHandler; + + return Weaver.callOriginal(); + } + + public Future bindAndHandleSync( + Function1 handler, + String interfaceString, int port, + ConnectionContext connectionContext, + ServerSettings settings, + LoggingAdapter adapter, Materializer mat) { + + AkkaSyncRequestHandler wrapperHandler = new AkkaSyncRequestHandler(handler, mat); + handler = wrapperHandler; + + return Weaver.callOriginal(); + } + + + // We are weaving the singleRequestImpl method here rather than just singleRequest because the javadsl only flows through here + public Future singleRequestImpl(HttpRequest httpRequest, HttpsConnectionContext connectionContext, ConnectionPoolSettings poolSettings, + LoggingAdapter loggingAdapter) { + + boolean isLockAcquired = acquireLockIfPossible(); + AbstractOperation operation = null; + // Preprocess Phase + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + if (isLockAcquired) { + operation = preprocessSecurityHook(httpRequest, AkkaCoreUtils.METHOD_SINGLE_REQUEST_IMPL); + } + + if (operation!=null) { + String iastHeader = NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getRaw(); + if (iastHeader != null && !iastHeader.trim().isEmpty()) { + httpRequest = (HttpRequest) httpRequest.addHeader(RawHeader.apply(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, iastHeader)); + } + + String csecParaentId = securityMetaData.getCustomAttribute(GenericHelper.CSEC_PARENT_ID, String.class); + if(StringUtils.isNotBlank(csecParaentId)){ + httpRequest = (HttpRequest) httpRequest.addHeader(RawHeader.apply(GenericHelper.CSEC_PARENT_ID, csecParaentId)); + } + + try { + NewRelicSecurity.getAgent().registerOperation(operation); + } catch (Exception e) { + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, this.getClass().getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE , String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, this.getClass().getName()); + } finally { + if (operation.getApiID() != null && !operation.getApiID().trim().isEmpty() && + operation.getExecutionId() != null && !operation.getExecutionId().trim().isEmpty()) { + // Add Security distributed tracing header + httpRequest = (HttpRequest) httpRequest.addHeader(RawHeader.apply(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, + SSRFUtils.generateTracingHeaderValue(securityMetaData.getTracingHeaderValue(), operation.getApiID(), operation.getExecutionId(), + NewRelicSecurity.getAgent().getAgentUUID()))); + } + } + } + + Future returnCode = null; + // Actual Call + try { + returnCode = Weaver.callOriginal(); + } finally { + if (isLockAcquired) { + releaseLock(); + } + } + registerExitOperation(isLockAcquired, operation); + return returnCode; + } + + private static void registerExitOperation(boolean isProcessingAllowed, AbstractOperation operation) { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive() || NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() + ) { + return; + } + NewRelicSecurity.getAgent().registerExitEvent(operation); + } catch (Throwable ignored) { + NewRelicSecurity.getAgent().log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_CORE_10_0_11, ignored.getMessage()), ignored, HttpExt_Instrumentation.class.getName()); + } + } + + private AbstractOperation preprocessSecurityHook(HttpRequest httpRequest, String methodName) { + try { + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + if (!NewRelicSecurity.isHookProcessingActive() || securityMetaData.getRequest().isEmpty()) { + return null; + } + + // Generate required URL + URI methodURI = null; + String uri = null; + try { + methodURI = new URI(httpRequest.getUri().toString()); + uri = methodURI.toString(); + if (methodURI == null) { + return null; + } + } catch (Exception ignored){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.URI_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_CORE_10_0_11, ignored.getMessage()), ignored, this.getClass().getName()); + return null; + } + + SSRFOperation operation = new SSRFOperation(uri, this.getClass().getName(), methodName); + return operation; + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, this.getClass().getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, this.getClass().getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE , String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, this.getClass().getName()); + } + return null; + } + + private void releaseLock() { + try { + GenericHelper.releaseLock(AkkaCoreUtils.NR_SEC_CUSTOM_ATTRIB_NAME, this.hashCode()); + } catch (Throwable ignored) { + } + } + + private boolean acquireLockIfPossible() { + try { + return GenericHelper.acquireLockIfPossible(AkkaCoreUtils.NR_SEC_CUSTOM_ATTRIB_NAME, this.hashCode()); + } catch (Throwable ignored) { + } + return false; + } + +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/Http_Instrumentation.java b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/Http_Instrumentation.java new file mode 100644 index 000000000..5aee71a85 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/Http_Instrumentation.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package akka.http.scaladsl; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.WeaveAllConstructors; +import com.newrelic.api.agent.weaver.Weaver; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.util.logging.Level; + +@Weave(type = MatchType.ExactClass, originalName = "akka.http.scaladsl.Http") +public class Http_Instrumentation { + + @Weave(type = MatchType.ExactClass, originalName = "akka.http.scaladsl.Http$ServerBinding") + public static class ServerBinding { + + public InetSocketAddress localAddress() { + return Weaver.callOriginal(); + } + + @WeaveAllConstructors + public ServerBinding() { + // AgentBridge.getAgent().getLogger().log(Level.FINE, "Setting akka-http port to: {0,number,#}", localAddress().getPort()); +// AgentBridge.publicApi.setAppServerPort(localAddress().getPort()); +// AgentBridge.publicApi.setServerInfo("Akka HTTP", ManifestUtils.getVersionFromManifest(getClass(), "akka-http-core", "10.2.0")); + + NewRelicSecurity.getAgent().setApplicationConnectionConfig(localAddress().getPort(), "http"); + try { + Class agentBridgeClass = Class.forName("com.newrelic.agent.bridge.AgentBridge"); + Field instrumentation = agentBridgeClass.getDeclaredField("instrumentation"); + Object instrumentationObject = instrumentation.get(null); + + Class instrumentationInterface = Class.forName("com.newrelic.agent.bridge.Instrumentation"); + Method retransformUninstrumentedClassMethod = instrumentationInterface.getDeclaredMethod("retransformUninstrumentedClass", Class.class); + + retransformUninstrumentedClassMethod.invoke(instrumentationObject, AkkaSyncRequestHandler.class); + retransformUninstrumentedClassMethod.invoke(instrumentationObject, AkkaAsyncRequestHandler.class); + } catch (Throwable e) { + NewRelic.getAgent().getLogger().log(Level.SEVERE, "Unable to instrument com.newrelic.instrumentation.security.akka-http-core-2.13_10.2.0 due to error", e); + } + + } + } + +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/IncomingConnection_Instrumentation.java b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/IncomingConnection_Instrumentation.java new file mode 100644 index 000000000..f55bd50e3 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/IncomingConnection_Instrumentation.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package akka.http.scaladsl; + +import akka.http.scaladsl.model.HttpRequest; +import akka.http.scaladsl.model.HttpResponse; +import akka.stream.Materializer; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import scala.Function1; +import scala.concurrent.Future; + +@Weave(originalName = "akka.http.scaladsl.Http$IncomingConnection") +public class IncomingConnection_Instrumentation { + + public void handleWithSyncHandler(Function1 func, Materializer mat) { + + AkkaSyncRequestHandler wrapperHandler = new AkkaSyncRequestHandler(func, mat); + func = wrapperHandler; + + Weaver.callOriginal(); + } + + public void handleWithAsyncHandler(Function1> func, int parallel, Materializer mat) { + + AkkaAsyncRequestHandler wrapperHandler = new AkkaAsyncRequestHandler(func, mat.executionContext(), mat); + func = wrapperHandler; + + Weaver.callOriginal(); + } + + +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/ResponseFutureHelper.scala b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/ResponseFutureHelper.scala new file mode 100644 index 000000000..1e9b6e179 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/akka/http/scaladsl/ResponseFutureHelper.scala @@ -0,0 +1,81 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package akka.http.scaladsl + +import akka.Done +import akka.http.scaladsl.model.HttpResponse +import akka.stream.Materializer +import akka.stream.javadsl.Source +import akka.stream.scaladsl.Sink +import akka.util.ByteString +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.schema.StringUtils +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException +import com.newrelic.api.agent.security.utils.logging.LogLevel +import com.newrelic.api.agent.{NewRelic, Token} + +import java.lang +import scala.concurrent.{ExecutionContext, Future} + +object ResponseFutureHelper { + + def wrapResponseAsync(token: Token, materializer: Materializer)(implicit ec: ExecutionContext): (HttpResponse) => Future[HttpResponse] = { response:HttpResponse => { + Future { + var updatedResponse: HttpResponse = response + + try { + val stringResponse: lang.StringBuilder = new lang.StringBuilder(); + val dataBytes: Source[ByteString, _] = response.entity.getDataBytes() + val isLockAquired = AkkaCoreUtils.acquireServletLockIfPossible(); + val sink: Sink[ByteString, Future[Done]] = Sink.foreach[ByteString] { byteString => + val chunk = byteString.utf8String + stringResponse.append(chunk) + } + val processingResult: Future[Done] = dataBytes.runWith(sink, materializer) + processingResult.onComplete { + _ => { + token.linkAndExpire() + AkkaCoreUtils.postProcessHttpRequest(isLockAquired, stringResponse, response.entity.contentType.toString(), this.getClass.getName, "apply", NewRelic.getAgent.getTransaction.getToken) + } + } + + } catch { + case t: NewRelicSecurityException => + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_CORE_10_0_11, t.getMessage), t, classOf[AkkaCoreUtils].getName) + throw t + case _: Throwable => + } + + updatedResponse + } + } + } + + def wrapResponseSync(httpResponse: HttpResponse, materializer: Materializer) { + try { + val stringResponse: lang.StringBuilder = new lang.StringBuilder(); + val dataBytes: Source[ByteString, _] = httpResponse.entity.getDataBytes() + val isLockAquired = AkkaCoreUtils.acquireServletLockIfPossible(); + val sink: Sink[ByteString, Future[Done]] = Sink.foreach[ByteString] { byteString => + val chunk = byteString.utf8String + stringResponse.append(chunk) + } + val processingResult: Future[Done] = dataBytes.runWith(sink, materializer) + + AkkaCoreUtils.postProcessHttpRequest(isLockAquired, stringResponse, httpResponse.entity.contentType.toString(), this.getClass.getName, "apply", NewRelic.getAgent.getTransaction.getToken()) + } catch { + case t: NewRelicSecurityException => + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_CORE_10_0_11, t.getMessage), t, classOf[AkkaCoreUtils].getName) + throw t + case _: Throwable => + } + + httpResponse + } +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/scala/Serializable_Instrumentation.scala b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/scala/Serializable_Instrumentation.scala new file mode 100644 index 000000000..5cf650019 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/main/scala/scala/Serializable_Instrumentation.scala @@ -0,0 +1,8 @@ +package scala + +import com.newrelic.api.agent.weaver.SkipIfPresent + +@SkipIfPresent(originalName = "scala.Serializable") +class Serializable_Instrumentation { + +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/AkkaHttpCoreTest.scala b/instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/AkkaHttpCoreTest.scala new file mode 100644 index 000000000..3fb9cd304 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/AkkaHttpCoreTest.scala @@ -0,0 +1,247 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.security.instrumentation.akka.http.core_10 + +import akka.actor.ActorSystem +import akka.http.scaladsl.{AkkaCoreUtils, Http} +import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpHeader, HttpRequest} +import akka.stream.ActorMaterializer +import akka.util.ByteString +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.Trace +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} +import com.newrelic.api.agent.security.schema.operation.{RXSSOperation, SSRFOperation} +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.{Assert, FixMethodOrder, Test} + +import java.net.ServerSocket +import java.util.UUID +import scala.concurrent.duration.DurationInt +import scala.concurrent.Await +import scala.jdk.CollectionConverters.CollectionHasAsScala + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@InstrumentationTestConfig(includePrefixes = Array("akka", "scala")) +class AkkaHttpCoreTest { + + implicit val system: ActorSystem = ActorSystem() + implicit val materializer: ActorMaterializer = ActorMaterializer() + + val akkaServer = new AkkaServer() + val playServer = new PlayServer() + var port: Int = getRandomPort + val baseUrl: String = "http://localhost:" + port + val asyncUrl: String = "/asyncPing" + val syncUrl: String = "/ping" + val contentType: String = "text/plain" + val responseBody: String = "Hoops!" + val requestBody: String = "Hurray!" + + @Test + def syncHandlerAkkaServerTestWithAkkaServer(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + introspector.setK2FuzzRequestId(headerValue) + introspector.setK2TracingData(headerValue) + introspector.setK2ParentId(headerValue) + + val headers: Seq[HttpHeader] = makeHttpRequest(async = false, withPlay = false) + + // assertions + Assert.assertTrue("No operations detected", introspector.getOperations.size() > 0) + assertCSECHeaders(headers, headerValue) + val operations = introspector.getOperations + for (op <- operations.asScala){ + op match { + case operation: SSRFOperation => assertSSRFOperation(operation, syncUrl) + case operation: RXSSOperation => assertRXSSOperation(operation) + case _ => + } + } + assertMetaData(introspector.getSecurityMetaData) + } + + @Test + def asyncHandlerAkkaServerTestWithAkkaServer(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + introspector.setK2FuzzRequestId(headerValue) + introspector.setK2TracingData(headerValue) + introspector.setK2ParentId(headerValue) + + val headers: Seq[HttpHeader] = makeHttpRequest(async = true, withPlay = false) + + // assertions + Assert.assertTrue("No operations detected", introspector.getOperations.size() > 0) + assertCSECHeaders(headers, headerValue) + val operations = introspector.getOperations + for (op <- operations.asScala){ + op match { + case operation: SSRFOperation => assertSSRFOperation(operation, asyncUrl) + case operation: RXSSOperation => assertRXSSOperation(operation) + case _ => + } + } + assertMetaData(introspector.getSecurityMetaData) + } + + @Test + def syncHandlerAkkaServerTestWithPlayServer(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + introspector.setK2FuzzRequestId(headerValue) + introspector.setK2TracingData(headerValue) + introspector.setK2ParentId(headerValue) + + val headers: Seq[HttpHeader] = makeHttpRequest(async = false, withPlay = true) + + // assertions + Assert.assertTrue("No operations detected", introspector.getOperations.size() > 0) + assertCSECHeaders(headers, headerValue) + val operations = introspector.getOperations + for (op <- operations.asScala){ + op match { + case operation: SSRFOperation => assertSSRFOperation(operation, syncUrl) + case operation: RXSSOperation => assertRXSSOperation(operation) + case _ => + } + } + assertMetaData(introspector.getSecurityMetaData) + } + + @Test + def asyncHandlerAkkaServerTestWithPlayServer(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + introspector.setK2FuzzRequestId(headerValue) + introspector.setK2TracingData(headerValue) + introspector.setK2ParentId(headerValue) + + val headers: Seq[HttpHeader] = makeHttpRequest(async = true, withPlay = true) + + // assertions + Assert.assertTrue("No operations detected", introspector.getOperations.size() > 0) + assertCSECHeaders(headers, headerValue) + val operations = introspector.getOperations + for (op <- operations.asScala){ + op match { + case operation: SSRFOperation => assertSSRFOperation(operation, asyncUrl) + case operation: RXSSOperation => assertRXSSOperation(operation) + case _ => + } + } + assertMetaData(introspector.getSecurityMetaData) + } + + @Trace(dispatcher = true, nameTransaction = true) + private def makeHttpRequest(async: Boolean, withPlay: Boolean): Seq[HttpHeader] = { + if (withPlay) { + // start play-akka server & make request + playServer.start(port, async) + + println("result of request: "+ Await.result( + Http().singleRequest( + HttpRequest(uri = baseUrl + (if (async) asyncUrl else syncUrl), + entity = HttpEntity.Strict.apply(ContentTypes.`text/plain(UTF-8)`, ByteString.fromString(requestBody)))), + new DurationInt(15).seconds) + ) + + playServer.stop() + playServer.getHeaders + } else { + // start akka server & make request + akkaServer.start(port, async) + + println("result of request: "+ Await.result( + Http().singleRequest( + HttpRequest(uri = baseUrl + (if (async) asyncUrl else syncUrl), + entity = HttpEntity.Strict.apply(ContentTypes.`text/plain(UTF-8)`, ByteString.fromString(requestBody)))), + new DurationInt(15).seconds) + ) + + akkaServer.stop() + akkaServer.getHeaders + } + } + + def getRandomPort: Int = { + var port: Int = 0 + try { + val socket: ServerSocket = new ServerSocket(0) + port = socket.getLocalPort + socket.close() + } catch { + case _: Exception => throw new RuntimeException("Unable to allocate ephemeral port") + } + port + } + + private def assertSSRFOperation(operation: SSRFOperation, url: String): Unit = { + Assert.assertFalse("operation should not be empty", operation.isEmpty) + Assert.assertFalse("JNDILookup should be false", operation.isJNDILookup) + Assert.assertFalse("LowSeverityHook should be disabled", operation.isLowSeverityHook) + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.HTTP_REQUEST, operation.getCaseType) + Assert.assertEquals("Invalid executed method name.", AkkaCoreUtils.METHOD_SINGLE_REQUEST_IMPL, operation.getMethodName) + Assert.assertEquals("Invalid executed parameters.", baseUrl + url, operation.getArg) + } + private def assertCSECHeaders(headers: Seq[HttpHeader], headerVal: String): Unit = { + Assert.assertTrue( + String.format("%s CSEC header should be present", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), + headers.exists(header => header.name().contains(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + ) + Assert.assertTrue( + String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), + headers.exists(header => header.value().contains(headerVal)) + ) + + Assert.assertTrue( + String.format("%s CSEC header should be present", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), + headers.exists(header => header.name().contains(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER)) + ) + Assert.assertTrue( + String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), + headers.exists(header => header.value().contains(String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerVal))) + ) + + Assert.assertTrue( + String.format("%s CSEC header should be present", GenericHelper.CSEC_PARENT_ID), + headers.exists(header => header.name().contains(GenericHelper.CSEC_PARENT_ID)) + ) + Assert.assertTrue( + String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), + headers.exists(header => header.value().contains(headerVal)) + ) + } + private def assertRXSSOperation(operation: RXSSOperation): Unit = { + Assert.assertFalse("operation should not be empty", operation.isEmpty) + Assert.assertFalse("LowSeverityHook should be disabled", operation.isLowSeverityHook) + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType) + Assert.assertEquals("Invalid executed method name.", "apply", operation.getMethodName) + + Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) + Assert.assertEquals("Invalid response content-type.", operation.getRequest.getContentType, contentType) + Assert.assertEquals("Invalid responseBody.", operation.getRequest.getBody.toString, requestBody) + Assert.assertEquals("Invalid protocol.", operation.getRequest.getProtocol, "http") + + Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", operation.getResponse.getResponseContentType, contentType) + Assert.assertEquals("Invalid responseBody.", operation.getResponse.getResponseBody.toString, responseBody) + } + private def assertMetaData(metaData: SecurityMetaData): Unit = { + Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", metaData.getRequest.getContentType, contentType) + Assert.assertEquals("Invalid responseBody.", metaData.getRequest.getBody.toString, requestBody) + Assert.assertFalse("response should not be empty", metaData.getRequest.isEmpty) + Assert.assertEquals("Invalid response content-type.", metaData.getResponse.getResponseContentType, contentType) + Assert.assertEquals("Invalid responseBody.", metaData.getResponse.getResponseBody.toString, responseBody) + Assert.assertEquals("Invalid protocol.", metaData.getRequest.getProtocol, "http") + } +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/AkkaServer.scala b/instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/AkkaServer.scala new file mode 100644 index 000000000..394047612 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/AkkaServer.scala @@ -0,0 +1,82 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.security.instrumentation.akka.http.core_10 + +import akka.actor.ActorSystem +import akka.http.scaladsl.Http +import akka.http.scaladsl.model.HttpMethods._ +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import akka.stream.scaladsl.{Source, _} +import akka.util.Timeout + +import scala.concurrent.duration._ +import scala.concurrent.{Await, ExecutionContextExecutor, Future} +import scala.language.postfixOps + +//how the akka http core docs' example sets up a server +class AkkaServer() { + implicit val system: ActorSystem = ActorSystem() + implicit val executor: ExecutionContextExecutor = system.dispatcher + implicit val materializer: ActorMaterializer = ActorMaterializer() + implicit val timeout: Timeout = 3 seconds + + var serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = _ + var bindingFuture: Future[Http.ServerBinding] = _ + var headers: Seq[HttpHeader] = Seq() + + def start(port: Int, async: Boolean): Unit = { + + serverSource = Http().bind(interface = "localhost", port) + + if (async) { + + val asyncRequestHandler: HttpRequest => Future[HttpResponse] = { + case HttpRequest(GET, Uri.Path("/asyncPing"), var1, _, _) => { + headers = var1 + Future[HttpResponse](HttpResponse(entity = "Hoops!")) + } + } + + bindingFuture = serverSource.to(Sink.foreach { + connection => + println("accepted connection from: " + connection.remoteAddress) + connection handleWithAsyncHandler asyncRequestHandler + }).run() + } + else { + + val requestHandler: HttpRequest => HttpResponse = { + case HttpRequest(GET, Uri.Path("/ping"), var1, _, _) => { + headers = var1 + HttpResponse(entity = "Hoops!") + } + } + + bindingFuture = serverSource.to(Sink.foreach { + connection => + println("accepted connection from: " + connection.remoteAddress) + connection handleWithSyncHandler requestHandler + }).run() + } + + Await.ready({ + bindingFuture + }, timeout.duration) + } + + def stop(): Unit = { + if (bindingFuture != null) { + bindingFuture.flatMap(_.unbind()).onComplete(_ => system.terminate()) + } + } + + def getHeaders: Seq[HttpHeader] = { + headers + } +} diff --git a/instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/PlayServer.scala b/instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/PlayServer.scala new file mode 100644 index 000000000..ec70bc3d1 --- /dev/null +++ b/instrumentation-security/akka-http-core-2.13_10.1.8/src/test/scala/com/nr/agent/security/instrumentation/akka/http/core_10/PlayServer.scala @@ -0,0 +1,73 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.security.instrumentation.akka.http.core_10 + +import akka.actor.ActorSystem +import akka.http.scaladsl.Http +import akka.http.scaladsl.model.HttpMethods._ +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import akka.util.Timeout + +import scala.concurrent.duration._ +import scala.concurrent.{Await, ExecutionContextExecutor, Future} +import scala.language.postfixOps + +//how play 2.6 sets up a server +class PlayServer { + implicit val system: ActorSystem = ActorSystem() + implicit val executor: ExecutionContextExecutor = system.dispatcher + implicit val materializer: ActorMaterializer = ActorMaterializer() + implicit val timeout: Timeout = 3 seconds + + var bindingFuture: Future[Http.ServerBinding] = _ + var headers: Seq[HttpHeader] = Seq() + + def start(port: Int, async: Boolean): Unit = { + + if (async) { + + val asyncRequestHandler: HttpRequest => Future[HttpResponse] = { + case HttpRequest(GET, Uri.Path("/asyncPing"), var1, _, _) => { + headers = var1 + Future[HttpResponse](HttpResponse(entity = "Hoops!")) + } + } + + bindingFuture = Http().bindAndHandleAsync(asyncRequestHandler, interface = "localhost", port) + + } + else { + + val requestHandler: HttpRequest => HttpResponse = { + case HttpRequest(GET, Uri.Path("/ping"), var1, _, _) => { + headers = var1 + HttpResponse(entity = "Hoops!") + } + } + + bindingFuture = Http().bindAndHandleSync(requestHandler, interface = "localhost", port) + } + + Await.ready({ + bindingFuture + }, timeout.duration) + } + + def stop(): Unit = { + if (bindingFuture != null) { + bindingFuture.flatMap(_.unbind()).onComplete(_ => { + system.terminate() + }) + } + } + + def getHeaders: Seq[HttpHeader] = { + headers + } +} diff --git a/settings.gradle b/settings.gradle index 8c3bd415a..8e01bac47 100644 --- a/settings.gradle +++ b/settings.gradle @@ -114,6 +114,7 @@ include 'instrumentation:saxpath' include 'instrumentation:javax-xpath' include 'instrumentation:akka-http-core-2.11_10.0.11' include 'instrumentation:akka-http-core-2.13_10.2.0' +include 'instrumentation:akka-http-core-2.13_10.1.8' include 'instrumentation:akka-http-core-10.0' include 'instrumentation:akka-http-2.11_10.0.0' include 'instrumentation:jetty-9'