diff --git a/instrumentation-security/spray-can-1.3.1/build.gradle b/instrumentation-security/spray-can-1.3.1/build.gradle new file mode 100644 index 000000000..777a3cbb1 --- /dev/null +++ b/instrumentation-security/spray-can-1.3.1/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.10") + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.spray-can-1.3.1' } +} + +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("org.scala-lang:scala-library:2.10.7") + implementation("io.spray:spray-can_2.10:1.3.3") + implementation("com.typesafe.akka:akka-actor_2.10:2.3.14") +} + +verifyInstrumentation { + passesOnly('io.spray:spray-can_2.11:[1.3.1,)'){ + implementation("com.typesafe.akka:akka-actor_2.11:2.3.14") + } + passesOnly('io.spray:spray-can_2.10:[1.3.1,)'){ + implementation("com.typesafe.akka:akka-actor_2.10:2.3.14") + } +} + +site { + title 'Spray-can' + type 'Messaging' +} \ No newline at end of file diff --git a/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/Http_Instrumentation.java b/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/Http_Instrumentation.java new file mode 100644 index 000000000..8bde104f4 --- /dev/null +++ b/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/Http_Instrumentation.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package spray.can; + +import java.net.InetSocketAddress; + +import akka.actor.ActorRef; +import akka.io.Inet; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.weaver.Weave; +import scala.Option; +import scala.collection.immutable.Traversable; +import spray.can.server.ServerSettings; +import spray.io.ServerSSLEngineProvider; + +@Weave(originalName = "spray.can.Http") +public class Http_Instrumentation { + + @Weave(originalName = "spray.can.Http$Bind") + public static class Bind { + + public Bind(final ActorRef listener, final InetSocketAddress endpoint, final int backlog, + final Traversable options, final Option settings, + final ServerSSLEngineProvider sslEngineProvider) { + NewRelicSecurity.getAgent().setApplicationConnectionConfig(endpoint.getPort(), "http"); + } + + } + +} diff --git a/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/SprayHttpUtils.java b/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/SprayHttpUtils.java new file mode 100644 index 000000000..bd9c9f361 --- /dev/null +++ b/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/SprayHttpUtils.java @@ -0,0 +1,150 @@ +package spray.can; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +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 scala.collection.Iterator; +import scala.collection.immutable.List; +import spray.http.*; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; + +public class SprayHttpUtils { + + public static final String QUESTION_MARK = "?"; + + private static final String X_FORWARDED_FOR = "x-forwarded-for"; + public static final String SPRAY_CAN_1_3_1 = "SPRAY-CAN-1.3.1"; + + public static String getNrSecCustomAttribName() { + return "SPRAY-CAN-" + Thread.currentThread().getId(); + } + public static String getNrSecCustomAttribNameForResponse() { + return "SPRAY-CAN-RXSS" + Thread.currentThread().getId(); + } + + public static void preProcessRequestHook(HttpRequest request) { + try { + 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(request.method().name()); + securityRequest.setProtocol(request.uri().scheme()); + securityRequest.setUrl(processURL(request.uri())); + securityRequest.setServerPort(request.uri().effectivePort()); + processHttpRequestHeader(request.headers(), securityRequest); + + securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders())); + + if (!request.entity().isEmpty()) { + if (request.entity() instanceof HttpEntity.NonEmpty) { + securityRequest.setContentType(((HttpEntity.NonEmpty) request.entity()).contentType().value()); + } + securityRequest.setBody(new StringBuilder(request.entity().data().asString(StandardCharsets.UTF_8))); + } + + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length)); + securityRequest.setRequestParsed(true); + } catch (Exception e){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, SPRAY_CAN_1_3_1, e.getMessage()), e, SprayHttpUtils.class.getName()); + } + } + + public static String getTraceHeader(Map headers) { + String data = StringUtils.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; + } + + private static void processHttpRequestHeader(List headers, com.newrelic.api.agent.security.schema.HttpRequest securityRequest) { + Iterator headerIterator = headers.iterator(); + while (headerIterator.hasNext()){ + HttpHeader element = headerIterator.next(); + String headerKey = element.lowercaseName(); + String headerValue = element.value(); + boolean takeNextValue = false; + 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(headerValue)); + } else if(GenericHelper.CSEC_PARENT_ID.equals(headerKey)) { + NewRelicSecurity.getAgent().getSecurityMetaData() + .addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headerValue); + } + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true); + securityRequest.setClientIP(headerValue); + agentMetaData.getIps() + .add(securityRequest.getClientIP()); + } + securityRequest.getHeaders().put(headerKey, headerValue); + } + } + + private static String processURL(Uri uri) { + String path = uri.path().toString(); + String queryString = StringUtils.substringAfter(uri.toString(), QUESTION_MARK); + if(StringUtils.isBlank(queryString)){ + return path; + } else { + return path + QUESTION_MARK + queryString; + } + } + + public static void postProcessSecurityHook(HttpResponse httpResponse, String className, String methodName) { + try { + if (!NewRelicSecurity.isHookProcessingActive() + ) { + return; + } + //Add request URI hash to low severity event filter + 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, SPRAY_CAN_1_3_1, e.getMessage()), e, SprayHttpUtils.class.getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, SPRAY_CAN_1_3_1, e.getMessage()), e, SprayHttpUtils.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, SPRAY_CAN_1_3_1, e.getMessage()), e, SprayHttpUtils.class.getName()); + } + } +} diff --git a/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/rendering/ResponseRendering_Instrumentation.java b/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/rendering/ResponseRendering_Instrumentation.java new file mode 100644 index 000000000..ccfccafa1 --- /dev/null +++ b/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/rendering/ResponseRendering_Instrumentation.java @@ -0,0 +1,44 @@ +package spray.can.rendering; + +import akka.event.LoggingAdapter; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.utils.logging.LogLevel; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import spray.can.SprayHttpUtils; +import spray.http.HttpEntity; +import spray.http.HttpResponse; +import spray.http.Rendering; + +import java.nio.charset.StandardCharsets; + +@Weave(originalName = "spray.can.rendering.ResponseRenderingComponent$class") +public class ResponseRendering_Instrumentation { + private static boolean renderResponse$1(ResponseRenderingComponent component, HttpResponse response, + Rendering rendering, ResponsePartRenderingContext context, LoggingAdapter adapter) { + + boolean isLockAcquired = GenericHelper.acquireLockIfPossible(SprayHttpUtils.getNrSecCustomAttribNameForResponse()); + try { + if (isLockAcquired && response.entity().nonEmpty()) { + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseBody(new StringBuilder(response.entity().data().asString(StandardCharsets.UTF_8))); + if (response.entity() instanceof HttpEntity.NonEmpty) { + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseContentType(((HttpEntity.NonEmpty) response.entity()).contentType().value()); + } + SprayHttpUtils.postProcessSecurityHook(response, ResponseRendering_Instrumentation.class.getName(), "renderResponse$1"); + } + } catch (Exception e){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_PARSING_HTTP_RESPONSE, SprayHttpUtils.SPRAY_CAN_1_3_1, e.getMessage()), e, ResponseRendering_Instrumentation.class.getName()); + } + boolean result; + try { + result = Weaver.callOriginal(); + } finally { + if(isLockAcquired){ + GenericHelper.releaseLock(SprayHttpUtils.getNrSecCustomAttribNameForResponse()); + } + } + return result; + } + +} diff --git a/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/server/ServerFrontend_Instrumentation.java b/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/server/ServerFrontend_Instrumentation.java new file mode 100644 index 000000000..6dd9a3fd5 --- /dev/null +++ b/instrumentation-security/spray-can-1.3.1/src/main/scala/spray/can/server/ServerFrontend_Instrumentation.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright 2020 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package spray.can.server; + +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import spray.can.SprayHttpUtils; +import spray.http.HttpRequest; + +@Weave(originalName = "spray.can.server.ServerFrontend$$anon$2$$anon$1") +public class ServerFrontend_Instrumentation { + + public void spray$can$server$ServerFrontend$$anon$$anon$$openNewRequest(final HttpRequest request, + final boolean closeAfterResponseCompletion, final RequestState state) { + boolean isLockAcquired = GenericHelper.acquireLockIfPossible(SprayHttpUtils.getNrSecCustomAttribName()); + if (isLockAcquired) { + SprayHttpUtils.preProcessRequestHook(request); + } + try { + Weaver.callOriginal(); + } finally { + if(isLockAcquired){ + GenericHelper.releaseLock(SprayHttpUtils.getNrSecCustomAttribName()); + } + } + } +} diff --git a/instrumentation-security/spray-http-1.3.1/src/main/scala/spray/routing/SprayRoutingHttpServer.java b/instrumentation-security/spray-http-1.3.1/src/main/scala/spray/routing/SprayRoutingHttpServer.java index 7fa994c8d..fe4c0f476 100644 --- a/instrumentation-security/spray-http-1.3.1/src/main/scala/spray/routing/SprayRoutingHttpServer.java +++ b/instrumentation-security/spray-http-1.3.1/src/main/scala/spray/routing/SprayRoutingHttpServer.java @@ -19,7 +19,6 @@ @Weave(type = MatchType.ExactClass, originalName = "spray.routing.HttpServiceBase$class") public class SprayRoutingHttpServer { - @Trace(dispatcher = true) public static final void runSealedRoute$1(final HttpServiceBase $this, final RequestContext ctx, final PartialFunction sealedExceptionHandler$1, final Function1 sealedRoute$1) { boolean isLockAcquired = GenericHelper.acquireLockIfPossible(SprayHttpUtils.getNrSecCustomAttribName()); if (isLockAcquired) { diff --git a/settings.gradle b/settings.gradle index 60884b5fa..42adec606 100644 --- a/settings.gradle +++ b/settings.gradle @@ -180,3 +180,4 @@ include 'instrumentation:jetty-12' include 'instrumentation:mule-3.7' include 'instrumentation:mule-3.6' include 'instrumentation:spray-http-1.3.1' +include 'instrumentation:spray-can-1.3.1' \ No newline at end of file