From 3a24ea8ea58809f9f350d7852145e008c5e8840d Mon Sep 17 00:00:00 2001 From: idawda Date: Fri, 18 Oct 2024 16:54:34 +0530 Subject: [PATCH 01/23] NR-325523: Incoming request interception support in Http4s Blaze Server --- .../build.gradle | 25 ++++ .../BlazeServerBuilder_Instrumentation.java | 20 +++ .../http4s/blaze/server/BlazeUtils.java | 67 ++++++++++ .../blaze/server/RequestProcessor.scala | 121 +++++++++++++++++ .../build.gradle | 26 ++++ .../BlazeServerBuilder_Instrumentation.java | 20 +++ .../http4s/blaze/server/BlazeUtils.java | 67 ++++++++++ .../blaze/server/RequestProcessor.scala | 122 ++++++++++++++++++ .../build.gradle | 25 ++++ .../BlazeServerBuilder_Instrumentation.java | 20 +++ .../http4s/blaze/server/BlazeUtils.java | 67 ++++++++++ .../blaze/server/RequestProcessor.scala | 122 ++++++++++++++++++ .../build.gradle | 25 ++++ .../BlazeServerBuilder_Instrumentation.java | 20 +++ .../http4s/blaze/server/BlazeUtils.java | 67 ++++++++++ .../blaze/server/RequestProcessor.scala | 121 +++++++++++++++++ .../build.gradle | 26 ++++ .../BlazeServerBuilder_Instrumentation.java | 20 +++ .../http4s/blaze/server/BlazeUtils.java | 67 ++++++++++ .../blaze/server/RequestProcessor.scala | 122 ++++++++++++++++++ .../build.gradle | 25 ++++ .../BlazeServerBuilder_Instrumentation.java | 20 +++ .../http4s/blaze/server/BlazeUtils.java | 67 ++++++++++ .../blaze/server/RequestProcessor.scala | 122 ++++++++++++++++++ settings.gradle | 8 +- 25 files changed, 1411 insertions(+), 1 deletion(-) create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.21/build.gradle create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/java/org/http4s/server/blaze/BlazeServerBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.22/build.gradle create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.23/build.gradle create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.21/build.gradle create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/java/org/http4s/server/blaze/BlazeServerBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.22/build.gradle create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.23/build.gradle create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/build.gradle b/instrumentation-security/http4s-blaze-server-2.12_0.21/build.gradle new file mode 100644 index 000000000..b5328c440 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.12") + +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.12.14") + implementation('org.http4s:http4s-blaze-server_2.12:0.21.24') +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-server-2.12_0.21', 'Priority': '-1' + } +} + +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-server_2.12:[0.21.0,0.22.0)' + excludeRegex '.*(RC|M)[0-9]*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/java/org/http4s/server/blaze/BlazeServerBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/java/org/http4s/server/blaze/BlazeServerBuilder_Instrumentation.java new file mode 100644 index 000000000..e6fc9daa9 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/java/org/http4s/server/blaze/BlazeServerBuilder_Instrumentation.java @@ -0,0 +1,20 @@ +package org.http4s.server.blaze; + +import cats.data.Kleisli; +import cats.effect.ConcurrentEffect; +import com.newrelic.agent.security.http4s.blaze.server.RequestProcessor$; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.Request; +import org.http4s.Response; + +@Weave(originalName = "org.http4s.server.blaze.BlazeServerBuilder") +public class BlazeServerBuilder_Instrumentation { + + private final ConcurrentEffect F = Weaver.callOriginal(); + + public BlazeServerBuilder withHttpApp(Kleisli, Response> httpApp) { + httpApp = RequestProcessor$.MODULE$.genHttpApp(httpApp, this.F); + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java new file mode 100644 index 000000000..262a24583 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -0,0 +1,67 @@ +package com.newrelic.agent.security.http4s.blaze.server; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.StringUtils; + +import java.util.Map; + +public class BlazeUtils { + + public static String getContentType(Map headers) { + String contentType = StringUtils.EMPTY; + if (headers.containsKey("content-type")){ + contentType = headers.get("content-type"); + } + return contentType; + } + + 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; + } + + public static String getProtocol(boolean isSecure) { + if (isSecure) { + return "https"; + } + return "http"; + } + + + private static boolean isLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) {} + return false; + } + + public static boolean acquireLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored){} + return false; + } + + public static void releaseLock() { + try { + if(NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored){} + } + + private static String getNrSecCustomAttribName() { + return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala new file mode 100644 index 000000000..1ebf1d799 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -0,0 +1,121 @@ +package com.newrelic.agent.security.http4s.blaze.server + +import cats.data.Kleisli +import cats.effect.Sync +import cats.implicits._ +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ICsecApiConstants, ServletHelper} +import com.newrelic.api.agent.security.schema._ +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 org.http4s.{Headers, Request, Response} + +import java.util + + +object RequestProcessor { + + private val METHOD_WITH_HTTP_APP = "withHttpApp" + private val HTTP_4S_EMBER_SERVER_2_12_0_23 = "HTTP4S-BLAZE-SERVER-2.12_0.21" + private val X_FORWARDED_FOR = "x-forwarded-for" + + def genHttpApp[F[_] : Sync](httpApp: Kleisli[F, Request[F], Response[F]]): Kleisli[F, Request[F], Response[F]] = { + Kleisli { req: Request[F] => nrRequestResponse(req, httpApp) } + } + + private def nrRequestResponse[F[_] : Sync](request: Request[F], httpApp: Kleisli[F, Request[F], Response[F]]): F[Response[F]] = { + val result = construct((): Unit) + .redeemWith(_ => httpApp(request), + _ => for { + _ <- preprocessHttpRequest(request) + resp <- httpApp(request) + } yield resp + ) + result + } + + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { + val isLockAcquired = BlazeUtils.acquireLockIfPossible() + try { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ + + val securityMetaData: SecurityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val securityRequest: HttpRequest = securityMetaData.getRequest + val securityAgentMetaData: AgentMetaData = securityMetaData.getMetaData + + securityRequest.setMethod(request.method.name) + securityRequest.setServerPort(request.serverPort.toInt) + securityRequest.setClientIP(request.remoteAddr.get) + securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + securityRequest.setUrl(request.uri.toString) + + if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { + securityAgentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(String.valueOf(request.remotePort.get)) + } + + processRequestHeaders(request.headers, securityRequest) + securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + + // TODO extract request body & user class detection + + val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityRequest.setRequestParsed(true) + } + + } catch { + case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } finally { + if (isLockAcquired) { + BlazeUtils.releaseLock() + } + } + } + + private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { + headers.foreach(header => { + var takeNextValue = false + var headerKey: String = StringUtils.EMPTY + if (header.name != null && header.name.isEmpty) { + headerKey = header.name.toString + } + val headerValue: String = header.value + + val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val 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 == headerKey) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent.getSecurityMetaData.setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(headerValue)) + } + if (GenericHelper.CSEC_PARENT_ID == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headerValue) + } + else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true) + } + + if (headerValue != null && headerValue.trim.nonEmpty) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true) + securityRequest.setClientIP(headerValue) + agentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(StringUtils.EMPTY) + takeNextValue = false + } + } + securityRequest.getHeaders.put(headerKey.toLowerCase, headerValue) + }) + } + + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/build.gradle b/instrumentation-security/http4s-blaze-server-2.12_0.22/build.gradle new file mode 100644 index 000000000..e33254e41 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.12") + +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.12.14") + implementation('org.http4s:http4s-blaze-server_2.12:0.22.14') +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-server-2.12_0.22', 'Priority': '-1' + } +} + +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-server_2.12:[0.22.0,0.23.0)' + excludeRegex '.*(RC|M)[0-9]*' + excludeRegex '.*0.22\\-[0-9].*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java new file mode 100644 index 000000000..aa47a334a --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java @@ -0,0 +1,20 @@ +package org.http4s.blaze.server; + +import cats.data.Kleisli; +import cats.effect.ConcurrentEffect; +import com.newrelic.agent.security.http4s.blaze.server.RequestProcessor$; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.Request; +import org.http4s.Response; + +@Weave(originalName = "org.http4s.blaze.server.BlazeServerBuilder") +public class BlazeServerBuilder_Instrumentation { + + private final ConcurrentEffect F = Weaver.callOriginal(); + + public BlazeServerBuilder withHttpApp(Kleisli, Response> httpApp) { + httpApp = RequestProcessor$.MODULE$.genHttpApp(httpApp, this.F); + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java new file mode 100644 index 000000000..262a24583 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -0,0 +1,67 @@ +package com.newrelic.agent.security.http4s.blaze.server; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.StringUtils; + +import java.util.Map; + +public class BlazeUtils { + + public static String getContentType(Map headers) { + String contentType = StringUtils.EMPTY; + if (headers.containsKey("content-type")){ + contentType = headers.get("content-type"); + } + return contentType; + } + + 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; + } + + public static String getProtocol(boolean isSecure) { + if (isSecure) { + return "https"; + } + return "http"; + } + + + private static boolean isLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) {} + return false; + } + + public static boolean acquireLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored){} + return false; + } + + public static void releaseLock() { + try { + if(NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored){} + } + + private static String getNrSecCustomAttribName() { + return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala new file mode 100644 index 000000000..b8bcc8f23 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -0,0 +1,122 @@ +package com.newrelic.agent.security.http4s.blaze.server + +import cats.data.Kleisli +import cats.effect.Sync +import cats.implicits._ +import com.comcast.ip4s.Port +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ICsecApiConstants, ServletHelper} +import com.newrelic.api.agent.security.schema._ +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 org.http4s.{Headers, Request, Response} + +import java.util + + +object RequestProcessor { + + private val METHOD_WITH_HTTP_APP = "withHttpApp" + private val HTTP_4S_EMBER_SERVER_2_12_0_23 = "HTTP4S-BLAZE-SERVER-2.12_0.22" + private val X_FORWARDED_FOR = "x-forwarded-for" + + def genHttpApp[F[_] : Sync](httpApp: Kleisli[F, Request[F], Response[F]]): Kleisli[F, Request[F], Response[F]] = { + Kleisli { req: Request[F] => nrRequestResponse(req, httpApp) } + } + + private def nrRequestResponse[F[_] : Sync](request: Request[F], httpApp: Kleisli[F, Request[F], Response[F]]): F[Response[F]] = { + val result = construct((): Unit) + .redeemWith(_ => httpApp(request), + _ => for { + _ <- preprocessHttpRequest(request) + resp <- httpApp(request) + } yield resp + ) + result + } + + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { + val isLockAcquired = BlazeUtils.acquireLockIfPossible() + try { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ + + val securityMetaData: SecurityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val securityRequest: HttpRequest = securityMetaData.getRequest + val securityAgentMetaData: AgentMetaData = securityMetaData.getMetaData + + securityRequest.setMethod(request.method.name) + securityRequest.setServerPort((request.serverPort).get.asInstanceOf[Port].value) + securityRequest.setClientIP(request.remoteAddr.get.toString) + securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + securityRequest.setUrl(request.uri.toString) + + if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { + securityAgentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(String.valueOf(request.remotePort.get)) + } + + processRequestHeaders(request.headers, securityRequest) + securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + + // TODO extract request body & user class detection + + val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityRequest.setRequestParsed(true) + } + + } catch { + case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } finally { + if (isLockAcquired) { + BlazeUtils.releaseLock() + } + } + } + + private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { + headers.foreach(header => { + var takeNextValue = false + var headerKey: String = StringUtils.EMPTY + if (header.name != null && header.name.nonEmpty) { + headerKey = header.name.toString + } + val headerValue: String = header.value + + val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val 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 == headerKey) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent.getSecurityMetaData.setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(headerValue)) + } + if (GenericHelper.CSEC_PARENT_ID == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headerValue) + } + else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true) + } + + if (headerValue != null && headerValue.trim.nonEmpty) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true) + securityRequest.setClientIP(headerValue) + agentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(StringUtils.EMPTY) + takeNextValue = false + } + } + securityRequest.getHeaders.put(headerKey.toLowerCase, headerValue) + }) + } + + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/build.gradle b/instrumentation-security/http4s-blaze-server-2.12_0.23/build.gradle new file mode 100644 index 000000000..a824a1833 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.12") + +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.12.14") + implementation('org.http4s:http4s-blaze-server_2.12:0.23.12') + implementation("org.typelevel:cats-effect_2.12:3.3.12") +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-server-2.12_0.23', 'Priority': '-1' + } +} +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-server_2.12:[0.23.0,0.24.0)' + excludeRegex '.*(RC|M)[0-9]*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java new file mode 100644 index 000000000..50298f8b6 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java @@ -0,0 +1,20 @@ +package org.http4s.blaze.server; + +import cats.data.Kleisli; +import cats.effect.kernel.Async; +import com.newrelic.agent.security.http4s.blaze.server.RequestProcessor$; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.Request; +import org.http4s.Response; + +@Weave(originalName = "org.http4s.blaze.server.BlazeServerBuilder") +public class BlazeServerBuilder_Instrumentation { + + private final Async F = Weaver.callOriginal(); + + public BlazeServerBuilder withHttpApp(Kleisli, Response> httpApp) { + httpApp = RequestProcessor$.MODULE$.genHttpApp(httpApp, this.F); + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java new file mode 100644 index 000000000..262a24583 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -0,0 +1,67 @@ +package com.newrelic.agent.security.http4s.blaze.server; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.StringUtils; + +import java.util.Map; + +public class BlazeUtils { + + public static String getContentType(Map headers) { + String contentType = StringUtils.EMPTY; + if (headers.containsKey("content-type")){ + contentType = headers.get("content-type"); + } + return contentType; + } + + 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; + } + + public static String getProtocol(boolean isSecure) { + if (isSecure) { + return "https"; + } + return "http"; + } + + + private static boolean isLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) {} + return false; + } + + public static boolean acquireLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored){} + return false; + } + + public static void releaseLock() { + try { + if(NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored){} + } + + private static String getNrSecCustomAttribName() { + return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala new file mode 100644 index 000000000..9e81c10c6 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -0,0 +1,122 @@ +package com.newrelic.agent.security.http4s.blaze.server + +import cats.data.Kleisli +import cats.effect.Sync +import cats.implicits._ +import com.comcast.ip4s.Port +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ICsecApiConstants, ServletHelper} +import com.newrelic.api.agent.security.schema._ +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 org.http4s.{Headers, Request, Response} + +import java.util + + +object RequestProcessor { + + private val METHOD_WITH_HTTP_APP = "withHttpApp" + private val HTTP_4S_EMBER_SERVER_2_12_0_23 = "HTTP4S-BLAZE-SERVER-2.12_0.23" + private val X_FORWARDED_FOR = "x-forwarded-for" + + def genHttpApp[F[_] : Sync](httpApp: Kleisli[F, Request[F], Response[F]]): Kleisli[F, Request[F], Response[F]] = { + Kleisli { req: Request[F] => nrRequestResponse(req, httpApp) } + } + + private def nrRequestResponse[F[_] : Sync](request: Request[F], httpApp: Kleisli[F, Request[F], Response[F]]): F[Response[F]] = { + val result = construct((): Unit) + .redeemWith(_ => httpApp(request), + _ => for { + _ <- preprocessHttpRequest(request) + resp <- httpApp(request) + } yield resp + ) + result + } + + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { + val isLockAcquired = BlazeUtils.acquireLockIfPossible() + try { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ + + val securityMetaData: SecurityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val securityRequest: HttpRequest = securityMetaData.getRequest + val securityAgentMetaData: AgentMetaData = securityMetaData.getMetaData + + securityRequest.setMethod(request.method.name) + securityRequest.setServerPort((request.serverPort).get.asInstanceOf[Port].value) + securityRequest.setClientIP(request.remoteAddr.get.toString) + securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + securityRequest.setUrl(request.uri.toString) + + if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { + securityAgentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(String.valueOf(request.remotePort.get)) + } + + processRequestHeaders(request.headers, securityRequest) + securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + + // TODO extract request body & user class detection + + val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityRequest.setRequestParsed(true) + } + + } catch { + case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } finally { + if (isLockAcquired) { + BlazeUtils.releaseLock() + } + } + } + + private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { + headers.foreach(header => { + var takeNextValue = false + var headerKey: String = StringUtils.EMPTY + if (header.name != null && header.name.nonEmpty) { + headerKey = header.name.toString + } + val headerValue: String = header.value + + val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val 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 == headerKey) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent.getSecurityMetaData.setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(headerValue)) + } + if (GenericHelper.CSEC_PARENT_ID == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headerValue) + } + else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true) + } + + if (headerValue != null && headerValue.trim.nonEmpty) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true) + securityRequest.setClientIP(headerValue) + agentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(StringUtils.EMPTY) + takeNextValue = false + } + } + securityRequest.getHeaders.put(headerKey.toLowerCase, headerValue) + }) + } + + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/build.gradle b/instrumentation-security/http4s-blaze-server-2.13_0.21/build.gradle new file mode 100644 index 000000000..7f46b942e --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.13") + +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.13.3") + implementation('org.http4s:http4s-blaze-server_2.13:0.21.24') +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-server-2.13_0.21', 'Priority': '-1' + } +} + +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-server_2.13:[0.21.0,0.22.0)' + excludeRegex '.*(RC|M)[0-9]*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/java/org/http4s/server/blaze/BlazeServerBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/java/org/http4s/server/blaze/BlazeServerBuilder_Instrumentation.java new file mode 100644 index 000000000..e6fc9daa9 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/java/org/http4s/server/blaze/BlazeServerBuilder_Instrumentation.java @@ -0,0 +1,20 @@ +package org.http4s.server.blaze; + +import cats.data.Kleisli; +import cats.effect.ConcurrentEffect; +import com.newrelic.agent.security.http4s.blaze.server.RequestProcessor$; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.Request; +import org.http4s.Response; + +@Weave(originalName = "org.http4s.server.blaze.BlazeServerBuilder") +public class BlazeServerBuilder_Instrumentation { + + private final ConcurrentEffect F = Weaver.callOriginal(); + + public BlazeServerBuilder withHttpApp(Kleisli, Response> httpApp) { + httpApp = RequestProcessor$.MODULE$.genHttpApp(httpApp, this.F); + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java new file mode 100644 index 000000000..262a24583 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -0,0 +1,67 @@ +package com.newrelic.agent.security.http4s.blaze.server; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.StringUtils; + +import java.util.Map; + +public class BlazeUtils { + + public static String getContentType(Map headers) { + String contentType = StringUtils.EMPTY; + if (headers.containsKey("content-type")){ + contentType = headers.get("content-type"); + } + return contentType; + } + + 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; + } + + public static String getProtocol(boolean isSecure) { + if (isSecure) { + return "https"; + } + return "http"; + } + + + private static boolean isLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) {} + return false; + } + + public static boolean acquireLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored){} + return false; + } + + public static void releaseLock() { + try { + if(NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored){} + } + + private static String getNrSecCustomAttribName() { + return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala new file mode 100644 index 000000000..64cd8dd75 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -0,0 +1,121 @@ +package com.newrelic.agent.security.http4s.blaze.server + +import cats.data.Kleisli +import cats.effect.Sync +import cats.implicits._ +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ICsecApiConstants, ServletHelper} +import com.newrelic.api.agent.security.schema._ +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 org.http4s.{Headers, Request, Response} + +import java.util + + +object RequestProcessor { + + private val METHOD_WITH_HTTP_APP = "withHttpApp" + private val HTTP_4S_EMBER_SERVER_2_12_0_23 = "HTTP4S-BLAZE-SERVER-2.13_0.21" + private val X_FORWARDED_FOR = "x-forwarded-for" + + def genHttpApp[F[_] : Sync](httpApp: Kleisli[F, Request[F], Response[F]]): Kleisli[F, Request[F], Response[F]] = { + Kleisli { req: Request[F] => nrRequestResponse(req, httpApp) } + } + + private def nrRequestResponse[F[_] : Sync](request: Request[F], httpApp: Kleisli[F, Request[F], Response[F]]): F[Response[F]] = { + val result = construct((): Unit) + .redeemWith(_ => httpApp(request), + _ => for { + _ <- preprocessHttpRequest(request) + resp <- httpApp(request) + } yield resp + ) + result + } + + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { + val isLockAcquired = BlazeUtils.acquireLockIfPossible() + try { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ + + val securityMetaData: SecurityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val securityRequest: HttpRequest = securityMetaData.getRequest + val securityAgentMetaData: AgentMetaData = securityMetaData.getMetaData + + securityRequest.setMethod(request.method.name) + securityRequest.setServerPort((request.serverPort).longValue().toInt) + securityRequest.setClientIP(request.remoteAddr.get) + securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + securityRequest.setUrl(request.uri.toString) + + if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { + securityAgentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(String.valueOf(request.remotePort.get)) + } + + processRequestHeaders(request.headers, securityRequest) + securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + + // TODO extract request body & user class detection + + val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityRequest.setRequestParsed(true) + } + + } catch { + case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } finally { + if (isLockAcquired) { + BlazeUtils.releaseLock() + } + } + } + + private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { + headers.foreach(header => { + var takeNextValue = false + var headerKey: String = StringUtils.EMPTY + if (header.name != null && header.name.isEmpty) { + headerKey = header.name.toString + } + val headerValue: String = header.value + + val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val 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 == headerKey) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent.getSecurityMetaData.setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(headerValue)) + } + if (GenericHelper.CSEC_PARENT_ID == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headerValue) + } + else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true) + } + + if (headerValue != null && headerValue.trim.nonEmpty) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true) + securityRequest.setClientIP(headerValue) + agentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(StringUtils.EMPTY) + takeNextValue = false + } + } + securityRequest.getHeaders.put(headerKey.toLowerCase, headerValue) + }) + } + + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/build.gradle b/instrumentation-security/http4s-blaze-server-2.13_0.22/build.gradle new file mode 100644 index 000000000..274eea2fe --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.13") + +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.13.3") + implementation('org.http4s:http4s-blaze-server_2.13:0.22.14') +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-server-2.13_0.22', 'Priority': '-1' + } +} + +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-server_2.13:[0.22.0,0.23.0)' + excludeRegex '.*(RC|M)[0-9]*' + excludeRegex '.*0.22\\-[0-9].*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java new file mode 100644 index 000000000..aa47a334a --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java @@ -0,0 +1,20 @@ +package org.http4s.blaze.server; + +import cats.data.Kleisli; +import cats.effect.ConcurrentEffect; +import com.newrelic.agent.security.http4s.blaze.server.RequestProcessor$; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.Request; +import org.http4s.Response; + +@Weave(originalName = "org.http4s.blaze.server.BlazeServerBuilder") +public class BlazeServerBuilder_Instrumentation { + + private final ConcurrentEffect F = Weaver.callOriginal(); + + public BlazeServerBuilder withHttpApp(Kleisli, Response> httpApp) { + httpApp = RequestProcessor$.MODULE$.genHttpApp(httpApp, this.F); + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java new file mode 100644 index 000000000..262a24583 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -0,0 +1,67 @@ +package com.newrelic.agent.security.http4s.blaze.server; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.StringUtils; + +import java.util.Map; + +public class BlazeUtils { + + public static String getContentType(Map headers) { + String contentType = StringUtils.EMPTY; + if (headers.containsKey("content-type")){ + contentType = headers.get("content-type"); + } + return contentType; + } + + 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; + } + + public static String getProtocol(boolean isSecure) { + if (isSecure) { + return "https"; + } + return "http"; + } + + + private static boolean isLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) {} + return false; + } + + public static boolean acquireLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored){} + return false; + } + + public static void releaseLock() { + try { + if(NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored){} + } + + private static String getNrSecCustomAttribName() { + return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala new file mode 100644 index 000000000..4bd62cf67 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -0,0 +1,122 @@ +package com.newrelic.agent.security.http4s.blaze.server + +import cats.data.Kleisli +import cats.effect.Sync +import cats.implicits._ +import com.comcast.ip4s.Port +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ICsecApiConstants, ServletHelper} +import com.newrelic.api.agent.security.schema._ +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 org.http4s.{Headers, Request, Response} + +import java.util + + +object RequestProcessor { + + private val METHOD_WITH_HTTP_APP = "withHttpApp" + private val HTTP_4S_EMBER_SERVER_2_12_0_23 = "HTTP4S-BLAZE-SERVER-2.13_0.22" + private val X_FORWARDED_FOR = "x-forwarded-for" + + def genHttpApp[F[_] : Sync](httpApp: Kleisli[F, Request[F], Response[F]]): Kleisli[F, Request[F], Response[F]] = { + Kleisli { req: Request[F] => nrRequestResponse(req, httpApp) } + } + + private def nrRequestResponse[F[_] : Sync](request: Request[F], httpApp: Kleisli[F, Request[F], Response[F]]): F[Response[F]] = { + val result = construct((): Unit) + .redeemWith(_ => httpApp(request), + _ => for { + _ <- preprocessHttpRequest(request) + resp <- httpApp(request) + } yield resp + ) + result + } + + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { + val isLockAcquired = BlazeUtils.acquireLockIfPossible() + try { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ + + val securityMetaData: SecurityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val securityRequest: HttpRequest = securityMetaData.getRequest + val securityAgentMetaData: AgentMetaData = securityMetaData.getMetaData + + securityRequest.setMethod(request.method.name) + securityRequest.setServerPort((request.serverPort).get.asInstanceOf[Port].value) + securityRequest.setClientIP(request.remoteAddr.get.toString) + securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + securityRequest.setUrl(request.uri.toString) + + if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { + securityAgentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(String.valueOf(request.remotePort.get)) + } + + processRequestHeaders(request.headers, securityRequest) + securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + + // TODO extract request body & user class detection + + val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityRequest.setRequestParsed(true) + } + + } catch { + case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } finally { + if (isLockAcquired) { + BlazeUtils.releaseLock() + } + } + } + + private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { + headers.foreach(header => { + var takeNextValue = false + var headerKey: String = StringUtils.EMPTY + if (header.name != null && header.name.nonEmpty) { + headerKey = header.name.toString + } + val headerValue: String = header.value + + val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val 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 == headerKey) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent.getSecurityMetaData.setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(headerValue)) + } + if (GenericHelper.CSEC_PARENT_ID == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headerValue) + } + else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true) + } + + if (headerValue != null && headerValue.trim.nonEmpty) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true) + securityRequest.setClientIP(headerValue) + agentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(StringUtils.EMPTY) + takeNextValue = false + } + } + securityRequest.getHeaders.put(headerKey.toLowerCase, headerValue) + }) + } + + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/build.gradle b/instrumentation-security/http4s-blaze-server-2.13_0.23/build.gradle new file mode 100644 index 000000000..8db9c99d9 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.13") + +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.13.3") + implementation('org.http4s:http4s-blaze-server_2.13:0.23.12') + implementation("org.typelevel:cats-effect_2.13:3.3.12") +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-server-2.13_0.23', 'Priority': '-1' + } +} +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-server_2.13:[0.23.0,0.24.0)' + excludeRegex '.*(RC|M)[0-9]*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java new file mode 100644 index 000000000..50298f8b6 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/java/org/http4s/blaze/server/BlazeServerBuilder_Instrumentation.java @@ -0,0 +1,20 @@ +package org.http4s.blaze.server; + +import cats.data.Kleisli; +import cats.effect.kernel.Async; +import com.newrelic.agent.security.http4s.blaze.server.RequestProcessor$; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.Request; +import org.http4s.Response; + +@Weave(originalName = "org.http4s.blaze.server.BlazeServerBuilder") +public class BlazeServerBuilder_Instrumentation { + + private final Async F = Weaver.callOriginal(); + + public BlazeServerBuilder withHttpApp(Kleisli, Response> httpApp) { + httpApp = RequestProcessor$.MODULE$.genHttpApp(httpApp, this.F); + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java new file mode 100644 index 000000000..262a24583 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -0,0 +1,67 @@ +package com.newrelic.agent.security.http4s.blaze.server; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.StringUtils; + +import java.util.Map; + +public class BlazeUtils { + + public static String getContentType(Map headers) { + String contentType = StringUtils.EMPTY; + if (headers.containsKey("content-type")){ + contentType = headers.get("content-type"); + } + return contentType; + } + + 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; + } + + public static String getProtocol(boolean isSecure) { + if (isSecure) { + return "https"; + } + return "http"; + } + + + private static boolean isLockAcquired() { + try { + return NewRelicSecurity.isHookProcessingActive() && + Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); + } catch (Throwable ignored) {} + return false; + } + + public static boolean acquireLockIfPossible() { + try { + if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); + return true; + } + } catch (Throwable ignored){} + return false; + } + + public static void releaseLock() { + try { + if(NewRelicSecurity.isHookProcessingActive()) { + NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); + } + } catch (Throwable ignored){} + } + + private static String getNrSecCustomAttribName() { + return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala new file mode 100644 index 000000000..6f9964e69 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -0,0 +1,122 @@ +package com.newrelic.agent.security.http4s.blaze.server + +import cats.data.Kleisli +import cats.effect.Sync +import cats.implicits._ +import com.comcast.ip4s.Port +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ICsecApiConstants, ServletHelper} +import com.newrelic.api.agent.security.schema._ +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 org.http4s.{Headers, Request, Response} + +import java.util + + +object RequestProcessor { + + private val METHOD_WITH_HTTP_APP = "withHttpApp" + private val HTTP_4S_EMBER_SERVER_2_12_0_23 = "HTTP4S-BLAZE-SERVER-2.13_0.23" + private val X_FORWARDED_FOR = "x-forwarded-for" + + def genHttpApp[F[_] : Sync](httpApp: Kleisli[F, Request[F], Response[F]]): Kleisli[F, Request[F], Response[F]] = { + Kleisli { req: Request[F] => nrRequestResponse(req, httpApp) } + } + + private def nrRequestResponse[F[_] : Sync](request: Request[F], httpApp: Kleisli[F, Request[F], Response[F]]): F[Response[F]] = { + val result = construct((): Unit) + .redeemWith(_ => httpApp(request), + _ => for { + _ <- preprocessHttpRequest(request) + resp <- httpApp(request) + } yield resp + ) + result + } + + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { + val isLockAcquired = BlazeUtils.acquireLockIfPossible() + try { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ + + val securityMetaData: SecurityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val securityRequest: HttpRequest = securityMetaData.getRequest + val securityAgentMetaData: AgentMetaData = securityMetaData.getMetaData + + securityRequest.setMethod(request.method.name) + securityRequest.setServerPort((request.serverPort).get.asInstanceOf[Port].value) + securityRequest.setClientIP(request.remoteAddr.get.toString) + securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + securityRequest.setUrl(request.uri.toString) + + if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { + securityAgentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(String.valueOf(request.remotePort.get)) + } + + processRequestHeaders(request.headers, securityRequest) + securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + + // TODO extract request body & user class detection + + val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityRequest.setRequestParsed(true) + } + + } catch { + case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } finally { + if (isLockAcquired) { + BlazeUtils.releaseLock() + } + } + } + + private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { + headers.foreach(header => { + var takeNextValue = false + var headerKey: String = StringUtils.EMPTY + if (header.name != null && header.name.nonEmpty) { + headerKey = header.name.toString + } + val headerValue: String = header.value + + val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val 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 == headerKey) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent.getSecurityMetaData.setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(headerValue)) + } + if (GenericHelper.CSEC_PARENT_ID == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, headerValue) + } + else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST == headerKey) { + NewRelicSecurity.getAgent.getSecurityMetaData.addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true) + } + + if (headerValue != null && headerValue.trim.nonEmpty) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true) + securityRequest.setClientIP(headerValue) + agentMetaData.getIps.add(securityRequest.getClientIP) + securityRequest.setClientPort(StringUtils.EMPTY) + takeNextValue = false + } + } + securityRequest.getHeaders.put(headerKey.toLowerCase, headerValue) + }) + } + + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) +} diff --git a/settings.gradle b/settings.gradle index 64dd79da8..36734416f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -217,4 +217,10 @@ include 'instrumentation:solr-7.0.0' include 'instrumentation:solr-8.0.0' include 'instrumentation:solr-9.0.0' include 'instrumentation:graphql-java-16.2' -include 'instrumentation:websphere-liberty-profile-environment-8.5.5.5' \ No newline at end of file +include 'instrumentation:websphere-liberty-profile-environment-8.5.5.5' +include 'instrumentation:http4s-blaze-server-2.12_0.21' +include 'instrumentation:http4s-blaze-server-2.12_0.22' +include 'instrumentation:http4s-blaze-server-2.12_0.23' +include 'instrumentation:http4s-blaze-server-2.13_0.21' +include 'instrumentation:http4s-blaze-server-2.13_0.22' +include 'instrumentation:http4s-blaze-server-2.13_0.23' \ No newline at end of file From d239f0f33053fe493a9af55230bdacc12bc9c56b Mon Sep 17 00:00:00 2001 From: idawda Date: Mon, 21 Oct 2024 12:55:16 +0530 Subject: [PATCH 02/23] NR-325525: Extract HTTP response in Http4s-Blaze server --- .../blaze/server/RequestProcessor.scala | 35 ++++++++++++++++++ .../blaze/server/RequestProcessor.scala | 36 +++++++++++++++++++ .../blaze/server/RequestProcessor.scala | 36 +++++++++++++++++++ .../blaze/server/RequestProcessor.scala | 36 +++++++++++++++++++ .../blaze/server/RequestProcessor.scala | 35 ++++++++++++++++++ .../blaze/server/RequestProcessor.scala | 36 +++++++++++++++++++ 6 files changed, 214 insertions(+) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 1ebf1d799..a4622dc6e 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -31,6 +31,7 @@ object RequestProcessor { _ => for { _ <- preprocessHttpRequest(request) resp <- httpApp(request) + _ <- postProcessSecurityHook(resp) } yield resp ) result @@ -117,5 +118,39 @@ object RequestProcessor { }) } + private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + try { + if (NewRelicSecurity.isHookProcessingActive) { + val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse + securityResponse.setResponseCode(response.status.code) + processResponseHeaders(response.headers, securityResponse) + securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + + // TODO extract response body + ServletHelper.executeBeforeExitingTransaction() + if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) + NewRelicSecurity.getAgent.registerOperation(rxssOperation) + } + } + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } + } + + private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { + headers.foreach(header => { + if (header.name != null && header.name.isEmpty) { + securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) + } + }) + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index b8bcc8f23..4a333e8a4 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -32,6 +32,7 @@ object RequestProcessor { _ => for { _ <- preprocessHttpRequest(request) resp <- httpApp(request) + _ <- postProcessSecurityHook(resp) } yield resp ) result @@ -118,5 +119,40 @@ object RequestProcessor { }) } + private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + try { + if (NewRelicSecurity.isHookProcessingActive) { + val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse + securityResponse.setResponseCode(response.status.code) + processResponseHeaders(response.headers, securityResponse) + securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + + // TODO extract response body + + ServletHelper.executeBeforeExitingTransaction() + if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) + NewRelicSecurity.getAgent.registerOperation(rxssOperation) + } + } + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } + } + + private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { + headers.foreach(header => { + if (header.name != null && header.name.isEmpty) { + securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) + } + }) + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 9e81c10c6..e028790b4 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -32,6 +32,7 @@ object RequestProcessor { _ => for { _ <- preprocessHttpRequest(request) resp <- httpApp(request) + _ <- postProcessSecurityHook(resp) } yield resp ) result @@ -118,5 +119,40 @@ object RequestProcessor { }) } + private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + try { + if (NewRelicSecurity.isHookProcessingActive) { + val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse + securityResponse.setResponseCode(response.status.code) + processResponseHeaders(response.headers, securityResponse) + securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + + // TODO extract response body + + ServletHelper.executeBeforeExitingTransaction() + if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) + NewRelicSecurity.getAgent.registerOperation(rxssOperation) + } + } + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } + } + + private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { + headers.foreach(header => { + if (header.name != null && header.name.isEmpty) { + securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) + } + }) + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 64cd8dd75..74b226dc5 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -31,6 +31,7 @@ object RequestProcessor { _ => for { _ <- preprocessHttpRequest(request) resp <- httpApp(request) + _ <- postProcessSecurityHook(resp) } yield resp ) result @@ -117,5 +118,40 @@ object RequestProcessor { }) } + private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + try { + if (NewRelicSecurity.isHookProcessingActive) { + val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse + securityResponse.setResponseCode(response.status.code) + processResponseHeaders(response.headers, securityResponse) + securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + + // TODO extract response body + + ServletHelper.executeBeforeExitingTransaction() + if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) + NewRelicSecurity.getAgent.registerOperation(rxssOperation) + } + } + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } + } + + private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { + headers.foreach(header => { + if (header.name != null && header.name.isEmpty) { + securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) + } + }) + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 4bd62cf67..7bf9ffcce 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -32,6 +32,7 @@ object RequestProcessor { _ => for { _ <- preprocessHttpRequest(request) resp <- httpApp(request) + _ <- postProcessSecurityHook(resp) } yield resp ) result @@ -117,6 +118,40 @@ object RequestProcessor { securityRequest.getHeaders.put(headerKey.toLowerCase, headerValue) }) } + private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + try { + if (NewRelicSecurity.isHookProcessingActive) { + val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse + securityResponse.setResponseCode(response.status.code) + processResponseHeaders(response.headers, securityResponse) + securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + + // TODO extract response body + + ServletHelper.executeBeforeExitingTransaction() + if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) + NewRelicSecurity.getAgent.registerOperation(rxssOperation) + } + } + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } + } + + private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { + headers.foreach(header => { + if (header.name != null && header.name.isEmpty) { + securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) + } + }) + } private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 6f9964e69..482f04462 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -32,6 +32,7 @@ object RequestProcessor { _ => for { _ <- preprocessHttpRequest(request) resp <- httpApp(request) + _ <- postProcessSecurityHook(resp) } yield resp ) result @@ -118,5 +119,40 @@ object RequestProcessor { }) } + private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + try { + if (NewRelicSecurity.isHookProcessingActive) { + val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse + securityResponse.setResponseCode(response.status.code) + processResponseHeaders(response.headers, securityResponse) + securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + + // TODO extract response body + + ServletHelper.executeBeforeExitingTransaction() + if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) + NewRelicSecurity.getAgent.registerOperation(rxssOperation) + } + } + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) + } + } + + private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { + headers.foreach(header => { + if (header.name != null && header.name.isEmpty) { + securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) + } + }) + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } From e9b701522e744d063aaae44e2afc3b99438f3687 Mon Sep 17 00:00:00 2001 From: idawda Date: Tue, 29 Oct 2024 15:14:28 +0530 Subject: [PATCH 03/23] NR-325526: Instrument outgoing HTTP Request in HTTP4s Blaze client --- .../build.gradle | 26 +++++ .../BlazeClientBuilder_Instrumentation.java | 22 +++++ .../NewrelicSecurityClientMiddleware.scala | 97 ++++++++++++++++++ .../build.gradle | 27 +++++ .../BlazeClientBuilder_Instrumentation.java | 22 +++++ .../NewrelicSecurityClientMiddleware.scala | 98 +++++++++++++++++++ .../build.gradle | 25 +++++ .../BlazeClientBuilder_Instrumentation.java | 22 +++++ .../NewrelicSecurityClientMiddleware.scala | 98 +++++++++++++++++++ .../build.gradle | 26 +++++ .../BlazeClientBuilder_Instrumentation.java | 22 +++++ .../NewrelicSecurityClientMiddleware.scala | 98 +++++++++++++++++++ .../build.gradle | 27 +++++ .../BlazeClientBuilder_Instrumentation.java | 22 +++++ .../NewrelicSecurityClientMiddleware.scala | 98 +++++++++++++++++++ .../build.gradle | 25 +++++ .../BlazeClientBuilder_Instrumentation.java | 22 +++++ .../NewrelicSecurityClientMiddleware.scala | 98 +++++++++++++++++++ settings.gradle | 8 +- 19 files changed, 882 insertions(+), 1 deletion(-) create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.21/build.gradle create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.22/build.gradle create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.23/build.gradle create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.21/build.gradle create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.22/build.gradle create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.23/build.gradle create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.21/build.gradle b/instrumentation-security/http4s-blaze-client-2.12_0.21/build.gradle new file mode 100644 index 000000000..cc4ee7d23 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.21/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.12") + +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.12.14") + implementation('org.http4s:http4s-blaze-client_2.12:0.21.24') + implementation("org.typelevel:cats-effect_2.12:2.5.5") +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-client-2.12_0.21', 'Priority': '-1' + } +} + +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-client_2.12:[0.21,0.22)' + excludeRegex '.*(RC|M)[0-9]*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java new file mode 100644 index 000000000..a80d809da --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java @@ -0,0 +1,22 @@ +package org.http4s; + +import cats.effect.ConcurrentEffect; +import cats.effect.Resource; +import com.newrelic.agent.security.instrumentation.http4s.blaze.NewrelicSecurityClientMiddleware$; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.client.Client; + +@Weave(type = MatchType.ExactClass, originalName = "org.http4s.client.blaze.BlazeClientBuilder") +public abstract class BlazeClientBuilder_Instrumentation { + + public ConcurrentEffect F() { + return Weaver.callOriginal(); + } + + public Resource> resource() { + Resource> delegateResource = Weaver.callOriginal(); + return NewrelicSecurityClientMiddleware$.MODULE$.resource(delegateResource, F()); + } +} diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala new file mode 100644 index 000000000..b18eab444 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -0,0 +1,97 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import cats.effect.{Async, ConcurrentEffect, Resource, Sync} +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.logging.LogLevel +import org.http4s.Request +import org.http4s.client.Client + +import java.net.URI + +object NewrelicSecurityClientMiddleware { + private final val nrSecCustomAttrName: String = "HTTP4S-BLAZE-CLIENT-OUTBOUND" + private final val HTTP4S_BLAZE_CLIENT: String = "HTTP4S-BLAZE-CLIENT-2.12_0.21" + + private def construct[F[_] : Sync, T](t: T): F[T] = Sync[F].delay(t) + + private def clientResource[F[_] : ConcurrentEffect](client: Client[F]): Client[F] = + Client { req: Request[F] => + for { + // pre-process hook + operation <- Resource.liftF(construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.HTTP_REQUEST, nrSecCustomAttrName) + var operation: AbstractOperation = null + if (isLockAcquired) { + operation = preprocessSecurityHook(req) + } + operation + }) + + // TODO add Security Headers + + // original call + response <- client.run(req) + + // post process and register exit event + newRes <- Resource.liftF(construct{ + val isLockAcquired = GenericHelper.isLockAcquired(nrSecCustomAttrName); + if (isLockAcquired) { + GenericHelper.releaseLock(nrSecCustomAttrName) + } + registerExitOperation(isLockAcquired, operation) + response + }) + + } yield newRes + } + + def resource[F[_] : ConcurrentEffect](delegate: Resource[F, Client[F]]): Resource[F, Client[F]] = { + val res: Resource[F, Client[F]] = delegate.map(c =>clientResource(c)) + res + } + + + private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { + try { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + if (!NewRelicSecurity.isHookProcessingActive || securityMetaData.getRequest.isEmpty) return null + // Generate required URL + var methodURI: URI = null + var uri: String = null + try { + methodURI = new URI(httpRequest.uri.toString) + uri = methodURI.toString + if (methodURI == null) return null + } catch { + case ignored: Exception => + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.URI_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, ignored.getMessage), ignored, this.getClass.getName) + return null + } + return new SSRFOperation(uri, this.getClass.getName, "run") + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + null + } + + private def registerExitOperation(isProcessingAllowed: Boolean, operation: AbstractOperation): Unit = { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive || NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isEmpty) return + NewRelicSecurity.getAgent.registerExitEvent(operation) + } catch { + case e: Throwable => + NewRelicSecurity.getAgent.log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.22/build.gradle b/instrumentation-security/http4s-blaze-client-2.12_0.22/build.gradle new file mode 100644 index 000000000..90a536b9c --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.22/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.12") + +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.12.14") + implementation('org.http4s:http4s-blaze-client_2.12:0.22.14') + implementation("org.typelevel:cats-effect_2.12:2.5.5") +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-client-2.12_0.22', 'Priority': '-1' + } +} + +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-client_2.12:[0.22.0,0.23.0)' + excludeRegex '.*(RC|M)[0-9]*' + excludeRegex '.*0.22\\-[0-9].*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java new file mode 100644 index 000000000..8159dc0be --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java @@ -0,0 +1,22 @@ +package org.http4s; + +import cats.effect.ConcurrentEffect; +import cats.effect.Resource; +import com.newrelic.agent.security.instrumentation.http4s.blaze.NewrelicSecurityClientMiddleware$; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.client.Client; + +@Weave(type = MatchType.ExactClass, originalName = "org.http4s.blaze.client.BlazeClientBuilder") +public abstract class BlazeClientBuilder_Instrumentation { + + public ConcurrentEffect F() { + return Weaver.callOriginal(); + } + + public Resource> resource() { + Resource> delegateResource = Weaver.callOriginal(); + return NewrelicSecurityClientMiddleware$.MODULE$.resource(delegateResource, F()); + } +} diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala new file mode 100644 index 000000000..656a5b04c --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -0,0 +1,98 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import cats.effect.{Async, Resource, Sync} +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.logging.LogLevel +import org.http4s.Request +import org.http4s.client.Client + +import java.net.URI + +object NewrelicSecurityClientMiddleware { + private final val nrSecCustomAttrName: String = "HTTP4S-BLAZE-CLIENT-OUTBOUND" + private final val HTTP4S_BLAZE_CLIENT: String = "HTTP4S-BLAZE-CLIENT-2.12_0.22" + + private def construct[F[_] : Sync, T](t: T): F[T] = Sync[F].delay(t) + + private def clientResource[F[_] : Async](client: Client[F]): Client[F] = + Client { req: Request[F] => + for { + // pre-process hook + operation <- Resource.eval( + construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.HTTP_REQUEST, nrSecCustomAttrName) + var operation: AbstractOperation = null + if (isLockAcquired) { + operation = preprocessSecurityHook(req) + } + operation + }) + + // TODO add Security Headers + + // original call + response <- client.run(req) + + // post process and register exit event + newRes <- Resource.eval(construct{ + val isLockAcquired = GenericHelper.isLockAcquired(nrSecCustomAttrName); + if (isLockAcquired) { + GenericHelper.releaseLock(nrSecCustomAttrName) + } + registerExitOperation(isLockAcquired, operation) + response + }) + + } yield newRes + } + + def resource[F[_] : Async](delegate: Resource[F, Client[F]]): Resource[F, Client[F]] = { + val res: Resource[F, Client[F]] = delegate.map(c =>clientResource(c)) + res + } + + + private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { + try { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + if (!NewRelicSecurity.isHookProcessingActive || securityMetaData.getRequest.isEmpty) return null + // Generate required URL + var methodURI: URI = null + var uri: String = null + try { + methodURI = new URI(httpRequest.uri.toString) + uri = methodURI.toString + if (methodURI == null) return null + } catch { + case ignored: Exception => + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.URI_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, ignored.getMessage), ignored, this.getClass.getName) + return null + } + return new SSRFOperation(uri, this.getClass.getName, "run") + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + null + } + + private def registerExitOperation(isProcessingAllowed: Boolean, operation: AbstractOperation): Unit = { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive || NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isEmpty) return + NewRelicSecurity.getAgent.registerExitEvent(operation) + } catch { + case e: Throwable => + NewRelicSecurity.getAgent.log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.23/build.gradle b/instrumentation-security/http4s-blaze-client-2.12_0.23/build.gradle new file mode 100644 index 000000000..0ad63331a --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.23/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.12") + +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.12.14") + implementation('org.http4s:http4s-blaze-client_2.12:0.23.12') + implementation("org.typelevel:cats-effect_2.12:3.3.12") +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-client-2.12_0.23', 'Priority': '-1' + } +} +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-client_2.12:[0.23.0,0.24.0)' + excludeRegex '.*(RC|M)[0-9]*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java new file mode 100644 index 000000000..c5ebc74be --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java @@ -0,0 +1,22 @@ +package org.http4s; + +import cats.effect.kernel.Async; +import cats.effect.kernel.Resource; +import com.newrelic.agent.security.instrumentation.http4s.blaze.NewrelicSecurityClientMiddleware$; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.client.Client; + +@Weave(type = MatchType.ExactClass, originalName = "org.http4s.blaze.client.BlazeClientBuilder") +public abstract class BlazeClientBuilder_Instrumentation { + + public Async F() { + return Weaver.callOriginal(); + } + + public Resource> resource() { + Resource> delegateResource = Weaver.callOriginal(); + return NewrelicSecurityClientMiddleware$.MODULE$.resource(delegateResource, F()); + } +} diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala new file mode 100644 index 000000000..7bcd49cad --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -0,0 +1,98 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import cats.effect.{Async, Resource, Sync} +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.logging.LogLevel +import org.http4s.Request +import org.http4s.client.Client + +import java.net.URI + +object NewrelicSecurityClientMiddleware { + private final val nrSecCustomAttrName: String = "HTTP4S-BLAZE-CLIENT-OUTBOUND" + private final val HTTP4S_BLAZE_CLIENT: String = "HTTP4S-BLAZE-CLIENT-2.12_0.23" + + private def construct[F[_] : Sync, T](t: T): F[T] = Sync[F].delay(t) + + private def clientResource[F[_] : Async](client: Client[F]): Client[F] = + Client { req: Request[F] => + for { + // pre-process hook + operation <- Resource.eval( + construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.HTTP_REQUEST, nrSecCustomAttrName) + var operation: AbstractOperation = null + if (isLockAcquired) { + operation = preprocessSecurityHook(req) + } + operation + }) + + // TODO add Security Headers + + // original call + response <- client.run(req) + + // post process and register exit event + newRes <- Resource.eval(construct{ + val isLockAcquired = GenericHelper.isLockAcquired(nrSecCustomAttrName); + if (isLockAcquired) { + GenericHelper.releaseLock(nrSecCustomAttrName) + } + registerExitOperation(isLockAcquired, operation) + response + }) + + } yield newRes + } + + def resource[F[_] : Async](delegate: Resource[F, Client[F]]): Resource[F, Client[F]] = { + val res: Resource[F, Client[F]] = delegate.map(c =>clientResource(c)) + res + } + + + private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { + try { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + if (!NewRelicSecurity.isHookProcessingActive || securityMetaData.getRequest.isEmpty) return null + // Generate required URL + var methodURI: URI = null + var uri: String = null + try { + methodURI = new URI(httpRequest.uri.toString) + uri = methodURI.toString + if (methodURI == null) return null + } catch { + case ignored: Exception => + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.URI_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, ignored.getMessage), ignored, this.getClass.getName) + return null + } + return new SSRFOperation(uri, this.getClass.getName, "run") + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + null + } + + private def registerExitOperation(isProcessingAllowed: Boolean, operation: AbstractOperation): Unit = { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive || NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isEmpty) return + NewRelicSecurity.getAgent.registerExitEvent(operation) + } catch { + case e: Throwable => + NewRelicSecurity.getAgent.log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.21/build.gradle b/instrumentation-security/http4s-blaze-client-2.13_0.21/build.gradle new file mode 100644 index 000000000..cd6ab5f5f --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.21/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.13") + +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.13.3") + implementation("org.typelevel:cats-effect_2.13:2.5.5") + implementation('org.http4s:http4s-blaze-client_2.13:0.21.24') +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-client-2.13_0.21', 'Priority': '-1' + } +} + +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-client_2.13:[0.21.0,0.22.0)' + excludeRegex '.*(RC|M)[0-9]*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java new file mode 100644 index 000000000..a80d809da --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java @@ -0,0 +1,22 @@ +package org.http4s; + +import cats.effect.ConcurrentEffect; +import cats.effect.Resource; +import com.newrelic.agent.security.instrumentation.http4s.blaze.NewrelicSecurityClientMiddleware$; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.client.Client; + +@Weave(type = MatchType.ExactClass, originalName = "org.http4s.client.blaze.BlazeClientBuilder") +public abstract class BlazeClientBuilder_Instrumentation { + + public ConcurrentEffect F() { + return Weaver.callOriginal(); + } + + public Resource> resource() { + Resource> delegateResource = Weaver.callOriginal(); + return NewrelicSecurityClientMiddleware$.MODULE$.resource(delegateResource, F()); + } +} diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala new file mode 100644 index 000000000..2ce65be03 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -0,0 +1,98 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import cats.effect.{Async, Resource, Sync} +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.logging.LogLevel +import org.http4s.Request +import org.http4s.client.Client + +import java.net.URI + +object NewrelicSecurityClientMiddleware { + private final val nrSecCustomAttrName: String = "HTTP4S-BLAZE-CLIENT-OUTBOUND" + private final val HTTP4S_BLAZE_CLIENT: String = "HTTP4S-BLAZE-CLIENT-2.13_0.21" + + private def construct[F[_] : Sync, T](t: T): F[T] = Sync[F].delay(t) + + private def clientResource[F[_] : Async](client: Client[F]): Client[F] = + Client { req: Request[F] => + for { + // pre-process hook + operation <- Resource.liftF( + construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.HTTP_REQUEST, nrSecCustomAttrName) + var operation: AbstractOperation = null + if (isLockAcquired) { + operation = preprocessSecurityHook(req) + } + operation + }) + + // TODO add Security Headers + + // original call + response <- client.run(req) + + // post process and register exit event + newRes <- Resource.liftF(construct{ + val isLockAcquired = GenericHelper.isLockAcquired(nrSecCustomAttrName); + if (isLockAcquired) { + GenericHelper.releaseLock(nrSecCustomAttrName) + } + registerExitOperation(isLockAcquired, operation) + response + }) + + } yield newRes + } + + def resource[F[_] : Async](delegate: Resource[F, Client[F]]): Resource[F, Client[F]] = { + val res: Resource[F, Client[F]] = delegate.map(c =>clientResource(c)) + res + } + + + private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { + try { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + if (!NewRelicSecurity.isHookProcessingActive || securityMetaData.getRequest.isEmpty) return null + // Generate required URL + var methodURI: URI = null + var uri: String = null + try { + methodURI = new URI(httpRequest.uri.toString) + uri = methodURI.toString + if (methodURI == null) return null + } catch { + case ignored: Exception => + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.URI_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, ignored.getMessage), ignored, this.getClass.getName) + return null + } + return new SSRFOperation(uri, this.getClass.getName, "run") + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + null + } + + private def registerExitOperation(isProcessingAllowed: Boolean, operation: AbstractOperation): Unit = { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive || NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isEmpty) return + NewRelicSecurity.getAgent.registerExitEvent(operation) + } catch { + case e: Throwable => + NewRelicSecurity.getAgent.log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.22/build.gradle b/instrumentation-security/http4s-blaze-client-2.13_0.22/build.gradle new file mode 100644 index 000000000..89d63f453 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.22/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.13") + +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.13.3") + implementation("org.typelevel:cats-effect_2.13:2.5.5") + implementation('org.http4s:http4s-blaze-client_2.13:0.22.14') +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-client-2.13_0.22', 'Priority': '-1' + } +} + +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-client_2.13:[0.22.0,0.23.0)' + excludeRegex '.*(RC|M)[0-9]*' + excludeRegex '.*0.22\\-[0-9].*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java new file mode 100644 index 000000000..8159dc0be --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java @@ -0,0 +1,22 @@ +package org.http4s; + +import cats.effect.ConcurrentEffect; +import cats.effect.Resource; +import com.newrelic.agent.security.instrumentation.http4s.blaze.NewrelicSecurityClientMiddleware$; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.client.Client; + +@Weave(type = MatchType.ExactClass, originalName = "org.http4s.blaze.client.BlazeClientBuilder") +public abstract class BlazeClientBuilder_Instrumentation { + + public ConcurrentEffect F() { + return Weaver.callOriginal(); + } + + public Resource> resource() { + Resource> delegateResource = Weaver.callOriginal(); + return NewrelicSecurityClientMiddleware$.MODULE$.resource(delegateResource, F()); + } +} diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala new file mode 100644 index 000000000..9899184ab --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -0,0 +1,98 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import cats.effect.{Async, Resource, Sync} +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.logging.LogLevel +import org.http4s.Request +import org.http4s.client.Client + +import java.net.URI + +object NewrelicSecurityClientMiddleware { + private final val nrSecCustomAttrName: String = "HTTP4S-BLAZE-CLIENT-OUTBOUND" + private final val HTTP4S_BLAZE_CLIENT: String = "HTTP4S-BLAZE-CLIENT-2.13_0.22" + + private def construct[F[_] : Sync, T](t: T): F[T] = Sync[F].delay(t) + + private def clientResource[F[_] : Async](client: Client[F]): Client[F] = + Client { req: Request[F] => + for { + // pre-process hook + operation <- Resource.eval( + construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.HTTP_REQUEST, nrSecCustomAttrName) + var operation: AbstractOperation = null + if (isLockAcquired) { + operation = preprocessSecurityHook(req) + } + operation + }) + + // TODO add Security Headers + + // original call + response <- client.run(req) + + // post process and register exit event + newRes <- Resource.eval(construct{ + val isLockAcquired = GenericHelper.isLockAcquired(nrSecCustomAttrName); + if (isLockAcquired) { + GenericHelper.releaseLock(nrSecCustomAttrName) + } + registerExitOperation(isLockAcquired, operation) + response + }) + + } yield newRes + } + + def resource[F[_] : Async](delegate: Resource[F, Client[F]]): Resource[F, Client[F]] = { + val res: Resource[F, Client[F]] = delegate.map(c =>clientResource(c)) + res + } + + + private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { + try { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + if (!NewRelicSecurity.isHookProcessingActive || securityMetaData.getRequest.isEmpty) return null + // Generate required URL + var methodURI: URI = null + var uri: String = null + try { + methodURI = new URI(httpRequest.uri.toString) + uri = methodURI.toString + if (methodURI == null) return null + } catch { + case ignored: Exception => + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.URI_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, ignored.getMessage), ignored, this.getClass.getName) + return null + } + return new SSRFOperation(uri, this.getClass.getName, "run") + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + null + } + + private def registerExitOperation(isProcessingAllowed: Boolean, operation: AbstractOperation): Unit = { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive || NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isEmpty) return + NewRelicSecurity.getAgent.registerExitEvent(operation) + } catch { + case e: Throwable => + NewRelicSecurity.getAgent.log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.23/build.gradle b/instrumentation-security/http4s-blaze-client-2.13_0.23/build.gradle new file mode 100644 index 000000000..4770da3ff --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.23/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'scala' + +isScalaProjectEnabled(project, "scala-2.13") + +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.13.3") + implementation('org.http4s:http4s-blaze-client_2.13:0.23.12') + implementation("org.typelevel:cats-effect_2.13:3.3.12") +} + +jar { + manifest { + attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.http4s-blaze-client-2.13_0.23', 'Priority': '-1' + } +} +verifyInstrumentation { + passes 'org.http4s:http4s-blaze-client_2.13:[0.23.0,0.24.0)' + excludeRegex '.*(RC|M)[0-9]*' +} + +sourceSets.main.scala.srcDirs = ['src/main/scala', 'src/main/java'] +sourceSets.main.java.srcDirs = [] diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java new file mode 100644 index 000000000..c5ebc74be --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/java/org/http4s/BlazeClientBuilder_Instrumentation.java @@ -0,0 +1,22 @@ +package org.http4s; + +import cats.effect.kernel.Async; +import cats.effect.kernel.Resource; +import com.newrelic.agent.security.instrumentation.http4s.blaze.NewrelicSecurityClientMiddleware$; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.http4s.client.Client; + +@Weave(type = MatchType.ExactClass, originalName = "org.http4s.blaze.client.BlazeClientBuilder") +public abstract class BlazeClientBuilder_Instrumentation { + + public Async F() { + return Weaver.callOriginal(); + } + + public Resource> resource() { + Resource> delegateResource = Weaver.callOriginal(); + return NewrelicSecurityClientMiddleware$.MODULE$.resource(delegateResource, F()); + } +} diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala new file mode 100644 index 000000000..0ac08ed43 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -0,0 +1,98 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import cats.effect.{Async, Resource, Sync} +import com.newrelic.api.agent.security.NewRelicSecurity +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.logging.LogLevel +import org.http4s.Request +import org.http4s.client.Client + +import java.net.URI + +object NewrelicSecurityClientMiddleware { + private final val nrSecCustomAttrName: String = "HTTP4S-BLAZE-CLIENT-OUTBOUND" + private final val HTTP4S_BLAZE_CLIENT: String = "HTTP4S-BLAZE-CLIENT-2.13_0.23" + + private def construct[F[_] : Sync, T](t: T): F[T] = Sync[F].delay(t) + + private def clientResource[F[_] : Async](client: Client[F]): Client[F] = + Client { req: Request[F] => + for { + // pre-process hook + operation <- Resource.eval( + construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.HTTP_REQUEST, nrSecCustomAttrName) + var operation: AbstractOperation = null + if (isLockAcquired) { + operation = preprocessSecurityHook(req) + } + operation + }) + + // TODO add Security Headers + + // original call + response <- client.run(req) + + // post process and register exit event + newRes <- Resource.eval(construct{ + val isLockAcquired = GenericHelper.isLockAcquired(nrSecCustomAttrName); + if (isLockAcquired) { + GenericHelper.releaseLock(nrSecCustomAttrName) + } + registerExitOperation(isLockAcquired, operation) + response + }) + + } yield newRes + } + + def resource[F[_] : Async](delegate: Resource[F, Client[F]]): Resource[F, Client[F]] = { + val res: Resource[F, Client[F]] = delegate.map(c =>clientResource(c)) + res + } + + + private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { + try { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + if (!NewRelicSecurity.isHookProcessingActive || securityMetaData.getRequest.isEmpty) return null + // Generate required URL + var methodURI: URI = null + var uri: String = null + try { + methodURI = new URI(httpRequest.uri.toString) + uri = methodURI.toString + if (methodURI == null) return null + } catch { + case ignored: Exception => + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.URI_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, ignored.getMessage), ignored, this.getClass.getName) + return null + } + return new SSRFOperation(uri, this.getClass.getName, "run") + } catch { + case e: Throwable => + if (e.isInstanceOf[NewRelicSecurityException]) { + NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + throw e + } + NewRelicSecurity.getAgent.log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + NewRelicSecurity.getAgent.reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + null + } + + private def registerExitOperation(isProcessingAllowed: Boolean, operation: AbstractOperation): Unit = { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive || NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isEmpty) return + NewRelicSecurity.getAgent.registerExitEvent(operation) + } catch { + case e: Throwable => + NewRelicSecurity.getAgent.log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, HTTP4S_BLAZE_CLIENT, e.getMessage), e, this.getClass.getName) + } + } +} + diff --git a/settings.gradle b/settings.gradle index 36734416f..643e14237 100644 --- a/settings.gradle +++ b/settings.gradle @@ -223,4 +223,10 @@ include 'instrumentation:http4s-blaze-server-2.12_0.22' include 'instrumentation:http4s-blaze-server-2.12_0.23' include 'instrumentation:http4s-blaze-server-2.13_0.21' include 'instrumentation:http4s-blaze-server-2.13_0.22' -include 'instrumentation:http4s-blaze-server-2.13_0.23' \ No newline at end of file +include 'instrumentation:http4s-blaze-server-2.13_0.23' +include 'instrumentation:http4s-blaze-client-2.12_0.21' +include 'instrumentation:http4s-blaze-client-2.12_0.22' +include 'instrumentation:http4s-blaze-client-2.12_0.23' +include 'instrumentation:http4s-blaze-client-2.13_0.21' +include 'instrumentation:http4s-blaze-client-2.13_0.22' +include 'instrumentation:http4s-blaze-client-2.13_0.23' \ No newline at end of file From 73e380c938346b26488ad653eadb6e96b55b1a8c Mon Sep 17 00:00:00 2001 From: idawda Date: Wed, 30 Oct 2024 17:55:20 +0530 Subject: [PATCH 04/23] NR-325527: Http4s Blaze client outgoing HTTP Request header manipulation --- .../NewrelicSecurityClientMiddleware.scala | 34 ++++++++++++++++--- .../http4s/blaze/OutboundRequestWrapper.scala | 19 +++++++++++ .../NewrelicSecurityClientMiddleware.scala | 33 +++++++++++++++--- .../http4s/blaze/OutboundRequestWrapper.scala | 19 +++++++++++ .../NewrelicSecurityClientMiddleware.scala | 33 +++++++++++++++--- .../http4s/blaze/OutboundRequestWrapper.scala | 19 +++++++++++ .../NewrelicSecurityClientMiddleware.scala | 33 +++++++++++++++--- .../http4s/blaze/OutboundRequestWrapper.scala | 19 +++++++++++ .../NewrelicSecurityClientMiddleware.scala | 33 +++++++++++++++--- .../http4s/blaze/OutboundRequestWrapper.scala | 19 +++++++++++ .../NewrelicSecurityClientMiddleware.scala | 33 +++++++++++++++--- .../http4s/blaze/OutboundRequestWrapper.scala | 19 +++++++++++ .../http4s/blaze/server/BlazeUtils.java | 2 +- .../http4s/blaze/server/BlazeUtils.java | 2 +- .../http4s/blaze/server/BlazeUtils.java | 2 +- .../http4s/blaze/server/BlazeUtils.java | 2 +- .../http4s/blaze/server/BlazeUtils.java | 2 +- .../http4s/blaze/server/BlazeUtils.java | 2 +- 18 files changed, 295 insertions(+), 30 deletions(-) create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala index b18eab444..cf63c2a81 100644 --- a/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala +++ b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -2,10 +2,11 @@ package com.newrelic.agent.security.instrumentation.http4s.blaze import cats.effect.{Async, ConcurrentEffect, Resource, Sync} import com.newrelic.api.agent.security.NewRelicSecurity -import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.SSRFOperation -import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.schema.{AbstractOperation, StringUtils, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.SSRFUtils import com.newrelic.api.agent.security.utils.logging.LogLevel import org.http4s.Request import org.http4s.client.Client @@ -31,10 +32,10 @@ object NewrelicSecurityClientMiddleware { operation }) - // TODO add Security Headers + request <- Resource.liftF(construct {addSecurityHeaders(req, operation)}) // original call - response <- client.run(req) + response <- client.run(request) // post process and register exit event newRes <- Resource.liftF(construct{ @@ -54,6 +55,31 @@ object NewrelicSecurityClientMiddleware { res } + private def addSecurityHeaders[F[_] : Async](request: Request[F], operation: AbstractOperation): Request[F] = { + val outboundRequest = new OutboundRequest(request) + if (operation != null) { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val iastHeader = NewRelicSecurity.getAgent.getSecurityMetaData.getFuzzRequestIdentifier.getRaw + if (iastHeader != null && iastHeader.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, iastHeader) + } + val csecParentId = securityMetaData.getCustomAttribute(GenericHelper.CSEC_PARENT_ID, classOf[String]) + if (StringUtils.isNotBlank(csecParentId)) { + outboundRequest.setHeader(GenericHelper.CSEC_PARENT_ID, csecParentId) + } + try { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(Integer.valueOf(4)) + NewRelicSecurity.getAgent.registerOperation(operation) + } + finally { + if (operation.getApiID != null && operation.getApiID.trim.nonEmpty && operation.getExecutionId != null && operation.getExecutionId.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, SSRFUtils.generateTracingHeaderValue(securityMetaData.getTracingHeaderValue, operation.getApiID, operation.getExecutionId, NewRelicSecurity.getAgent.getAgentUUID)) + } + } + } + outboundRequest.getRequest + } + private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { try { diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala new file mode 100644 index 000000000..40a92e4f7 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala @@ -0,0 +1,19 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import org.http4s.util.CaseInsensitiveString +import org.http4s.{Header, Request} + +/** + * Http4s's HttpRequest is immutable so we have to create a copy with the new headers. + */ + +class OutboundRequest[F[_]](request: Request[F]) { + private var req: Request[F] = request + + def setHeader(key: String, value: String): Unit = { + req = req.withHeaders(req.headers.put(Header.Raw.apply(CaseInsensitiveString.apply(key), value))) + } + def getRequest: Request[F] = { + req + } +} \ No newline at end of file diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala index 656a5b04c..c26aaa4d3 100644 --- a/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala +++ b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -2,10 +2,11 @@ package com.newrelic.agent.security.instrumentation.http4s.blaze import cats.effect.{Async, Resource, Sync} import com.newrelic.api.agent.security.NewRelicSecurity -import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.SSRFOperation -import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.schema.{AbstractOperation, StringUtils, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.SSRFUtils import com.newrelic.api.agent.security.utils.logging.LogLevel import org.http4s.Request import org.http4s.client.Client @@ -32,10 +33,10 @@ object NewrelicSecurityClientMiddleware { operation }) - // TODO add Security Headers + request <- Resource.eval(construct{addSecurityHeaders(req, operation)}) // original call - response <- client.run(req) + response <- client.run(request) // post process and register exit event newRes <- Resource.eval(construct{ @@ -55,6 +56,30 @@ object NewrelicSecurityClientMiddleware { res } + private def addSecurityHeaders[F[_] : Async](request: Request[F], operation: AbstractOperation): Request[F] = { + val outboundRequest = new OutboundRequest(request) + if (operation != null) { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val iastHeader = NewRelicSecurity.getAgent.getSecurityMetaData.getFuzzRequestIdentifier.getRaw + if (iastHeader != null && iastHeader.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, iastHeader) + } + val csecParentId = securityMetaData.getCustomAttribute(GenericHelper.CSEC_PARENT_ID, classOf[String]) + if (StringUtils.isNotBlank(csecParentId)) { + outboundRequest.setHeader(GenericHelper.CSEC_PARENT_ID, csecParentId) + } + try { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(Integer.valueOf(4)) + NewRelicSecurity.getAgent.registerOperation(operation) + } + finally { + if (operation.getApiID != null && operation.getApiID.trim.nonEmpty && operation.getExecutionId != null && operation.getExecutionId.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, SSRFUtils.generateTracingHeaderValue(securityMetaData.getTracingHeaderValue, operation.getApiID, operation.getExecutionId, NewRelicSecurity.getAgent.getAgentUUID)) + } + } + } + outboundRequest.getRequest + } private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { try { diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala new file mode 100644 index 000000000..8f30ab6d3 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala @@ -0,0 +1,19 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import org.http4s.{Header, Request} +import org.typelevel.ci.CIString + +/** + * Http4s's HttpRequest is immutable so we have to create a copy with the new headers. + */ + +class OutboundRequest[F[_]](request: Request[F]) { + private var req: Request[F] = request + + def setHeader(key: String, value: String): Unit = { + req = req.withHeaders(req.headers.put(Header.Raw.apply(CIString.apply(key), value))) + } + def getRequest: Request[F] = { + req + } +} \ No newline at end of file diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala index 7bcd49cad..d111a785f 100644 --- a/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala +++ b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -2,10 +2,11 @@ package com.newrelic.agent.security.instrumentation.http4s.blaze import cats.effect.{Async, Resource, Sync} import com.newrelic.api.agent.security.NewRelicSecurity -import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.SSRFOperation -import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.schema.{AbstractOperation, StringUtils, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.SSRFUtils import com.newrelic.api.agent.security.utils.logging.LogLevel import org.http4s.Request import org.http4s.client.Client @@ -32,10 +33,10 @@ object NewrelicSecurityClientMiddleware { operation }) - // TODO add Security Headers + request <- Resource.eval(construct{addSecurityHeaders(req, operation)}) // original call - response <- client.run(req) + response <- client.run(request) // post process and register exit event newRes <- Resource.eval(construct{ @@ -55,6 +56,30 @@ object NewrelicSecurityClientMiddleware { res } + private def addSecurityHeaders[F[_] : Async](request: Request[F], operation: AbstractOperation): Request[F] = { + val outboundRequest = new OutboundRequest(request) + if (operation != null) { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val iastHeader = NewRelicSecurity.getAgent.getSecurityMetaData.getFuzzRequestIdentifier.getRaw + if (iastHeader != null && iastHeader.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, iastHeader) + } + val csecParentId = securityMetaData.getCustomAttribute(GenericHelper.CSEC_PARENT_ID, classOf[String]) + if (StringUtils.isNotBlank(csecParentId)) { + outboundRequest.setHeader(GenericHelper.CSEC_PARENT_ID, csecParentId) + } + try { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(Integer.valueOf(4)) + NewRelicSecurity.getAgent.registerOperation(operation) + } + finally { + if (operation.getApiID != null && operation.getApiID.trim.nonEmpty && operation.getExecutionId != null && operation.getExecutionId.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, SSRFUtils.generateTracingHeaderValue(securityMetaData.getTracingHeaderValue, operation.getApiID, operation.getExecutionId, NewRelicSecurity.getAgent.getAgentUUID)) + } + } + } + outboundRequest.getRequest + } private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { try { diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala new file mode 100644 index 000000000..8f30ab6d3 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala @@ -0,0 +1,19 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import org.http4s.{Header, Request} +import org.typelevel.ci.CIString + +/** + * Http4s's HttpRequest is immutable so we have to create a copy with the new headers. + */ + +class OutboundRequest[F[_]](request: Request[F]) { + private var req: Request[F] = request + + def setHeader(key: String, value: String): Unit = { + req = req.withHeaders(req.headers.put(Header.Raw.apply(CIString.apply(key), value))) + } + def getRequest: Request[F] = { + req + } +} \ No newline at end of file diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala index 2ce65be03..989bf012e 100644 --- a/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala +++ b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -2,10 +2,11 @@ package com.newrelic.agent.security.instrumentation.http4s.blaze import cats.effect.{Async, Resource, Sync} import com.newrelic.api.agent.security.NewRelicSecurity -import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.SSRFOperation -import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.schema.{AbstractOperation, StringUtils, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.SSRFUtils import com.newrelic.api.agent.security.utils.logging.LogLevel import org.http4s.Request import org.http4s.client.Client @@ -32,10 +33,10 @@ object NewrelicSecurityClientMiddleware { operation }) - // TODO add Security Headers + request <- Resource.liftF(construct {addSecurityHeaders(req, operation)}) // original call - response <- client.run(req) + response <- client.run(request) // post process and register exit event newRes <- Resource.liftF(construct{ @@ -55,6 +56,30 @@ object NewrelicSecurityClientMiddleware { res } + private def addSecurityHeaders[F[_] : Async](request: Request[F], operation: AbstractOperation): Request[F] = { + val outboundRequest = new OutboundRequest(request) + if (operation != null) { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val iastHeader = NewRelicSecurity.getAgent.getSecurityMetaData.getFuzzRequestIdentifier.getRaw + if (iastHeader != null && iastHeader.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, iastHeader) + } + val csecParentId = securityMetaData.getCustomAttribute(GenericHelper.CSEC_PARENT_ID, classOf[String]) + if (StringUtils.isNotBlank(csecParentId)) { + outboundRequest.setHeader(GenericHelper.CSEC_PARENT_ID, csecParentId) + } + try { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(Integer.valueOf(4)) + NewRelicSecurity.getAgent.registerOperation(operation) + } + finally { + if (operation.getApiID != null && operation.getApiID.trim.nonEmpty && operation.getExecutionId != null && operation.getExecutionId.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, SSRFUtils.generateTracingHeaderValue(securityMetaData.getTracingHeaderValue, operation.getApiID, operation.getExecutionId, NewRelicSecurity.getAgent.getAgentUUID)) + } + } + } + outboundRequest.getRequest + } private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { try { diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala new file mode 100644 index 000000000..40a92e4f7 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala @@ -0,0 +1,19 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import org.http4s.util.CaseInsensitiveString +import org.http4s.{Header, Request} + +/** + * Http4s's HttpRequest is immutable so we have to create a copy with the new headers. + */ + +class OutboundRequest[F[_]](request: Request[F]) { + private var req: Request[F] = request + + def setHeader(key: String, value: String): Unit = { + req = req.withHeaders(req.headers.put(Header.Raw.apply(CaseInsensitiveString.apply(key), value))) + } + def getRequest: Request[F] = { + req + } +} \ No newline at end of file diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala index 9899184ab..425ee249c 100644 --- a/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala +++ b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -2,10 +2,11 @@ package com.newrelic.agent.security.instrumentation.http4s.blaze import cats.effect.{Async, Resource, Sync} import com.newrelic.api.agent.security.NewRelicSecurity -import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.SSRFOperation -import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.schema.{AbstractOperation, StringUtils, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.SSRFUtils import com.newrelic.api.agent.security.utils.logging.LogLevel import org.http4s.Request import org.http4s.client.Client @@ -32,10 +33,10 @@ object NewrelicSecurityClientMiddleware { operation }) - // TODO add Security Headers + request <- Resource.eval(construct{addSecurityHeaders(req, operation)}) // original call - response <- client.run(req) + response <- client.run(request) // post process and register exit event newRes <- Resource.eval(construct{ @@ -55,6 +56,30 @@ object NewrelicSecurityClientMiddleware { res } + private def addSecurityHeaders[F[_] : Async](request: Request[F], operation: AbstractOperation): Request[F] = { + val outboundRequest = new OutboundRequest(request) + if (operation != null) { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val iastHeader = NewRelicSecurity.getAgent.getSecurityMetaData.getFuzzRequestIdentifier.getRaw + if (iastHeader != null && iastHeader.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, iastHeader) + } + val csecParentId = securityMetaData.getCustomAttribute(GenericHelper.CSEC_PARENT_ID, classOf[String]) + if (StringUtils.isNotBlank(csecParentId)) { + outboundRequest.setHeader(GenericHelper.CSEC_PARENT_ID, csecParentId) + } + try { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(Integer.valueOf(4)) + NewRelicSecurity.getAgent.registerOperation(operation) + } + finally { + if (operation.getApiID != null && operation.getApiID.trim.nonEmpty && operation.getExecutionId != null && operation.getExecutionId.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, SSRFUtils.generateTracingHeaderValue(securityMetaData.getTracingHeaderValue, operation.getApiID, operation.getExecutionId, NewRelicSecurity.getAgent.getAgentUUID)) + } + } + } + outboundRequest.getRequest + } private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { try { diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala new file mode 100644 index 000000000..8f30ab6d3 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala @@ -0,0 +1,19 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import org.http4s.{Header, Request} +import org.typelevel.ci.CIString + +/** + * Http4s's HttpRequest is immutable so we have to create a copy with the new headers. + */ + +class OutboundRequest[F[_]](request: Request[F]) { + private var req: Request[F] = request + + def setHeader(key: String, value: String): Unit = { + req = req.withHeaders(req.headers.put(Header.Raw.apply(CIString.apply(key), value))) + } + def getRequest: Request[F] = { + req + } +} \ No newline at end of file diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala index 0ac08ed43..788343539 100644 --- a/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala +++ b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/NewrelicSecurityClientMiddleware.scala @@ -2,10 +2,11 @@ package com.newrelic.agent.security.instrumentation.http4s.blaze import cats.effect.{Async, Resource, Sync} import com.newrelic.api.agent.security.NewRelicSecurity -import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.SSRFOperation -import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.newrelic.api.agent.security.schema.{AbstractOperation, StringUtils, VulnerabilityCaseType} +import com.newrelic.api.agent.security.utils.SSRFUtils import com.newrelic.api.agent.security.utils.logging.LogLevel import org.http4s.Request import org.http4s.client.Client @@ -32,10 +33,10 @@ object NewrelicSecurityClientMiddleware { operation }) - // TODO add Security Headers + request <- Resource.eval(construct{addSecurityHeaders(req, operation)}) // original call - response <- client.run(req) + response <- client.run(request) // post process and register exit event newRes <- Resource.eval(construct{ @@ -55,6 +56,30 @@ object NewrelicSecurityClientMiddleware { res } + private def addSecurityHeaders[F[_] : Async](request: Request[F], operation: AbstractOperation): Request[F] = { + val outboundRequest = new OutboundRequest(request) + if (operation != null) { + val securityMetaData = NewRelicSecurity.getAgent.getSecurityMetaData + val iastHeader = NewRelicSecurity.getAgent.getSecurityMetaData.getFuzzRequestIdentifier.getRaw + if (iastHeader != null && iastHeader.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, iastHeader) + } + val csecParentId = securityMetaData.getCustomAttribute(GenericHelper.CSEC_PARENT_ID, classOf[String]) + if (StringUtils.isNotBlank(csecParentId)) { + outboundRequest.setHeader(GenericHelper.CSEC_PARENT_ID, csecParentId) + } + try { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(Integer.valueOf(4)) + NewRelicSecurity.getAgent.registerOperation(operation) + } + finally { + if (operation.getApiID != null && operation.getApiID.trim.nonEmpty && operation.getExecutionId != null && operation.getExecutionId.trim.nonEmpty) { + outboundRequest.setHeader(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, SSRFUtils.generateTracingHeaderValue(securityMetaData.getTracingHeaderValue, operation.getApiID, operation.getExecutionId, NewRelicSecurity.getAgent.getAgentUUID)) + } + } + } + outboundRequest.getRequest + } private def preprocessSecurityHook[F[_] : Async](httpRequest: Request[F]): AbstractOperation = { try { diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala new file mode 100644 index 000000000..8f30ab6d3 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/main/scala/com/newrelic/agent/security/instrumentation/http4s/blaze/OutboundRequestWrapper.scala @@ -0,0 +1,19 @@ +package com.newrelic.agent.security.instrumentation.http4s.blaze + +import org.http4s.{Header, Request} +import org.typelevel.ci.CIString + +/** + * Http4s's HttpRequest is immutable so we have to create a copy with the new headers. + */ + +class OutboundRequest[F[_]](request: Request[F]) { + private var req: Request[F] = request + + def setHeader(key: String, value: String): Unit = { + req = req.withHeaders(req.headers.put(Header.Raw.apply(CIString.apply(key), value))) + } + def getRequest: Request[F] = { + req + } +} \ No newline at end of file diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java index 262a24583..b12ca2233 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -62,6 +62,6 @@ public static void releaseLock() { } private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + return "HTTP4S-BLAZE-REQUEST_LOCK" + Thread.currentThread().getId(); } } diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java index 262a24583..b12ca2233 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -62,6 +62,6 @@ public static void releaseLock() { } private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + return "HTTP4S-BLAZE-REQUEST_LOCK" + Thread.currentThread().getId(); } } diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java index 262a24583..b12ca2233 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -62,6 +62,6 @@ public static void releaseLock() { } private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + return "HTTP4S-BLAZE-REQUEST_LOCK" + Thread.currentThread().getId(); } } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java index 262a24583..b12ca2233 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -62,6 +62,6 @@ public static void releaseLock() { } private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + return "HTTP4S-BLAZE-REQUEST_LOCK" + Thread.currentThread().getId(); } } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java index 262a24583..b12ca2233 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -62,6 +62,6 @@ public static void releaseLock() { } private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + return "HTTP4S-BLAZE-REQUEST_LOCK" + Thread.currentThread().getId(); } } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java index 262a24583..b12ca2233 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java @@ -62,6 +62,6 @@ public static void releaseLock() { } private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); + return "HTTP4S-BLAZE-REQUEST_LOCK" + Thread.currentThread().getId(); } } From 9cebbf3732dad7779ed9ba3a67743e4de215e6ed Mon Sep 17 00:00:00 2001 From: idawda Date: Tue, 5 Nov 2024 17:58:01 +0530 Subject: [PATCH 05/23] NR-332541: Unit tests for http4s-blaze client instrumentation support --- .../blaze/client/BlazeClientTest.scala | 93 +++++++++++++++++++ .../blaze/client/BlazeClientTest.scala | 93 +++++++++++++++++++ .../blaze/client/BlazeClientTest.scala | 93 +++++++++++++++++++ .../blaze/client/BlazeClientTest.scala | 93 +++++++++++++++++++ .../blaze/client/BlazeClientTest.scala | 93 +++++++++++++++++++ .../blaze/client/BlazeClientTest.scala | 93 +++++++++++++++++++ 6 files changed, 558 insertions(+) create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala create mode 100644 instrumentation-security/http4s-blaze-client-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala new file mode 100644 index 000000000..8cc8bc228 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala @@ -0,0 +1,93 @@ +package com.nr.agent.security.instrumentation.blaze.client + +import cats.effect.{ConcurrentEffect, ContextShift, IO, Timer} +import com.newrelic.agent.security.introspec.internal.HttpServerRule +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.nr.agent.security.instrumentation.blaze.client.Http4sTestUtils.makeRequest +import org.http4s.client.blaze.BlazeClientBuilder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.{Assert, FixMethodOrder, Rule, Test} + +import java.util +import java.util.UUID +import scala.concurrent.ExecutionContext +import scala.concurrent.ExecutionContext.global +import scala.concurrent.duration.DurationInt + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.instrumentation.http4s")) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class BlazeClientTest { + + @Rule + def server: HttpServerRule = httpServer + + implicit val ec: ExecutionContext = global + implicit val cs: ContextShift[IO] = IO.contextShift(global) + implicit val timer: Timer[IO] = IO.timer(global) + + val httpServer = new HttpServerRule() + + @Test + def blazeClientTest(): Unit = { + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + makeRequest(s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + + } + + @Test + def blazeClientTestWithHeaders(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + setCSECHeaders(headerValue = headerValue, introspector = introspector) + makeRequest(s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + verifyHeaders(headerValue, httpServer.getHeaders) + } + + + private def assertSSRFOperation(operations: util.List[AbstractOperation]): Unit = { + Assert.assertTrue("Incorrect number of operations detected!", operations.size == 1) + Assert.assertTrue("SSRFOperation not found!", operations.get(0).isInstanceOf[SSRFOperation]) + val operation: SSRFOperation = operations.get(0).asInstanceOf[SSRFOperation] + + 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.", "run", operation.getMethodName) + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint.toString, operation.getArg) + } + + private def verifyHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def setCSECHeaders(headerValue: String, introspector: SecurityIntrospector): Unit = { + introspector.setK2FuzzRequestId(headerValue + "a") + introspector.setK2ParentId(headerValue + "b") + introspector.setK2TracingData(headerValue) + } +} + +object Http4sTestUtils { + def makeRequest[F[_] : ContextShift : Timer](url: String)( + implicit ex: ExecutionContext, c: ConcurrentEffect[F]): F[String] = { + BlazeClientBuilder[F](ex).resource.use { client => + client.expect[String](url) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala new file mode 100644 index 000000000..8cc8bc228 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala @@ -0,0 +1,93 @@ +package com.nr.agent.security.instrumentation.blaze.client + +import cats.effect.{ConcurrentEffect, ContextShift, IO, Timer} +import com.newrelic.agent.security.introspec.internal.HttpServerRule +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.nr.agent.security.instrumentation.blaze.client.Http4sTestUtils.makeRequest +import org.http4s.client.blaze.BlazeClientBuilder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.{Assert, FixMethodOrder, Rule, Test} + +import java.util +import java.util.UUID +import scala.concurrent.ExecutionContext +import scala.concurrent.ExecutionContext.global +import scala.concurrent.duration.DurationInt + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.instrumentation.http4s")) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class BlazeClientTest { + + @Rule + def server: HttpServerRule = httpServer + + implicit val ec: ExecutionContext = global + implicit val cs: ContextShift[IO] = IO.contextShift(global) + implicit val timer: Timer[IO] = IO.timer(global) + + val httpServer = new HttpServerRule() + + @Test + def blazeClientTest(): Unit = { + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + makeRequest(s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + + } + + @Test + def blazeClientTestWithHeaders(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + setCSECHeaders(headerValue = headerValue, introspector = introspector) + makeRequest(s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + verifyHeaders(headerValue, httpServer.getHeaders) + } + + + private def assertSSRFOperation(operations: util.List[AbstractOperation]): Unit = { + Assert.assertTrue("Incorrect number of operations detected!", operations.size == 1) + Assert.assertTrue("SSRFOperation not found!", operations.get(0).isInstanceOf[SSRFOperation]) + val operation: SSRFOperation = operations.get(0).asInstanceOf[SSRFOperation] + + 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.", "run", operation.getMethodName) + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint.toString, operation.getArg) + } + + private def verifyHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def setCSECHeaders(headerValue: String, introspector: SecurityIntrospector): Unit = { + introspector.setK2FuzzRequestId(headerValue + "a") + introspector.setK2ParentId(headerValue + "b") + introspector.setK2TracingData(headerValue) + } +} + +object Http4sTestUtils { + def makeRequest[F[_] : ContextShift : Timer](url: String)( + implicit ex: ExecutionContext, c: ConcurrentEffect[F]): F[String] = { + BlazeClientBuilder[F](ex).resource.use { client => + client.expect[String](url) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala new file mode 100644 index 000000000..0f60d5313 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala @@ -0,0 +1,93 @@ +package com.nr.agent.security.instrumentation.blaze.client + +import cats.effect.IO +import cats.effect.kernel.Async +import cats.effect.unsafe.IORuntime +import com.newrelic.agent.security.introspec.internal.HttpServerRule +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.nr.agent.security.instrumentation.blaze.client.Http4sTestUtils.makeRequest +import org.http4s.blaze.client.BlazeClientBuilder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.{Assert, FixMethodOrder, Rule, Test} + +import java.util +import java.util.UUID +import scala.concurrent.ExecutionContext +import scala.concurrent.ExecutionContext.global +import scala.concurrent.duration.DurationInt + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.instrumentation.http4s")) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class BlazeClientTest { + + @Rule + def server: HttpServerRule = httpServer + + implicit val ec: ExecutionContext = global + implicit val io: IORuntime = IORuntime.global + + val httpServer = new HttpServerRule() + + @Test + def blazeClientTest(): Unit = { + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + makeRequest[IO](s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + + } + + @Test + def blazeClientTestWithHeaders(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + setCSECHeaders(headerValue = headerValue, introspector = introspector) + makeRequest[IO](s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + verifyHeaders(headerValue, httpServer.getHeaders) + } + + + private def assertSSRFOperation(operations: util.List[AbstractOperation]): Unit = { + Assert.assertTrue("Incorrect number of operations detected!", operations.size == 1) + Assert.assertTrue("SSRFOperation not found!", operations.get(0).isInstanceOf[SSRFOperation]) + val operation: SSRFOperation = operations.get(0).asInstanceOf[SSRFOperation] + + 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.", "run", operation.getMethodName) + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint.toString, operation.getArg) + } + + private def verifyHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def setCSECHeaders(headerValue: String, introspector: SecurityIntrospector): Unit = { + introspector.setK2FuzzRequestId(headerValue + "a") + introspector.setK2ParentId(headerValue + "b") + introspector.setK2TracingData(headerValue) + } +} + +object Http4sTestUtils { + def makeRequest[F[_]: Async](url: String)(implicit ex: ExecutionContext): F[String] = { + BlazeClientBuilder[F].resource.use { client => + client.expect[String](url) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala new file mode 100644 index 000000000..a73ddb232 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala @@ -0,0 +1,93 @@ +package com.nr.agent.security.instrumentation.blaze.client + +import cats.effect.{ConcurrentEffect, ContextShift, IO, Timer} +import com.newrelic.agent.security.introspec.internal.HttpServerRule +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.nr.agent.security.instrumentation.blaze.client.BlazeClientTest$.makeRequest +import org.http4s.client.blaze.BlazeClientBuilder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.{Assert, FixMethodOrder, Rule, Test} + +import java.util +import java.util.UUID +import scala.concurrent.ExecutionContext +import scala.concurrent.ExecutionContext.global +import scala.concurrent.duration.DurationInt + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.instrumentation.http4s")) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class BlazeClientTest { + + @Rule + def server: HttpServerRule = httpServer + + implicit val ec: ExecutionContext = global + implicit val cs: ContextShift[IO] = IO.contextShift(global) + implicit val timer: Timer[IO] = IO.timer(global) + + val httpServer = new HttpServerRule() + + @Test + def blazeClientTest(): Unit = { + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + makeRequest(s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + + } + + @Test + def blazeClientTestWithHeaders(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + setCSECHeaders(headerValue = headerValue, introspector = introspector) + makeRequest(s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + verifyHeaders(headerValue, httpServer.getHeaders) + } + + + private def assertSSRFOperation(operations: util.List[AbstractOperation]): Unit = { + Assert.assertTrue("Incorrect number of operations detected!", operations.size == 1) + Assert.assertTrue("SSRFOperation not found!", operations.get(0).isInstanceOf[SSRFOperation]) + val operation: SSRFOperation = operations.get(0).asInstanceOf[SSRFOperation] + + 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.", "run", operation.getMethodName) + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint.toString, operation.getArg) + } + + private def verifyHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def setCSECHeaders(headerValue: String, introspector: SecurityIntrospector): Unit = { + introspector.setK2FuzzRequestId(headerValue + "a") + introspector.setK2ParentId(headerValue + "b") + introspector.setK2TracingData(headerValue) + } +} + +object BlazeClientTest$ { + def makeRequest[F[_] : ContextShift : Timer](url: String)( + implicit ex: ExecutionContext, c: ConcurrentEffect[F]): F[String] = { + BlazeClientBuilder[F](ex).resource.use { client => + client.expect[String](url) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala new file mode 100644 index 000000000..fe646f10b --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala @@ -0,0 +1,93 @@ +package com.nr.agent.security.instrumentation.blaze.client + +import cats.effect.{ConcurrentEffect, ContextShift, IO, Timer} +import com.newrelic.agent.security.introspec.internal.HttpServerRule +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.nr.agent.security.instrumentation.blaze.client.Http4sTestUtils.makeRequest +import org.http4s.blaze.client.BlazeClientBuilder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.{Assert, FixMethodOrder, Rule, Test} + +import java.util +import java.util.UUID +import scala.concurrent.ExecutionContext +import scala.concurrent.ExecutionContext.global +import scala.concurrent.duration.DurationInt + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.instrumentation.http4s")) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class BlazeClientTest { + + @Rule + def server: HttpServerRule = httpServer + + implicit val ec: ExecutionContext = global + implicit val cs: ContextShift[IO] = IO.contextShift(global) + implicit val timer: Timer[IO] = IO.timer(global) + + val httpServer = new HttpServerRule() + + @Test + def blazeClientTest(): Unit = { + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + makeRequest(s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + + } + + @Test + def blazeClientTestWithHeaders(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + setCSECHeaders(headerValue = headerValue, introspector = introspector) + makeRequest(s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + verifyHeaders(headerValue, httpServer.getHeaders) + } + + + private def assertSSRFOperation(operations: util.List[AbstractOperation]): Unit = { + Assert.assertTrue("Incorrect number of operations detected!", operations.size == 1) + Assert.assertTrue("SSRFOperation not found!", operations.get(0).isInstanceOf[SSRFOperation]) + val operation: SSRFOperation = operations.get(0).asInstanceOf[SSRFOperation] + + 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.", "run", operation.getMethodName) + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint.toString, operation.getArg) + } + + private def verifyHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def setCSECHeaders(headerValue: String, introspector: SecurityIntrospector): Unit = { + introspector.setK2FuzzRequestId(headerValue + "a") + introspector.setK2ParentId(headerValue + "b") + introspector.setK2TracingData(headerValue) + } +} + +object Http4sTestUtils { + def makeRequest[F[_] : ContextShift : Timer](url: String)( + implicit ex: ExecutionContext, c: ConcurrentEffect[F]): F[String] = { + BlazeClientBuilder[F](ex).resource.use { client => + client.expect[String](url) + } + } +} + diff --git a/instrumentation-security/http4s-blaze-client-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala new file mode 100644 index 000000000..0f60d5313 --- /dev/null +++ b/instrumentation-security/http4s-blaze-client-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/client/BlazeClientTest.scala @@ -0,0 +1,93 @@ +package com.nr.agent.security.instrumentation.blaze.client + +import cats.effect.IO +import cats.effect.kernel.Async +import cats.effect.unsafe.IORuntime +import com.newrelic.agent.security.introspec.internal.HttpServerRule +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.SSRFOperation +import com.newrelic.api.agent.security.schema.{AbstractOperation, VulnerabilityCaseType} +import com.nr.agent.security.instrumentation.blaze.client.Http4sTestUtils.makeRequest +import org.http4s.blaze.client.BlazeClientBuilder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.{Assert, FixMethodOrder, Rule, Test} + +import java.util +import java.util.UUID +import scala.concurrent.ExecutionContext +import scala.concurrent.ExecutionContext.global +import scala.concurrent.duration.DurationInt + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.instrumentation.http4s")) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class BlazeClientTest { + + @Rule + def server: HttpServerRule = httpServer + + implicit val ec: ExecutionContext = global + implicit val io: IORuntime = IORuntime.global + + val httpServer = new HttpServerRule() + + @Test + def blazeClientTest(): Unit = { + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + makeRequest[IO](s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + + } + + @Test + def blazeClientTestWithHeaders(): Unit = { + val headerValue = String.valueOf(UUID.randomUUID) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + setCSECHeaders(headerValue = headerValue, introspector = introspector) + makeRequest[IO](s"${server.getEndPoint}").unsafeRunTimed(2.seconds) + assertSSRFOperation(introspector.getOperations) + verifyHeaders(headerValue, httpServer.getHeaders) + } + + + private def assertSSRFOperation(operations: util.List[AbstractOperation]): Unit = { + Assert.assertTrue("Incorrect number of operations detected!", operations.size == 1) + Assert.assertTrue("SSRFOperation not found!", operations.get(0).isInstanceOf[SSRFOperation]) + val operation: SSRFOperation = operations.get(0).asInstanceOf[SSRFOperation] + + 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.", "run", operation.getMethodName) + Assert.assertEquals("Invalid executed parameters.", server.getEndPoint.toString, operation.getArg) + } + + private def verifyHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def setCSECHeaders(headerValue: String, introspector: SecurityIntrospector): Unit = { + introspector.setK2FuzzRequestId(headerValue + "a") + introspector.setK2ParentId(headerValue + "b") + introspector.setK2TracingData(headerValue) + } +} + +object Http4sTestUtils { + def makeRequest[F[_]: Async](url: String)(implicit ex: ExecutionContext): F[String] = { + BlazeClientBuilder[F].resource.use { client => + client.expect[String](url) + } + } +} + From db618967e5bf7c1b37a1d6da94e23a412bfd2091 Mon Sep 17 00:00:00 2001 From: idawda Date: Thu, 7 Nov 2024 19:07:14 +0530 Subject: [PATCH 06/23] NR-336715: Fix for NR-322822 Where Incorrect user class details found in mule-demo-app --- .../agent/security/instrumentation/mule36/MuleHelper.java | 4 +--- .../processor/InvokerMessageProcessor_Instrumentation.java | 1 + .../agent/security/instrumentation/mule37/MuleHelper.java | 4 +--- .../processor/InvokerMessageProcessor_Instrumentation.java | 1 + 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/instrumentation-security/mule-3.6/src/main/java/com/newrelic/agent/security/instrumentation/mule36/MuleHelper.java b/instrumentation-security/mule-3.6/src/main/java/com/newrelic/agent/security/instrumentation/mule36/MuleHelper.java index 4683aab38..ef8aaeeca 100644 --- a/instrumentation-security/mule-3.6/src/main/java/com/newrelic/agent/security/instrumentation/mule36/MuleHelper.java +++ b/instrumentation-security/mule-3.6/src/main/java/com/newrelic/agent/security/instrumentation/mule36/MuleHelper.java @@ -124,9 +124,7 @@ public static void gatherURLMappings(HttpListener messageSource, List Date: Thu, 7 Nov 2024 19:08:35 +0530 Subject: [PATCH 07/23] process stacktrace in mule server for both RXSS and non RXSS events --- .../listener/HttpRequestToMuleEvent_Instrumentation.java | 4 +--- .../listener/async/RequestHandler_Instrumentation.java | 4 +--- .../InvokerMessageProcessor_Instrumentation.java | 9 +++++++++ .../listener/HttpRequestToMuleEvent_Instrumentation.java | 4 +--- .../listener/async/RequestHandler_Instrumentation.java | 4 +--- .../InvokerMessageProcessor_Instrumentation.java | 9 +++++++++ 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java b/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java index c67512563..5aa5916de 100644 --- a/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java +++ b/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java @@ -82,9 +82,6 @@ private static void preprocessSecurityHook(HttpRequestContext requestContext) { // TODO: Create OutBoundHttp data here : Skipping for now. securityRequest.setContentType(MuleHelper.getContentType(securityRequest.getHeaders())); - - // TODO: need to update UserClassEntity - ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); securityRequest.setRequestParsed(true); } catch (Throwable ignored){} } @@ -95,6 +92,7 @@ private static void postProcessSecurityHook() { ) { return; } + ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); ServletHelper.executeBeforeExitingTransaction(); //Add request URI hash to low severity event filter LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); diff --git a/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java b/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java index 5b4de2ce6..72cdf26b1 100644 --- a/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java +++ b/instrumentation-security/mule-3.6/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java @@ -73,9 +73,6 @@ private void preprocessSecurityHook(HttpRequestContext requestContext) { // TODO: Create OutBoundHttp data here : Skipping for now. securityRequest.setContentType(MuleHelper.getContentType(securityRequest.getHeaders())); - - // TODO: need to update UserClassEntity - ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); securityRequest.setRequestParsed(true); } catch (Throwable ignored){} } @@ -85,6 +82,7 @@ private void postProcessSecurityHook() { if (!NewRelicSecurity.isHookProcessingActive()) { return; } + ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); ServletHelper.executeBeforeExitingTransaction(); //Add request URI hash to low severity event filter LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); diff --git a/instrumentation-security/mule-3.6/src/main/java/org/mule/processor/InvokerMessageProcessor_Instrumentation.java b/instrumentation-security/mule-3.6/src/main/java/org/mule/processor/InvokerMessageProcessor_Instrumentation.java index d497dc716..a19377b01 100644 --- a/instrumentation-security/mule-3.6/src/main/java/org/mule/processor/InvokerMessageProcessor_Instrumentation.java +++ b/instrumentation-security/mule-3.6/src/main/java/org/mule/processor/InvokerMessageProcessor_Instrumentation.java @@ -1,9 +1,13 @@ package org.mule.processor; import com.newrelic.agent.security.instrumentation.mule36.MuleHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.URLMappingsHelper; import com.newrelic.api.agent.weaver.MatchType; import com.newrelic.api.agent.weaver.Weave; import com.newrelic.api.agent.weaver.Weaver; +import org.mule.api.MuleEvent; +import org.mule.api.MuleException; import org.mule.api.lifecycle.InitialisationException; @Weave(originalName = "org.mule.processor.InvokerMessageProcessor", type = MatchType.ExactClass) @@ -18,4 +22,9 @@ public void initialise() throws InitialisationException { URLMappingsHelper.getHandlersHash().add(object.getClass().getName().hashCode()); } } + + public MuleEvent process(MuleEvent event) throws MuleException { + ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); + return Weaver.callOriginal(); + } } diff --git a/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java index dbd8ff353..099e8119a 100644 --- a/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java +++ b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java @@ -82,9 +82,6 @@ private static void preprocessSecurityHook(HttpRequestContext requestContext) { // TODO: Create OutBoundHttp data here : Skipping for now. securityRequest.setContentType(MuleHelper.getContentType(securityRequest.getHeaders())); - - // TODO: need to update UserClassEntity - ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); securityRequest.setRequestParsed(true); } catch (Throwable ignored){} } @@ -94,6 +91,7 @@ private static void postProcessSecurityHook() { if (!NewRelicSecurity.isHookProcessingActive()) { return; } + ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); ServletHelper.executeBeforeExitingTransaction(); //Add request URI hash to low severity event filter LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); diff --git a/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java index 4dd3ad108..166369320 100644 --- a/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java +++ b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java @@ -73,9 +73,6 @@ private void preprocessSecurityHook(HttpRequestContext requestContext) { // TODO: Create OutBoundHttp data here : Skipping for now. securityRequest.setContentType(MuleHelper.getContentType(securityRequest.getHeaders())); - - // TODO: need to update UserClassEntity - ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); securityRequest.setRequestParsed(true); } catch (Throwable ignored){} } @@ -85,6 +82,7 @@ private void postProcessSecurityHook() { if (!NewRelicSecurity.isHookProcessingActive()) { return; } + ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); ServletHelper.executeBeforeExitingTransaction(); //Add request URI hash to low severity event filter LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); diff --git a/instrumentation-security/mule-3.7/src/main/java/org/mule/processor/InvokerMessageProcessor_Instrumentation.java b/instrumentation-security/mule-3.7/src/main/java/org/mule/processor/InvokerMessageProcessor_Instrumentation.java index da294f74f..b52486147 100644 --- a/instrumentation-security/mule-3.7/src/main/java/org/mule/processor/InvokerMessageProcessor_Instrumentation.java +++ b/instrumentation-security/mule-3.7/src/main/java/org/mule/processor/InvokerMessageProcessor_Instrumentation.java @@ -1,9 +1,13 @@ package org.mule.processor; import com.newrelic.agent.security.instrumentation.mule37.MuleHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.URLMappingsHelper; import com.newrelic.api.agent.weaver.MatchType; import com.newrelic.api.agent.weaver.Weave; import com.newrelic.api.agent.weaver.Weaver; +import org.mule.api.MuleEvent; +import org.mule.api.MuleException; import org.mule.api.lifecycle.InitialisationException; @Weave(originalName = "org.mule.processor.InvokerMessageProcessor", type = MatchType.ExactClass) @@ -18,4 +22,9 @@ public void initialise() throws InitialisationException { URLMappingsHelper.getHandlersHash().add(object.getClass().getName().hashCode()); } } + + public MuleEvent process(MuleEvent event) throws MuleException { + ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); + return Weaver.callOriginal(); + } } From ef4a6f10f90fdbb16583d20c8414256395d967c6 Mon Sep 17 00:00:00 2001 From: idawda Date: Mon, 11 Nov 2024 17:33:53 +0530 Subject: [PATCH 08/23] Unit tests for http4s blaze 2.21 version support --- .../build.gradle | 1 + .../blaze/server/BlazeServerBuilderTest.scala | 118 ++++++++++++++++++ .../blaze/server/Http4sTestServer.scala | 36 ++++++ .../build.gradle | 1 + .../blaze/server/BlazeServerBuilderTest.scala | 118 ++++++++++++++++++ .../blaze/server/Http4sTestServer.scala | 36 ++++++ 6 files changed, 310 insertions(+) create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/build.gradle b/instrumentation-security/http4s-blaze-server-2.12_0.21/build.gradle index b5328c440..74acd27fd 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.21/build.gradle +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/build.gradle @@ -8,6 +8,7 @@ dependencies { implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") implementation("org.scala-lang:scala-library:2.12.14") implementation('org.http4s:http4s-blaze-server_2.12:0.21.24') + testImplementation("org.http4s:http4s-dsl_2.12:0.21.24") } jar { diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala new file mode 100644 index 000000000..883528c80 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -0,0 +1,118 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import cats.effect.IO +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.RXSSOperation +import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} +import org.http4s.HttpRoutes +import org.http4s.Method.GET +import org.http4s.dsl.io._ +import org.http4s.implicits._ +import org.junit.runner.RunWith +import org.junit.{After, Assert, Before, Test} + +import java.net.{HttpURLConnection, URL} +import java.util +import java.util.UUID + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.http4s.blaze.server")) +class EmberServerBuilderTest { + + val hostname = "0.0.0.0" + val port: Int = SecurityInstrumentationTestRunner.getIntrospector.getRandomPort + val contentType: String = "text/plain" + + val emberServer = new Http4sTestServer(hostname, port, + HttpRoutes.of[IO] { + case GET -> Root / "hello" / name => + Ok(s"Hello, $name.") + }.orNotFound) + + @Before + def setup(): Unit = { + emberServer.start() + } + + @After + def reset(): Unit = { + emberServer.stop() + } + + + @Test + def emberServerTest(): Unit = { + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = false, "") + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + } + + @Test + def emberServerHeaderTest(): Unit = { + val headerValue: String = String.valueOf(UUID.randomUUID()) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = true, headerValue) + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + assertCSECHeaders(headerValue, introspector.getSecurityMetaData.getRequest.getHeaders) + } + + private def assertCSECHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def assertRXSSOperation(operation: RXSSOperation): Unit = { + Assert.assertFalse("LowSeverityHook should be disabled", operation.isLowSeverityHook) + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType) + Assert.assertEquals("Invalid executed method name.", "withHttpApp", operation.getMethodName) + + Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + } + + private def assertMetaData(metaData: SecurityMetaData): Unit = { + Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + } +} + +object Http4sTestUtils { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + val u: URL = new URL(url) + val conn = u.openConnection.asInstanceOf[HttpURLConnection] + conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + if (addCSECHeader) { + conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") + conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") + conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) + } + conn.connect() + conn.getResponseCode + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala new file mode 100644 index 000000000..70cd5c070 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala @@ -0,0 +1,36 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import scala.concurrent.ExecutionContext.global +import cats.effect.{ConcurrentEffect, ContextShift, IO, Resource, Timer} +import org.http4s.HttpApp +import org.http4s.server.blaze.BlazeServerBuilder +import org.http4s.server.Server + +import scala.concurrent.ExecutionContext + +class Http4sTestServer(val testServerHost: String, val port: Int, val httpApp: HttpApp[IO]) { + + var server: Server[IO] = _ + var finalizer: IO[Unit] = _ + + implicit val cs: ContextShift[IO] = IO.contextShift(global) + implicit val timer: Timer[IO] = IO.timer(global) + implicit val concurrentEffect: ConcurrentEffect[IO] = IO.ioConcurrentEffect + + implicit val ec: ExecutionContext = global + + val serverResource: Resource[IO, Server[IO]] = BlazeServerBuilder.apply(global) + .withHttpApp(httpApp) + .bindHttp(port, testServerHost) + .resource + + def start(): Unit = { + val materializedServer = serverResource.allocated.unsafeRunSync() + server = materializedServer._1 + finalizer = materializedServer._2 + } + + def stop(): Unit = finalizer.unsafeRunSync() + + def hostname: String = server.address.getHostName +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/build.gradle b/instrumentation-security/http4s-blaze-server-2.13_0.21/build.gradle index 7f46b942e..0f291eb00 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.21/build.gradle +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/build.gradle @@ -8,6 +8,7 @@ dependencies { implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") implementation("org.scala-lang:scala-library:2.13.3") implementation('org.http4s:http4s-blaze-server_2.13:0.21.24') + testImplementation("org.http4s:http4s-dsl_2.13:0.21.24") } jar { diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala new file mode 100644 index 000000000..883528c80 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -0,0 +1,118 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import cats.effect.IO +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.RXSSOperation +import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} +import org.http4s.HttpRoutes +import org.http4s.Method.GET +import org.http4s.dsl.io._ +import org.http4s.implicits._ +import org.junit.runner.RunWith +import org.junit.{After, Assert, Before, Test} + +import java.net.{HttpURLConnection, URL} +import java.util +import java.util.UUID + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.http4s.blaze.server")) +class EmberServerBuilderTest { + + val hostname = "0.0.0.0" + val port: Int = SecurityInstrumentationTestRunner.getIntrospector.getRandomPort + val contentType: String = "text/plain" + + val emberServer = new Http4sTestServer(hostname, port, + HttpRoutes.of[IO] { + case GET -> Root / "hello" / name => + Ok(s"Hello, $name.") + }.orNotFound) + + @Before + def setup(): Unit = { + emberServer.start() + } + + @After + def reset(): Unit = { + emberServer.stop() + } + + + @Test + def emberServerTest(): Unit = { + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = false, "") + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + } + + @Test + def emberServerHeaderTest(): Unit = { + val headerValue: String = String.valueOf(UUID.randomUUID()) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = true, headerValue) + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + assertCSECHeaders(headerValue, introspector.getSecurityMetaData.getRequest.getHeaders) + } + + private def assertCSECHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def assertRXSSOperation(operation: RXSSOperation): Unit = { + Assert.assertFalse("LowSeverityHook should be disabled", operation.isLowSeverityHook) + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType) + Assert.assertEquals("Invalid executed method name.", "withHttpApp", operation.getMethodName) + + Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + } + + private def assertMetaData(metaData: SecurityMetaData): Unit = { + Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + } +} + +object Http4sTestUtils { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + val u: URL = new URL(url) + val conn = u.openConnection.asInstanceOf[HttpURLConnection] + conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + if (addCSECHeader) { + conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") + conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") + conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) + } + conn.connect() + conn.getResponseCode + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala new file mode 100644 index 000000000..70cd5c070 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala @@ -0,0 +1,36 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import scala.concurrent.ExecutionContext.global +import cats.effect.{ConcurrentEffect, ContextShift, IO, Resource, Timer} +import org.http4s.HttpApp +import org.http4s.server.blaze.BlazeServerBuilder +import org.http4s.server.Server + +import scala.concurrent.ExecutionContext + +class Http4sTestServer(val testServerHost: String, val port: Int, val httpApp: HttpApp[IO]) { + + var server: Server[IO] = _ + var finalizer: IO[Unit] = _ + + implicit val cs: ContextShift[IO] = IO.contextShift(global) + implicit val timer: Timer[IO] = IO.timer(global) + implicit val concurrentEffect: ConcurrentEffect[IO] = IO.ioConcurrentEffect + + implicit val ec: ExecutionContext = global + + val serverResource: Resource[IO, Server[IO]] = BlazeServerBuilder.apply(global) + .withHttpApp(httpApp) + .bindHttp(port, testServerHost) + .resource + + def start(): Unit = { + val materializedServer = serverResource.allocated.unsafeRunSync() + server = materializedServer._1 + finalizer = materializedServer._2 + } + + def stop(): Unit = finalizer.unsafeRunSync() + + def hostname: String = server.address.getHostName +} From 53b3284a0dcd0e650301baf4c820aad9f03ea0d7 Mon Sep 17 00:00:00 2001 From: idawda Date: Mon, 11 Nov 2024 17:36:15 +0530 Subject: [PATCH 09/23] Fix for duplicate RXSS events and code optimization in http4s blaze server --- .../http4s/blaze/server/BlazeUtils.java | 67 ------------------- .../blaze/server/RequestProcessor.scala | 59 ++++++++++------ .../http4s/blaze/server/BlazeUtils.java | 67 ------------------- .../blaze/server/RequestProcessor.scala | 54 +++++++++------ .../http4s/blaze/server/BlazeUtils.java | 67 ------------------- .../blaze/server/RequestProcessor.scala | 54 +++++++++------ .../http4s/blaze/server/BlazeUtils.java | 67 ------------------- .../blaze/server/RequestProcessor.scala | 60 +++++++++++------ .../http4s/blaze/server/BlazeUtils.java | 67 ------------------- .../blaze/server/RequestProcessor.scala | 55 +++++++++------ .../http4s/blaze/server/BlazeUtils.java | 67 ------------------- .../blaze/server/RequestProcessor.scala | 56 ++++++++++------ 12 files changed, 218 insertions(+), 522 deletions(-) delete mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java delete mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java delete mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java delete mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java delete mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java delete mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java deleted file mode 100644 index 262a24583..000000000 --- a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.newrelic.agent.security.http4s.blaze.server; - -import com.newrelic.api.agent.security.NewRelicSecurity; -import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; -import com.newrelic.api.agent.security.schema.StringUtils; - -import java.util.Map; - -public class BlazeUtils { - - public static String getContentType(Map headers) { - String contentType = StringUtils.EMPTY; - if (headers.containsKey("content-type")){ - contentType = headers.get("content-type"); - } - return contentType; - } - - 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; - } - - public static String getProtocol(boolean isSecure) { - if (isSecure) { - return "https"; - } - return "http"; - } - - - private static boolean isLockAcquired() { - try { - return NewRelicSecurity.isHookProcessingActive() && - Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); - } catch (Throwable ignored) {} - return false; - } - - public static boolean acquireLockIfPossible() { - try { - if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); - return true; - } - } catch (Throwable ignored){} - return false; - } - - public static void releaseLock() { - try { - if(NewRelicSecurity.isHookProcessingActive()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); - } - } catch (Throwable ignored){} - } - - private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); - } -} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index a4622dc6e..cf800774c 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -8,7 +8,6 @@ import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, I import com.newrelic.api.agent.security.schema._ 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 org.http4s.{Headers, Request, Response} @@ -29,16 +28,16 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - _ <- preprocessHttpRequest(request) + isLockAcquired <- preprocessHttpRequest(request) resp <- httpApp(request) - _ <- postProcessSecurityHook(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { - val isLockAcquired = BlazeUtils.acquireLockIfPossible() + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -48,8 +47,13 @@ object RequestProcessor { securityRequest.setMethod(request.method.name) securityRequest.setServerPort(request.serverPort.toInt) - securityRequest.setClientIP(request.remoteAddr.get) - securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + securityRequest.setClientIP(request.remoteAddr.get.toString) + + securityRequest.setProtocol("http") + if (request.isSecure.get) { + securityRequest.setProtocol("https") + } + securityRequest.setUrl(request.uri.toString) if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { @@ -58,8 +62,8 @@ object RequestProcessor { } processRequestHeaders(request.headers, securityRequest) - securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) - securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(getContentType(securityRequest.getHeaders)) // TODO extract request body & user class detection @@ -70,24 +74,27 @@ object RequestProcessor { } catch { case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) - } finally { - if (isLockAcquired) { - BlazeUtils.releaseLock() - } } + isLockAcquired + } + + private def getContentType(headers: util.Map[String, String]): String = { + var contentType = StringUtils.EMPTY + if (headers.containsKey("content-type")) contentType = headers.get("content-type") + contentType } private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { headers.foreach(header => { var takeNextValue = false - var headerKey: String = StringUtils.EMPTY - if (header.name != null && header.name.isEmpty) { + var headerKey = StringUtils.EMPTY + if (header.name != null && !header.name.isEmpty) { headerKey = header.name.toString } - val headerValue: String = header.value + val headerValue = header.value - val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy - val agentMetaData: AgentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData + val agentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val agentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData if (agentPolicy != null && agentPolicy.getProtectionMode.getEnabled() && agentPolicy.getProtectionMode.getIpBlocking.getEnabled() @@ -118,15 +125,16 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) - securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) // TODO extract response body + ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) @@ -152,5 +160,14 @@ object RequestProcessor { }) } + private def getTraceHeader(headers: util.Map[String, String]): String = { + var 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) + } + data + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java deleted file mode 100644 index 262a24583..000000000 --- a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.newrelic.agent.security.http4s.blaze.server; - -import com.newrelic.api.agent.security.NewRelicSecurity; -import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; -import com.newrelic.api.agent.security.schema.StringUtils; - -import java.util.Map; - -public class BlazeUtils { - - public static String getContentType(Map headers) { - String contentType = StringUtils.EMPTY; - if (headers.containsKey("content-type")){ - contentType = headers.get("content-type"); - } - return contentType; - } - - 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; - } - - public static String getProtocol(boolean isSecure) { - if (isSecure) { - return "https"; - } - return "http"; - } - - - private static boolean isLockAcquired() { - try { - return NewRelicSecurity.isHookProcessingActive() && - Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); - } catch (Throwable ignored) {} - return false; - } - - public static boolean acquireLockIfPossible() { - try { - if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); - return true; - } - } catch (Throwable ignored){} - return false; - } - - public static void releaseLock() { - try { - if(NewRelicSecurity.isHookProcessingActive()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); - } - } catch (Throwable ignored){} - } - - private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); - } -} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 4a333e8a4..ab091964c 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -9,7 +9,6 @@ import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, I import com.newrelic.api.agent.security.schema._ 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 org.http4s.{Headers, Request, Response} @@ -30,16 +29,16 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - _ <- preprocessHttpRequest(request) + isLockAcquired <- preprocessHttpRequest(request) resp <- httpApp(request) - _ <- postProcessSecurityHook(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { - val isLockAcquired = BlazeUtils.acquireLockIfPossible() + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -50,7 +49,12 @@ object RequestProcessor { securityRequest.setMethod(request.method.name) securityRequest.setServerPort((request.serverPort).get.asInstanceOf[Port].value) securityRequest.setClientIP(request.remoteAddr.get.toString) - securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + + securityRequest.setProtocol("http") + if (request.isSecure.get) { + securityRequest.setProtocol("https") + } + securityRequest.setUrl(request.uri.toString) if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { @@ -59,8 +63,8 @@ object RequestProcessor { } processRequestHeaders(request.headers, securityRequest) - securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) - securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(getContentType(securityRequest.getHeaders)) // TODO extract request body & user class detection @@ -71,24 +75,27 @@ object RequestProcessor { } catch { case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) - } finally { - if (isLockAcquired) { - BlazeUtils.releaseLock() - } } + isLockAcquired + } + + private def getContentType(headers: util.Map[String, String]): String = { + var contentType = StringUtils.EMPTY + if (headers.containsKey("content-type")) contentType = headers.get("content-type") + contentType } private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { headers.foreach(header => { var takeNextValue = false - var headerKey: String = StringUtils.EMPTY + var headerKey = StringUtils.EMPTY if (header.name != null && header.name.nonEmpty) { headerKey = header.name.toString } - val headerValue: String = header.value + val headerValue = header.value - val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy - val agentMetaData: AgentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData + val agentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val agentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData if (agentPolicy != null && agentPolicy.getProtectionMode.getEnabled() && agentPolicy.getProtectionMode.getIpBlocking.getEnabled() @@ -119,13 +126,13 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) - securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) // TODO extract response body @@ -154,5 +161,14 @@ object RequestProcessor { }) } + private def getTraceHeader(headers: util.Map[String, String]): String = { + var 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) + } + data + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java deleted file mode 100644 index 262a24583..000000000 --- a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.newrelic.agent.security.http4s.blaze.server; - -import com.newrelic.api.agent.security.NewRelicSecurity; -import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; -import com.newrelic.api.agent.security.schema.StringUtils; - -import java.util.Map; - -public class BlazeUtils { - - public static String getContentType(Map headers) { - String contentType = StringUtils.EMPTY; - if (headers.containsKey("content-type")){ - contentType = headers.get("content-type"); - } - return contentType; - } - - 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; - } - - public static String getProtocol(boolean isSecure) { - if (isSecure) { - return "https"; - } - return "http"; - } - - - private static boolean isLockAcquired() { - try { - return NewRelicSecurity.isHookProcessingActive() && - Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); - } catch (Throwable ignored) {} - return false; - } - - public static boolean acquireLockIfPossible() { - try { - if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); - return true; - } - } catch (Throwable ignored){} - return false; - } - - public static void releaseLock() { - try { - if(NewRelicSecurity.isHookProcessingActive()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); - } - } catch (Throwable ignored){} - } - - private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); - } -} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index e028790b4..fbbb71849 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -9,7 +9,6 @@ import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, I import com.newrelic.api.agent.security.schema._ 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 org.http4s.{Headers, Request, Response} @@ -30,16 +29,16 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - _ <- preprocessHttpRequest(request) + isLockAcquired <- preprocessHttpRequest(request) resp <- httpApp(request) - _ <- postProcessSecurityHook(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { - val isLockAcquired = BlazeUtils.acquireLockIfPossible() + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -50,7 +49,12 @@ object RequestProcessor { securityRequest.setMethod(request.method.name) securityRequest.setServerPort((request.serverPort).get.asInstanceOf[Port].value) securityRequest.setClientIP(request.remoteAddr.get.toString) - securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + + securityRequest.setProtocol("http") + if (request.isSecure.get) { + securityRequest.setProtocol("https") + } + securityRequest.setUrl(request.uri.toString) if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { @@ -59,8 +63,8 @@ object RequestProcessor { } processRequestHeaders(request.headers, securityRequest) - securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) - securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(getContentType(securityRequest.getHeaders)) // TODO extract request body & user class detection @@ -71,24 +75,27 @@ object RequestProcessor { } catch { case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) - } finally { - if (isLockAcquired) { - BlazeUtils.releaseLock() - } } + isLockAcquired + } + + private def getContentType(headers: util.Map[String, String]): String = { + var contentType = StringUtils.EMPTY + if (headers.containsKey("content-type")) contentType = headers.get("content-type") + contentType } private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { headers.foreach(header => { var takeNextValue = false - var headerKey: String = StringUtils.EMPTY + var headerKey = StringUtils.EMPTY if (header.name != null && header.name.nonEmpty) { headerKey = header.name.toString } - val headerValue: String = header.value + val headerValue = header.value - val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy - val agentMetaData: AgentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData + val agentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val agentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData if (agentPolicy != null && agentPolicy.getProtectionMode.getEnabled() && agentPolicy.getProtectionMode.getIpBlocking.getEnabled() @@ -119,13 +126,13 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) - securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) // TODO extract response body @@ -154,5 +161,14 @@ object RequestProcessor { }) } + private def getTraceHeader(headers: util.Map[String, String]): String = { + var 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) + } + data + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java deleted file mode 100644 index 262a24583..000000000 --- a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.newrelic.agent.security.http4s.blaze.server; - -import com.newrelic.api.agent.security.NewRelicSecurity; -import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; -import com.newrelic.api.agent.security.schema.StringUtils; - -import java.util.Map; - -public class BlazeUtils { - - public static String getContentType(Map headers) { - String contentType = StringUtils.EMPTY; - if (headers.containsKey("content-type")){ - contentType = headers.get("content-type"); - } - return contentType; - } - - 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; - } - - public static String getProtocol(boolean isSecure) { - if (isSecure) { - return "https"; - } - return "http"; - } - - - private static boolean isLockAcquired() { - try { - return NewRelicSecurity.isHookProcessingActive() && - Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); - } catch (Throwable ignored) {} - return false; - } - - public static boolean acquireLockIfPossible() { - try { - if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); - return true; - } - } catch (Throwable ignored){} - return false; - } - - public static void releaseLock() { - try { - if(NewRelicSecurity.isHookProcessingActive()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); - } - } catch (Throwable ignored){} - } - - private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); - } -} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 74b226dc5..70443d5fd 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -8,7 +8,6 @@ import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, I import com.newrelic.api.agent.security.schema._ 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 org.http4s.{Headers, Request, Response} @@ -29,16 +28,16 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - _ <- preprocessHttpRequest(request) + isLockAcquired <- preprocessHttpRequest(request) resp <- httpApp(request) - _ <- postProcessSecurityHook(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { - val isLockAcquired = BlazeUtils.acquireLockIfPossible() + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -47,9 +46,14 @@ object RequestProcessor { val securityAgentMetaData: AgentMetaData = securityMetaData.getMetaData securityRequest.setMethod(request.method.name) - securityRequest.setServerPort((request.serverPort).longValue().toInt) - securityRequest.setClientIP(request.remoteAddr.get) - securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + securityRequest.setServerPort(request.serverPort.toInt) + securityRequest.setClientIP(request.remoteAddr.get.toString) + + securityRequest.setProtocol("http") + if (request.isSecure.get) { + securityRequest.setProtocol("https") + } + securityRequest.setUrl(request.uri.toString) if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { @@ -58,8 +62,8 @@ object RequestProcessor { } processRequestHeaders(request.headers, securityRequest) - securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) - securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(getContentType(securityRequest.getHeaders)) // TODO extract request body & user class detection @@ -70,24 +74,27 @@ object RequestProcessor { } catch { case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) - } finally { - if (isLockAcquired) { - BlazeUtils.releaseLock() - } } + isLockAcquired + } + + private def getContentType(headers: util.Map[String, String]): String = { + var contentType = StringUtils.EMPTY + if (headers.containsKey("content-type")) contentType = headers.get("content-type") + contentType } private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { headers.foreach(header => { var takeNextValue = false - var headerKey: String = StringUtils.EMPTY - if (header.name != null && header.name.isEmpty) { + var headerKey = StringUtils.EMPTY + if (header.name != null && !header.name.isEmpty) { headerKey = header.name.toString } - val headerValue: String = header.value + val headerValue = header.value - val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy - val agentMetaData: AgentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData + val agentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val agentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData if (agentPolicy != null && agentPolicy.getProtectionMode.getEnabled() && agentPolicy.getProtectionMode.getIpBlocking.getEnabled() @@ -118,13 +125,13 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) - securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) // TODO extract response body @@ -153,5 +160,14 @@ object RequestProcessor { }) } + private def getTraceHeader(headers: util.Map[String, String]): String = { + var 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) + } + data + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java deleted file mode 100644 index 262a24583..000000000 --- a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.newrelic.agent.security.http4s.blaze.server; - -import com.newrelic.api.agent.security.NewRelicSecurity; -import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; -import com.newrelic.api.agent.security.schema.StringUtils; - -import java.util.Map; - -public class BlazeUtils { - - public static String getContentType(Map headers) { - String contentType = StringUtils.EMPTY; - if (headers.containsKey("content-type")){ - contentType = headers.get("content-type"); - } - return contentType; - } - - 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; - } - - public static String getProtocol(boolean isSecure) { - if (isSecure) { - return "https"; - } - return "http"; - } - - - private static boolean isLockAcquired() { - try { - return NewRelicSecurity.isHookProcessingActive() && - Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); - } catch (Throwable ignored) {} - return false; - } - - public static boolean acquireLockIfPossible() { - try { - if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); - return true; - } - } catch (Throwable ignored){} - return false; - } - - public static void releaseLock() { - try { - if(NewRelicSecurity.isHookProcessingActive()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); - } - } catch (Throwable ignored){} - } - - private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); - } -} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 7bf9ffcce..d19d2aa49 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -9,7 +9,6 @@ import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, I import com.newrelic.api.agent.security.schema._ 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 org.http4s.{Headers, Request, Response} @@ -30,16 +29,16 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - _ <- preprocessHttpRequest(request) + isLockAcquired <- preprocessHttpRequest(request) resp <- httpApp(request) - _ <- postProcessSecurityHook(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { - val isLockAcquired = BlazeUtils.acquireLockIfPossible() + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -50,7 +49,12 @@ object RequestProcessor { securityRequest.setMethod(request.method.name) securityRequest.setServerPort((request.serverPort).get.asInstanceOf[Port].value) securityRequest.setClientIP(request.remoteAddr.get.toString) - securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + + securityRequest.setProtocol("http") + if (request.isSecure.get) { + securityRequest.setProtocol("https") + } + securityRequest.setUrl(request.uri.toString) if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { @@ -59,8 +63,8 @@ object RequestProcessor { } processRequestHeaders(request.headers, securityRequest) - securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) - securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(getContentType(securityRequest.getHeaders)) // TODO extract request body & user class detection @@ -71,24 +75,27 @@ object RequestProcessor { } catch { case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) - } finally { - if (isLockAcquired) { - BlazeUtils.releaseLock() - } } + isLockAcquired + } + + private def getContentType(headers: util.Map[String, String]): String = { + var contentType = StringUtils.EMPTY + if (headers.containsKey("content-type")) contentType = headers.get("content-type") + contentType } private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { headers.foreach(header => { var takeNextValue = false - var headerKey: String = StringUtils.EMPTY + var headerKey = StringUtils.EMPTY if (header.name != null && header.name.nonEmpty) { headerKey = header.name.toString } - val headerValue: String = header.value + val headerValue = header.value - val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy - val agentMetaData: AgentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData + val agentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val agentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData if (agentPolicy != null && agentPolicy.getProtectionMode.getEnabled() && agentPolicy.getProtectionMode.getIpBlocking.getEnabled() @@ -118,13 +125,14 @@ object RequestProcessor { securityRequest.getHeaders.put(headerKey.toLowerCase, headerValue) }) } - private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) - securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) // TODO extract response body @@ -153,5 +161,14 @@ object RequestProcessor { }) } + private def getTraceHeader(headers: util.Map[String, String]): String = { + var 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) + } + data + } + private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java deleted file mode 100644 index 262a24583..000000000 --- a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/BlazeUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.newrelic.agent.security.http4s.blaze.server; - -import com.newrelic.api.agent.security.NewRelicSecurity; -import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; -import com.newrelic.api.agent.security.schema.StringUtils; - -import java.util.Map; - -public class BlazeUtils { - - public static String getContentType(Map headers) { - String contentType = StringUtils.EMPTY; - if (headers.containsKey("content-type")){ - contentType = headers.get("content-type"); - } - return contentType; - } - - 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; - } - - public static String getProtocol(boolean isSecure) { - if (isSecure) { - return "https"; - } - return "http"; - } - - - private static boolean isLockAcquired() { - try { - return NewRelicSecurity.isHookProcessingActive() && - Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class)); - } catch (Throwable ignored) {} - return false; - } - - public static boolean acquireLockIfPossible() { - try { - if (NewRelicSecurity.isHookProcessingActive() && !isLockAcquired()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true); - return true; - } - } catch (Throwable ignored){} - return false; - } - - public static void releaseLock() { - try { - if(NewRelicSecurity.isHookProcessingActive()) { - NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null); - } - } catch (Throwable ignored){} - } - - private static String getNrSecCustomAttribName() { - return "HTTP4S-EMBER-REQUEST_LOCK" + Thread.currentThread().getId(); - } -} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 482f04462..e502cb9e4 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -9,7 +9,6 @@ import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, I import com.newrelic.api.agent.security.schema._ 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 org.http4s.{Headers, Request, Response} @@ -30,16 +29,16 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - _ <- preprocessHttpRequest(request) + isLockAcquired <- preprocessHttpRequest(request) resp <- httpApp(request) - _ <- postProcessSecurityHook(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Unit] = construct { - val isLockAcquired = BlazeUtils.acquireLockIfPossible() + private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -50,7 +49,12 @@ object RequestProcessor { securityRequest.setMethod(request.method.name) securityRequest.setServerPort((request.serverPort).get.asInstanceOf[Port].value) securityRequest.setClientIP(request.remoteAddr.get.toString) - securityRequest.setProtocol(BlazeUtils.getProtocol(request.isSecure.get)) + + securityRequest.setProtocol("http") + if (request.isSecure.get) { + securityRequest.setProtocol("https") + } + securityRequest.setUrl(request.uri.toString) if (securityRequest.getClientIP != null && securityRequest.getClientIP.trim.nonEmpty) { @@ -59,8 +63,8 @@ object RequestProcessor { } processRequestHeaders(request.headers, securityRequest) - securityMetaData.setTracingHeaderValue(BlazeUtils.getTraceHeader(securityRequest.getHeaders)) - securityRequest.setContentType(BlazeUtils.getContentType(securityRequest.getHeaders)) + securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) + securityRequest.setContentType(getContentType(securityRequest.getHeaders)) // TODO extract request body & user class detection @@ -71,24 +75,27 @@ object RequestProcessor { } catch { case e: Throwable => NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, HTTP_4S_EMBER_SERVER_2_12_0_23, e.getMessage), e, this.getClass.getName) - } finally { - if (isLockAcquired) { - BlazeUtils.releaseLock() - } } + isLockAcquired + } + + private def getContentType(headers: util.Map[String, String]): String = { + var contentType = StringUtils.EMPTY + if (headers.containsKey("content-type")) contentType = headers.get("content-type") + contentType } private def processRequestHeaders(headers: Headers, securityRequest: HttpRequest): Unit = { headers.foreach(header => { var takeNextValue = false - var headerKey: String = StringUtils.EMPTY + var headerKey = StringUtils.EMPTY if (header.name != null && header.name.nonEmpty) { headerKey = header.name.toString } - val headerValue: String = header.value + val headerValue = header.value - val agentPolicy: AgentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy - val agentMetaData: AgentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData + val agentPolicy = NewRelicSecurity.getAgent.getCurrentPolicy + val agentMetaData = NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData if (agentPolicy != null && agentPolicy.getProtectionMode.getEnabled() && agentPolicy.getProtectionMode.getIpBlocking.getEnabled() @@ -119,13 +126,13 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](response: Response[F]): F[Unit] = construct { + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) - securityResponse.setResponseContentType(BlazeUtils.getContentType(securityResponse.getHeaders)) + securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) // TODO extract response body @@ -154,5 +161,14 @@ object RequestProcessor { }) } - private def construct[F[_]: Sync, T](t: => T): F[T] = Sync[F].delay(t) + private def getTraceHeader(headers: util.Map[String, String]): String = { + var 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) + } + data + } + + private def construct[F[_] : Sync, T](t: => T): F[T] = Sync[F].delay(t) } From 747acf72bf0bb04de468b20e598d6db1891154ea Mon Sep 17 00:00:00 2001 From: idawda Date: Tue, 12 Nov 2024 18:14:38 +0530 Subject: [PATCH 10/23] NR-333392: Adds Unit tests for http4s blaze server instrumentation support --- .../build.gradle | 1 + .../blaze/server/BlazeServerBuilderTest.scala | 118 ++++++++++++++++++ .../blaze/server/Http4sTestServer.scala | 36 ++++++ .../build.gradle | 1 + .../blaze/server/BlazeServerBuilderTest.scala | 118 ++++++++++++++++++ .../blaze/server/Http4sTestServer.scala | 28 +++++ .../build.gradle | 1 + .../blaze/server/BlazeServerBuilderTest.scala | 118 ++++++++++++++++++ .../blaze/server/Http4sTestServer.scala | 36 ++++++ .../build.gradle | 1 + .../blaze/server/BlazeServerBuilderTest.scala | 118 ++++++++++++++++++ .../blaze/server/Http4sTestServer.scala | 28 +++++ 12 files changed, 604 insertions(+) create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala create mode 100644 instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/build.gradle b/instrumentation-security/http4s-blaze-server-2.12_0.22/build.gradle index e33254e41..0cbffcfaa 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.22/build.gradle +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/build.gradle @@ -8,6 +8,7 @@ dependencies { implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") implementation("org.scala-lang:scala-library:2.12.14") implementation('org.http4s:http4s-blaze-server_2.12:0.22.14') + testImplementation("org.http4s:http4s-dsl_2.12:0.22.14") } jar { diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala new file mode 100644 index 000000000..ad5f3736d --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -0,0 +1,118 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import cats.effect.IO +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.RXSSOperation +import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} +import org.http4s.HttpRoutes +import org.http4s.Method.GET +import org.http4s.dsl.io._ +import org.http4s.implicits._ +import org.junit.runner.RunWith +import org.junit.{After, Assert, Before, Test} + +import java.net.{HttpURLConnection, URL} +import java.util +import java.util.UUID + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.http4s.blaze.server", "scala")) +class EmberServerBuilderTest { + + val hostname = "0.0.0.0" + val port: Int = SecurityInstrumentationTestRunner.getIntrospector.getRandomPort + val contentType: String = "text/plain" + + val emberServer = new Http4sTestServer(hostname, port, + HttpRoutes.of[IO] { + case GET -> Root / "hello" / name => + Ok(s"Hello, $name.") + }.orNotFound) + + @Before + def setup(): Unit = { + emberServer.start() + } + + @After + def reset(): Unit = { + emberServer.stop() + } + + + @Test + def emberServerTest(): Unit = { + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = false, "") + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + } + + @Test + def emberServerHeaderTest(): Unit = { + val headerValue: String = String.valueOf(UUID.randomUUID()) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = true, headerValue) + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + assertCSECHeaders(headerValue, introspector.getSecurityMetaData.getRequest.getHeaders) + } + + private def assertCSECHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def assertRXSSOperation(operation: RXSSOperation): Unit = { + Assert.assertFalse("LowSeverityHook should be disabled", operation.isLowSeverityHook) + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType) + Assert.assertEquals("Invalid executed method name.", "withHttpApp", operation.getMethodName) + + Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + } + + private def assertMetaData(metaData: SecurityMetaData): Unit = { + Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + } +} + +object Http4sTestUtils { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + val u: URL = new URL(url) + val conn = u.openConnection.asInstanceOf[HttpURLConnection] + conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + if (addCSECHeader) { + conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") + conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") + conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) + } + conn.connect() + conn.getResponseCode + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala new file mode 100644 index 000000000..479ba05f4 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala @@ -0,0 +1,36 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import scala.concurrent.ExecutionContext.global +import cats.effect.{ConcurrentEffect, ContextShift, IO, Resource, Timer} +import org.http4s.HttpApp +import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.server.Server + +import scala.concurrent.ExecutionContext + +class Http4sTestServer(val testServerHost: String, val port: Int, val httpApp: HttpApp[IO]) { + + var server: Server = _ + var finalizer: IO[Unit] = _ + + implicit val cs: ContextShift[IO] = IO.contextShift(global) + implicit val timer: Timer[IO] = IO.timer(global) + implicit val concurrentEffect: ConcurrentEffect[IO] = IO.ioConcurrentEffect + + implicit val ec: ExecutionContext = global + + val serverResource: Resource[IO, Server] = BlazeServerBuilder.apply(global) + .withHttpApp(httpApp) + .bindHttp(port, testServerHost) + .resource + + def start(): Unit = { + val materializedServer = serverResource.allocated.unsafeRunSync() + server = materializedServer._1 + finalizer = materializedServer._2 + } + + def stop(): Unit = finalizer.unsafeRunSync() + + def hostname: String = server.address.getHostName +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/build.gradle b/instrumentation-security/http4s-blaze-server-2.12_0.23/build.gradle index a824a1833..1c12651fb 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.23/build.gradle +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation("org.scala-lang:scala-library:2.12.14") implementation('org.http4s:http4s-blaze-server_2.12:0.23.12') implementation("org.typelevel:cats-effect_2.12:3.3.12") + testImplementation("org.http4s:http4s-dsl_2.12:0.23.12") } jar { diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala new file mode 100644 index 000000000..ad5f3736d --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -0,0 +1,118 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import cats.effect.IO +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.RXSSOperation +import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} +import org.http4s.HttpRoutes +import org.http4s.Method.GET +import org.http4s.dsl.io._ +import org.http4s.implicits._ +import org.junit.runner.RunWith +import org.junit.{After, Assert, Before, Test} + +import java.net.{HttpURLConnection, URL} +import java.util +import java.util.UUID + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.http4s.blaze.server", "scala")) +class EmberServerBuilderTest { + + val hostname = "0.0.0.0" + val port: Int = SecurityInstrumentationTestRunner.getIntrospector.getRandomPort + val contentType: String = "text/plain" + + val emberServer = new Http4sTestServer(hostname, port, + HttpRoutes.of[IO] { + case GET -> Root / "hello" / name => + Ok(s"Hello, $name.") + }.orNotFound) + + @Before + def setup(): Unit = { + emberServer.start() + } + + @After + def reset(): Unit = { + emberServer.stop() + } + + + @Test + def emberServerTest(): Unit = { + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = false, "") + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + } + + @Test + def emberServerHeaderTest(): Unit = { + val headerValue: String = String.valueOf(UUID.randomUUID()) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = true, headerValue) + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + assertCSECHeaders(headerValue, introspector.getSecurityMetaData.getRequest.getHeaders) + } + + private def assertCSECHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def assertRXSSOperation(operation: RXSSOperation): Unit = { + Assert.assertFalse("LowSeverityHook should be disabled", operation.isLowSeverityHook) + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType) + Assert.assertEquals("Invalid executed method name.", "withHttpApp", operation.getMethodName) + + Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + } + + private def assertMetaData(metaData: SecurityMetaData): Unit = { + Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + } +} + +object Http4sTestUtils { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + val u: URL = new URL(url) + val conn = u.openConnection.asInstanceOf[HttpURLConnection] + conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + if (addCSECHeader) { + conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") + conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") + conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) + } + conn.connect() + conn.getResponseCode + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala new file mode 100644 index 000000000..182bec092 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala @@ -0,0 +1,28 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import cats.effect.unsafe.implicits.global +import cats.effect.{IO, Resource} +import org.http4s.HttpApp +import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.server.Server + +class Http4sTestServer(val testServerHost: String, val port: Int, val httpApp: HttpApp[IO]) { + + var server: Server = _ + var finalizer: IO[Unit] = _ + + val serverResource: Resource[IO, Server] = BlazeServerBuilder[IO] + .withHttpApp(httpApp) + .bindHttp(port, testServerHost) + .resource + + def start(): Unit = { + val materializedServer = serverResource.allocated.unsafeRunSync() + server = materializedServer._1 + finalizer = materializedServer._2 + } + + def stop(): Unit = finalizer.unsafeRunSync() + + def hostname: String = server.address.getHostName +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/build.gradle b/instrumentation-security/http4s-blaze-server-2.13_0.22/build.gradle index 274eea2fe..eecd9a894 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.22/build.gradle +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/build.gradle @@ -8,6 +8,7 @@ dependencies { implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") implementation("org.scala-lang:scala-library:2.13.3") implementation('org.http4s:http4s-blaze-server_2.13:0.22.14') + testImplementation("org.http4s:http4s-dsl_2.13:0.22.12") } jar { diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala new file mode 100644 index 000000000..883528c80 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -0,0 +1,118 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import cats.effect.IO +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.RXSSOperation +import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} +import org.http4s.HttpRoutes +import org.http4s.Method.GET +import org.http4s.dsl.io._ +import org.http4s.implicits._ +import org.junit.runner.RunWith +import org.junit.{After, Assert, Before, Test} + +import java.net.{HttpURLConnection, URL} +import java.util +import java.util.UUID + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.http4s.blaze.server")) +class EmberServerBuilderTest { + + val hostname = "0.0.0.0" + val port: Int = SecurityInstrumentationTestRunner.getIntrospector.getRandomPort + val contentType: String = "text/plain" + + val emberServer = new Http4sTestServer(hostname, port, + HttpRoutes.of[IO] { + case GET -> Root / "hello" / name => + Ok(s"Hello, $name.") + }.orNotFound) + + @Before + def setup(): Unit = { + emberServer.start() + } + + @After + def reset(): Unit = { + emberServer.stop() + } + + + @Test + def emberServerTest(): Unit = { + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = false, "") + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + } + + @Test + def emberServerHeaderTest(): Unit = { + val headerValue: String = String.valueOf(UUID.randomUUID()) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = true, headerValue) + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + assertCSECHeaders(headerValue, introspector.getSecurityMetaData.getRequest.getHeaders) + } + + private def assertCSECHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def assertRXSSOperation(operation: RXSSOperation): Unit = { + Assert.assertFalse("LowSeverityHook should be disabled", operation.isLowSeverityHook) + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType) + Assert.assertEquals("Invalid executed method name.", "withHttpApp", operation.getMethodName) + + Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + } + + private def assertMetaData(metaData: SecurityMetaData): Unit = { + Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + } +} + +object Http4sTestUtils { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + val u: URL = new URL(url) + val conn = u.openConnection.asInstanceOf[HttpURLConnection] + conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + if (addCSECHeader) { + conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") + conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") + conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) + } + conn.connect() + conn.getResponseCode + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala new file mode 100644 index 000000000..479ba05f4 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala @@ -0,0 +1,36 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import scala.concurrent.ExecutionContext.global +import cats.effect.{ConcurrentEffect, ContextShift, IO, Resource, Timer} +import org.http4s.HttpApp +import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.server.Server + +import scala.concurrent.ExecutionContext + +class Http4sTestServer(val testServerHost: String, val port: Int, val httpApp: HttpApp[IO]) { + + var server: Server = _ + var finalizer: IO[Unit] = _ + + implicit val cs: ContextShift[IO] = IO.contextShift(global) + implicit val timer: Timer[IO] = IO.timer(global) + implicit val concurrentEffect: ConcurrentEffect[IO] = IO.ioConcurrentEffect + + implicit val ec: ExecutionContext = global + + val serverResource: Resource[IO, Server] = BlazeServerBuilder.apply(global) + .withHttpApp(httpApp) + .bindHttp(port, testServerHost) + .resource + + def start(): Unit = { + val materializedServer = serverResource.allocated.unsafeRunSync() + server = materializedServer._1 + finalizer = materializedServer._2 + } + + def stop(): Unit = finalizer.unsafeRunSync() + + def hostname: String = server.address.getHostName +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/build.gradle b/instrumentation-security/http4s-blaze-server-2.13_0.23/build.gradle index 8db9c99d9..1dadbfc1e 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.23/build.gradle +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation("org.scala-lang:scala-library:2.13.3") implementation('org.http4s:http4s-blaze-server_2.13:0.23.12') implementation("org.typelevel:cats-effect_2.13:3.3.12") + testImplementation("org.http4s:http4s-dsl_2.13:0.23.12") } jar { diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala new file mode 100644 index 000000000..ad5f3736d --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -0,0 +1,118 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import cats.effect.IO +import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, SecurityInstrumentationTestRunner, SecurityIntrospector} +import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} +import com.newrelic.api.agent.security.schema.operation.RXSSOperation +import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} +import org.http4s.HttpRoutes +import org.http4s.Method.GET +import org.http4s.dsl.io._ +import org.http4s.implicits._ +import org.junit.runner.RunWith +import org.junit.{After, Assert, Before, Test} + +import java.net.{HttpURLConnection, URL} +import java.util +import java.util.UUID + +@RunWith(classOf[SecurityInstrumentationTestRunner]) +@InstrumentationTestConfig(includePrefixes = Array("org.http4s", "com.newrelic.agent.security.http4s.blaze.server", "scala")) +class EmberServerBuilderTest { + + val hostname = "0.0.0.0" + val port: Int = SecurityInstrumentationTestRunner.getIntrospector.getRandomPort + val contentType: String = "text/plain" + + val emberServer = new Http4sTestServer(hostname, port, + HttpRoutes.of[IO] { + case GET -> Root / "hello" / name => + Ok(s"Hello, $name.") + }.orNotFound) + + @Before + def setup(): Unit = { + emberServer.start() + } + + @After + def reset(): Unit = { + emberServer.stop() + } + + + @Test + def emberServerTest(): Unit = { + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = false, "") + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + } + + @Test + def emberServerHeaderTest(): Unit = { + val headerValue: String = String.valueOf(UUID.randomUUID()) + + val introspector: SecurityIntrospector = SecurityInstrumentationTestRunner.getIntrospector + Http4sTestUtils.makeRequest(s"http://$hostname:$port/hello/bob", addCSECHeader = true, headerValue) + + val operations = introspector.getOperations + Assert.assertTrue(operations.size() > 0) + Assert.assertTrue(operations.get(0).isInstanceOf[RXSSOperation]) + + assertRXSSOperation(operations.get(0).asInstanceOf[RXSSOperation]) + assertMetaData(introspector.getSecurityMetaData) + assertCSECHeaders(headerValue, introspector.getSecurityMetaData.getRequest.getHeaders) + } + + private def assertCSECHeaders(headerValue: String, headers: util.Map[String, String]): Unit = { + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headers.containsKey(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID), headerValue + "a", headers.get(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", GenericHelper.CSEC_PARENT_ID), headers.containsKey(GenericHelper.CSEC_PARENT_ID)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", GenericHelper.CSEC_PARENT_ID), headerValue + "b", headers.get(GenericHelper.CSEC_PARENT_ID)) + Assert.assertTrue(String.format("Missing CSEC header: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + Assert.assertEquals(String.format("Invalid CSEC header value for: %s", ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER), String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue), headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase)) + } + + private def assertRXSSOperation(operation: RXSSOperation): Unit = { + Assert.assertFalse("LowSeverityHook should be disabled", operation.isLowSeverityHook) + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.REFLECTED_XSS, operation.getCaseType) + Assert.assertEquals("Invalid executed method name.", "withHttpApp", operation.getMethodName) + + Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + } + + private def assertMetaData(metaData: SecurityMetaData): Unit = { + Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) + Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) + Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) + + // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + } +} + +object Http4sTestUtils { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + val u: URL = new URL(url) + val conn = u.openConnection.asInstanceOf[HttpURLConnection] + conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + if (addCSECHeader) { + conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") + conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") + conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) + } + conn.connect() + conn.getResponseCode + } +} diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala new file mode 100644 index 000000000..182bec092 --- /dev/null +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/Http4sTestServer.scala @@ -0,0 +1,28 @@ +package com.nr.agent.security.instrumentation.blaze.server + +import cats.effect.unsafe.implicits.global +import cats.effect.{IO, Resource} +import org.http4s.HttpApp +import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.server.Server + +class Http4sTestServer(val testServerHost: String, val port: Int, val httpApp: HttpApp[IO]) { + + var server: Server = _ + var finalizer: IO[Unit] = _ + + val serverResource: Resource[IO, Server] = BlazeServerBuilder[IO] + .withHttpApp(httpApp) + .bindHttp(port, testServerHost) + .resource + + def start(): Unit = { + val materializedServer = serverResource.allocated.unsafeRunSync() + server = materializedServer._1 + finalizer = materializedServer._2 + } + + def stop(): Unit = finalizer.unsafeRunSync() + + def hostname: String = server.address.getHostName +} From e22afddd80a33565afe1d2b4272b7d370e631949 Mon Sep 17 00:00:00 2001 From: idawda Date: Tue, 26 Nov 2024 16:46:03 +0530 Subject: [PATCH 11/23] NR-293846: Extracting request and response body in http4s-blaze server --- .../blaze/server/RequestProcessor.scala | 30 +++++++++++------- .../blaze/server/RequestProcessor.scala | 31 ++++++++++++------- .../blaze/server/RequestProcessor.scala | 31 ++++++++++++------- .../blaze/server/RequestProcessor.scala | 31 ++++++++++++------- .../blaze/server/RequestProcessor.scala | 31 ++++++++++++------- .../blaze/server/RequestProcessor.scala | 31 +++++++++++++------ 6 files changed, 120 insertions(+), 65 deletions(-) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index cf800774c..6a1272cd4 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -9,7 +9,7 @@ import com.newrelic.api.agent.security.schema._ import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.utils.logging.LogLevel -import org.http4s.{Headers, Request, Response} +import org.http4s.{Headers, Message, Request, Response} import java.util @@ -28,15 +28,17 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - isLockAcquired <- preprocessHttpRequest(request) + requestBody <- extractBody(request) + isLockAcquired <- preprocessHttpRequest(request, requestBody) resp <- httpApp(request) - _ <- postProcessSecurityHook(isLockAcquired, resp) + responseBody <- extractBody(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp, responseBody) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + private def preprocessHttpRequest[F[_]: Sync](request: Request[F], body: String): F[Boolean] = construct { val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -65,10 +67,8 @@ object RequestProcessor { securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) - // TODO extract request body & user class detection - val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace - securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) securityRequest.setRequestParsed(true) } @@ -125,7 +125,16 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { + private def extractBody[F[_]: Sync](msg: Message[F]): F[String] = { + if (msg.contentType.nonEmpty && msg.contentType.get.charset.nonEmpty) { + val charset = msg.contentType.get.charset.get; + msg.bodyAsText(defaultCharset = charset).compile.string + } else { + msg.bodyAsText.compile.string + } + } + + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse @@ -133,10 +142,9 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) - // TODO extract response body - ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) NewRelicSecurity.getAgent.registerOperation(rxssOperation) } @@ -154,7 +162,7 @@ object RequestProcessor { private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { headers.foreach(header => { - if (header.name != null && header.name.isEmpty) { + if (header.name != null && !header.name.isEmpty) { securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) } }) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index ab091964c..ee590a66a 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -10,7 +10,8 @@ import com.newrelic.api.agent.security.schema._ import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.utils.logging.LogLevel -import org.http4s.{Headers, Request, Response} +import fs2.RaiseThrowable +import org.http4s.{Headers, Message, Request, Response} import java.util @@ -29,15 +30,17 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - isLockAcquired <- preprocessHttpRequest(request) + requestBody <- extractBody(request) + isLockAcquired <- preprocessHttpRequest(request, requestBody) resp <- httpApp(request) - _ <- postProcessSecurityHook(isLockAcquired, resp) + responseBody <- extractBody(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp, responseBody) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + private def preprocessHttpRequest[F[_]: Sync](request: Request[F], body: String): F[Boolean] = construct { val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -66,10 +69,8 @@ object RequestProcessor { securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) - // TODO extract request body & user class detection - val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace - securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) securityRequest.setRequestParsed(true) } @@ -126,7 +127,16 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { + private def extractBody[F[_]: Sync](msg: Message[F]): F[String] = { + if (msg.contentType.nonEmpty && msg.contentType.get.charset.nonEmpty) { + val charset = msg.contentType.get.charset.get; + msg.bodyText(RaiseThrowable.fromApplicativeError, defaultCharset = charset).compile.string + } else { + msg.bodyText.compile.string + } + } + + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse @@ -134,10 +144,9 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) - // TODO extract response body - ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) NewRelicSecurity.getAgent.registerOperation(rxssOperation) } @@ -155,7 +164,7 @@ object RequestProcessor { private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { headers.foreach(header => { - if (header.name != null && header.name.isEmpty) { + if (header.name != null && !header.name.isEmpty) { securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) } }) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index fbbb71849..490a247c2 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -10,7 +10,8 @@ import com.newrelic.api.agent.security.schema._ import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.utils.logging.LogLevel -import org.http4s.{Headers, Request, Response} +import fs2.RaiseThrowable +import org.http4s.{Headers, Message, Request, Response} import java.util @@ -29,15 +30,17 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - isLockAcquired <- preprocessHttpRequest(request) + requestBody <- extractBody(request) + isLockAcquired <- preprocessHttpRequest(request, requestBody) resp <- httpApp(request) - _ <- postProcessSecurityHook(isLockAcquired, resp) + responseBody <- extractBody(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp, responseBody) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + private def preprocessHttpRequest[F[_]: Sync](request: Request[F], body: String): F[Boolean] = construct { val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -66,10 +69,8 @@ object RequestProcessor { securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) - // TODO extract request body & user class detection - val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace - securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) securityRequest.setRequestParsed(true) } @@ -126,7 +127,16 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { + private def extractBody[F[_]: Sync](msg: Message[F]): F[String] = { + if (msg.contentType.nonEmpty && msg.contentType.get.charset.nonEmpty) { + val charset = msg.contentType.get.charset.get; + msg.bodyText(RaiseThrowable.fromApplicativeError, defaultCharset = charset).compile.string + } else { + msg.bodyText.compile.string + } + } + + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse @@ -134,10 +144,9 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) - // TODO extract response body - ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) NewRelicSecurity.getAgent.registerOperation(rxssOperation) } @@ -155,7 +164,7 @@ object RequestProcessor { private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { headers.foreach(header => { - if (header.name != null && header.name.isEmpty) { + if (header.name != null && !header.name.isEmpty) { securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) } }) diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 70443d5fd..aca1310ef 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -9,7 +9,7 @@ import com.newrelic.api.agent.security.schema._ import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.utils.logging.LogLevel -import org.http4s.{Headers, Request, Response} +import org.http4s.{Headers, Message, Request, Response} import java.util @@ -28,15 +28,16 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - isLockAcquired <- preprocessHttpRequest(request) + requestBody <- extractBody(request) + isLockAcquired <- preprocessHttpRequest(request, requestBody) resp <- httpApp(request) - _ <- postProcessSecurityHook(isLockAcquired, resp) + responseBody <- extractBody(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp, responseBody) } yield resp ) result } - - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + private def preprocessHttpRequest[F[_]: Sync](request: Request[F], body: String): F[Boolean] = construct { val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -65,10 +66,8 @@ object RequestProcessor { securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) - // TODO extract request body & user class detection - val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace - securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) securityRequest.setRequestParsed(true) } @@ -125,7 +124,16 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { + private def extractBody[F[_]: Sync](msg: Message[F]): F[String] = { + if (msg.contentType.nonEmpty && msg.contentType.get.charset.nonEmpty) { + val charset = msg.contentType.get.charset.get; + msg.bodyAsText(defaultCharset = charset).compile.string + } else { + msg.bodyAsText.compile.string + } + } + + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse @@ -133,10 +141,9 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) - // TODO extract response body - ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) NewRelicSecurity.getAgent.registerOperation(rxssOperation) } @@ -154,7 +161,7 @@ object RequestProcessor { private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { headers.foreach(header => { - if (header.name != null && header.name.isEmpty) { + if (header.name != null && !header.name.isEmpty) { securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) } }) diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index d19d2aa49..4528feed0 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -10,7 +10,8 @@ import com.newrelic.api.agent.security.schema._ import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.utils.logging.LogLevel -import org.http4s.{Headers, Request, Response} +import fs2.RaiseThrowable +import org.http4s.{Headers, Message, Request, Response} import java.util @@ -29,15 +30,17 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - isLockAcquired <- preprocessHttpRequest(request) + requestBody <- extractBody(request) + isLockAcquired <- preprocessHttpRequest(request, requestBody) resp <- httpApp(request) - _ <- postProcessSecurityHook(isLockAcquired, resp) + responseBody <- extractBody(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp, responseBody) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + private def preprocessHttpRequest[F[_]: Sync](request: Request[F], body: String): F[Boolean] = construct { val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -66,10 +69,8 @@ object RequestProcessor { securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) - // TODO extract request body & user class detection - val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace - securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) securityRequest.setRequestParsed(true) } @@ -126,7 +127,16 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { + private def extractBody[F[_]: Sync](msg: Message[F]): F[String] = { + if (msg.contentType.nonEmpty && msg.contentType.get.charset.nonEmpty) { + val charset = msg.contentType.get.charset.get; + msg.bodyText(RaiseThrowable.fromApplicativeError, defaultCharset = charset).compile.string + } else { + msg.bodyText.compile.string + } + } + + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse @@ -134,11 +144,10 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) - // TODO extract response body - ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) NewRelicSecurity.getAgent.registerOperation(rxssOperation) } } @@ -155,7 +164,7 @@ object RequestProcessor { private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { headers.foreach(header => { - if (header.name != null && header.name.isEmpty) { + if (header.name != null && !header.name.isEmpty) { securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) } }) diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index e502cb9e4..8fbb5b53c 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -10,7 +10,8 @@ import com.newrelic.api.agent.security.schema._ import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.utils.logging.LogLevel -import org.http4s.{Headers, Request, Response} +import fs2.RaiseThrowable +import org.http4s.{Headers, Message, Request, Response} import java.util @@ -29,15 +30,17 @@ object RequestProcessor { val result = construct((): Unit) .redeemWith(_ => httpApp(request), _ => for { - isLockAcquired <- preprocessHttpRequest(request) + requestBody <- extractBody(request) + isLockAcquired <- preprocessHttpRequest(request, requestBody) resp <- httpApp(request) - _ <- postProcessSecurityHook(isLockAcquired, resp) + responseBody <- extractBody(resp) + _ <- postProcessSecurityHook(isLockAcquired, resp, responseBody) } yield resp ) result } - private def preprocessHttpRequest[F[_]: Sync](request: Request[F]): F[Boolean] = construct { + private def preprocessHttpRequest[F[_]: Sync](request: Request[F], body: String): F[Boolean] = construct { val isLockAcquired = GenericHelper.acquireLockIfPossible("HTTP4S-BLAZE-REQUEST_LOCK") try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getSecurityMetaData.getRequest.isRequestParsed){ @@ -66,10 +69,10 @@ object RequestProcessor { securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) - // TODO extract request body & user class detection + securityRequest.getBody.append(body) val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace - securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 1, trace.length)) + securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) securityRequest.setRequestParsed(true) } @@ -79,6 +82,15 @@ object RequestProcessor { isLockAcquired } + private def extractBody[F[_]: Sync](msg: Message[F]): F[String] = { + if (msg.contentType.nonEmpty && msg.contentType.get.charset.nonEmpty) { + val charset = msg.contentType.get.charset.get; + msg.bodyText(RaiseThrowable.fromApplicativeError, defaultCharset = charset).compile.string + } else { + msg.bodyText.compile.string + } + } + private def getContentType(headers: util.Map[String, String]): String = { var contentType = StringUtils.EMPTY if (headers.containsKey("content-type")) contentType = headers.get("content-type") @@ -126,7 +138,7 @@ object RequestProcessor { }) } - private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F]): F[Unit] = construct { + private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse @@ -134,10 +146,11 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) - // TODO extract response body + securityResponse.getResponseBody.append(body) ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { + NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) NewRelicSecurity.getAgent.registerOperation(rxssOperation) } @@ -155,7 +168,7 @@ object RequestProcessor { private def processResponseHeaders(headers: Headers, securityResp: HttpResponse): Unit = { headers.foreach(header => { - if (header.name != null && header.name.isEmpty) { + if (header.name != null && !header.name.isEmpty) { securityResp.getHeaders.put(header.name.toString.toLowerCase, header.value) } }) From 121f95afc3659e99e41ff26363e418043bff45bf Mon Sep 17 00:00:00 2001 From: idawda Date: Tue, 26 Nov 2024 17:10:49 +0530 Subject: [PATCH 12/23] Extracting request body in http4s-blaze server --- .../agent/security/http4s/blaze/server/RequestProcessor.scala | 3 +++ .../agent/security/http4s/blaze/server/RequestProcessor.scala | 3 +++ .../agent/security/http4s/blaze/server/RequestProcessor.scala | 3 +++ .../agent/security/http4s/blaze/server/RequestProcessor.scala | 3 +++ .../agent/security/http4s/blaze/server/RequestProcessor.scala | 3 +++ 5 files changed, 15 insertions(+) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 6a1272cd4..03886ed16 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -66,6 +66,7 @@ object RequestProcessor { processRequestHeaders(request.headers, securityRequest) securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) + securityRequest.getBody.append(body) val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) @@ -142,6 +143,8 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) + securityResponse.getResponseBody.append(body) + ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index ee590a66a..e929f358b 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -68,6 +68,7 @@ object RequestProcessor { processRequestHeaders(request.headers, securityRequest) securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) + securityRequest.getBody.append(body) val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) @@ -144,6 +145,8 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) + securityResponse.getResponseBody.append(body) + ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 490a247c2..4a0f62ef5 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -68,6 +68,7 @@ object RequestProcessor { processRequestHeaders(request.headers, securityRequest) securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) + securityRequest.getBody.append(body) val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) @@ -144,6 +145,8 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) + securityResponse.getResponseBody.append(body) + ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index aca1310ef..8bb4cfd4a 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -65,6 +65,7 @@ object RequestProcessor { processRequestHeaders(request.headers, securityRequest) securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) + securityRequest.getBody.append(body) val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) @@ -141,6 +142,8 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) + securityResponse.getResponseBody.append(body) + ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { NewRelicSecurity.getAgent.getSecurityMetaData.getMetaData.setFromJumpRequiredInStackTrace(3) diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 4528feed0..32ca014bc 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -68,6 +68,7 @@ object RequestProcessor { processRequestHeaders(request.headers, securityRequest) securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders)) securityRequest.setContentType(getContentType(securityRequest.getHeaders)) + securityRequest.getBody.append(body) val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace securityMetaData.getMetaData.setServiceTrace(util.Arrays.copyOfRange(trace, 2, trace.length)) @@ -144,6 +145,8 @@ object RequestProcessor { processResponseHeaders(response.headers, securityResponse) securityResponse.setResponseContentType(getContentType(securityResponse.getHeaders)) + securityResponse.getResponseBody.append(body) + ServletHelper.executeBeforeExitingTransaction() if (!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent.getSecurityMetaData.getResponse.getResponseContentType)) { val rxssOperation = new RXSSOperation(NewRelicSecurity.getAgent.getSecurityMetaData.getRequest, NewRelicSecurity.getAgent.getSecurityMetaData.getResponse, this.getClass.getName, METHOD_WITH_HTTP_APP) From 17d84dfaf02981721d0e7faac85051923e8cdc85 Mon Sep 17 00:00:00 2001 From: idawda Date: Tue, 26 Nov 2024 17:15:57 +0530 Subject: [PATCH 13/23] Update instrumentation unit tests for blaze body extraction --- .../blaze/server/BlazeServerBuilderTest.scala | 43 +++++++++++++------ .../blaze/server/BlazeServerBuilderTest.scala | 43 +++++++++++++------ .../blaze/server/BlazeServerBuilderTest.scala | 43 +++++++++++++------ .../blaze/server/BlazeServerBuilderTest.scala | 43 +++++++++++++------ .../blaze/server/BlazeServerBuilderTest.scala | 43 +++++++++++++------ .../blaze/server/BlazeServerBuilderTest.scala | 43 +++++++++++++------ 6 files changed, 180 insertions(+), 78 deletions(-) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala index 883528c80..00dc05603 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -5,10 +5,10 @@ import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, Securit import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} -import org.http4s.HttpRoutes -import org.http4s.Method.GET import org.http4s.dsl.io._ import org.http4s.implicits._ +import org.http4s.util.CaseInsensitiveString +import org.http4s.{Header, HttpRoutes} import org.junit.runner.RunWith import org.junit.{After, Assert, Before, Test} @@ -26,8 +26,8 @@ class EmberServerBuilderTest { val emberServer = new Http4sTestServer(hostname, port, HttpRoutes.of[IO] { - case GET -> Root / "hello" / name => - Ok(s"Hello, $name.") + case _ -> Root / "hello" / name => + Ok(s"Hello, $name.").map(_.putHeaders(Header.Raw(CaseInsensitiveString.apply("content-type"), contentType))) }.orNotFound) @Before @@ -87,32 +87,49 @@ class EmberServerBuilderTest { Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getRequest.getHeaders.isEmpty) + Assert.assertEquals("Invalid Request body", "body extract", operation.getRequest.getBody.toString) + + Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", operation.getResponse.getResponseBody.toString) + Assert.assertEquals("Invalid Response code", 200, operation.getResponse.getResponseCode) } private def assertMetaData(metaData: SecurityMetaData): Unit = { Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Request body", "body extract", metaData.getRequest.getBody.toString) + Assert.assertFalse("Headers should not be empty", metaData.getRequest.getHeaders.isEmpty) + + Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Response code", 200, metaData.getResponse.getResponseCode) + Assert.assertFalse("Headers should not be empty", metaData.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", metaData.getResponse.getResponseBody.toString) } } object Http4sTestUtils { - def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Unit = { val u: URL = new URL(url) val conn = u.openConnection.asInstanceOf[HttpURLConnection] - conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + conn.setDoOutput(true) + + conn.setRequestProperty("content-type", "text/plain") + if (addCSECHeader) { conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) } + + val stream = conn.getOutputStream + stream.write("body extract".getBytes) + conn.connect() - conn.getResponseCode + println(conn.getResponseCode) } } diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala index ad5f3736d..3fc5dc675 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -5,12 +5,12 @@ import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, Securit import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} -import org.http4s.HttpRoutes -import org.http4s.Method.GET import org.http4s.dsl.io._ import org.http4s.implicits._ +import org.http4s.{Header, HttpRoutes} import org.junit.runner.RunWith import org.junit.{After, Assert, Before, Test} +import org.typelevel.ci.CIString import java.net.{HttpURLConnection, URL} import java.util @@ -26,8 +26,8 @@ class EmberServerBuilderTest { val emberServer = new Http4sTestServer(hostname, port, HttpRoutes.of[IO] { - case GET -> Root / "hello" / name => - Ok(s"Hello, $name.") + case _ -> Root / "hello" / name => + Ok(s"Hello, $name.").map(_.putHeaders(Header.Raw(CIString.apply("content-type"), contentType))) }.orNotFound) @Before @@ -87,32 +87,49 @@ class EmberServerBuilderTest { Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getRequest.getHeaders.isEmpty) + Assert.assertEquals("Invalid Request body", "body extract", operation.getRequest.getBody.toString) + + Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", operation.getResponse.getResponseBody.toString) + Assert.assertEquals("Invalid Response code", 200, operation.getResponse.getResponseCode) } private def assertMetaData(metaData: SecurityMetaData): Unit = { Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Request body", "body extract", metaData.getRequest.getBody.toString) + Assert.assertFalse("Headers should not be empty", metaData.getRequest.getHeaders.isEmpty) + + Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Response code", 200, metaData.getResponse.getResponseCode) + Assert.assertFalse("Headers should not be empty", metaData.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", metaData.getResponse.getResponseBody.toString) } } object Http4sTestUtils { - def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Unit = { val u: URL = new URL(url) val conn = u.openConnection.asInstanceOf[HttpURLConnection] - conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + conn.setDoOutput(true) + + conn.setRequestProperty("content-type", "text/plain") + if (addCSECHeader) { conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) } + + val stream = conn.getOutputStream + stream.write("body extract".getBytes) + conn.connect() - conn.getResponseCode + println(conn.getResponseCode) } } diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala index ad5f3736d..3fc5dc675 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -5,12 +5,12 @@ import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, Securit import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} -import org.http4s.HttpRoutes -import org.http4s.Method.GET import org.http4s.dsl.io._ import org.http4s.implicits._ +import org.http4s.{Header, HttpRoutes} import org.junit.runner.RunWith import org.junit.{After, Assert, Before, Test} +import org.typelevel.ci.CIString import java.net.{HttpURLConnection, URL} import java.util @@ -26,8 +26,8 @@ class EmberServerBuilderTest { val emberServer = new Http4sTestServer(hostname, port, HttpRoutes.of[IO] { - case GET -> Root / "hello" / name => - Ok(s"Hello, $name.") + case _ -> Root / "hello" / name => + Ok(s"Hello, $name.").map(_.putHeaders(Header.Raw(CIString.apply("content-type"), contentType))) }.orNotFound) @Before @@ -87,32 +87,49 @@ class EmberServerBuilderTest { Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getRequest.getHeaders.isEmpty) + Assert.assertEquals("Invalid Request body", "body extract", operation.getRequest.getBody.toString) + + Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", operation.getResponse.getResponseBody.toString) + Assert.assertEquals("Invalid Response code", 200, operation.getResponse.getResponseCode) } private def assertMetaData(metaData: SecurityMetaData): Unit = { Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Request body", "body extract", metaData.getRequest.getBody.toString) + Assert.assertFalse("Headers should not be empty", metaData.getRequest.getHeaders.isEmpty) + + Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Response code", 200, metaData.getResponse.getResponseCode) + Assert.assertFalse("Headers should not be empty", metaData.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", metaData.getResponse.getResponseBody.toString) } } object Http4sTestUtils { - def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Unit = { val u: URL = new URL(url) val conn = u.openConnection.asInstanceOf[HttpURLConnection] - conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + conn.setDoOutput(true) + + conn.setRequestProperty("content-type", "text/plain") + if (addCSECHeader) { conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) } + + val stream = conn.getOutputStream + stream.write("body extract".getBytes) + conn.connect() - conn.getResponseCode + println(conn.getResponseCode) } } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala index 883528c80..00dc05603 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -5,10 +5,10 @@ import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, Securit import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} -import org.http4s.HttpRoutes -import org.http4s.Method.GET import org.http4s.dsl.io._ import org.http4s.implicits._ +import org.http4s.util.CaseInsensitiveString +import org.http4s.{Header, HttpRoutes} import org.junit.runner.RunWith import org.junit.{After, Assert, Before, Test} @@ -26,8 +26,8 @@ class EmberServerBuilderTest { val emberServer = new Http4sTestServer(hostname, port, HttpRoutes.of[IO] { - case GET -> Root / "hello" / name => - Ok(s"Hello, $name.") + case _ -> Root / "hello" / name => + Ok(s"Hello, $name.").map(_.putHeaders(Header.Raw(CaseInsensitiveString.apply("content-type"), contentType))) }.orNotFound) @Before @@ -87,32 +87,49 @@ class EmberServerBuilderTest { Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getRequest.getHeaders.isEmpty) + Assert.assertEquals("Invalid Request body", "body extract", operation.getRequest.getBody.toString) + + Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", operation.getResponse.getResponseBody.toString) + Assert.assertEquals("Invalid Response code", 200, operation.getResponse.getResponseCode) } private def assertMetaData(metaData: SecurityMetaData): Unit = { Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Request body", "body extract", metaData.getRequest.getBody.toString) + Assert.assertFalse("Headers should not be empty", metaData.getRequest.getHeaders.isEmpty) + + Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Response code", 200, metaData.getResponse.getResponseCode) + Assert.assertFalse("Headers should not be empty", metaData.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", metaData.getResponse.getResponseBody.toString) } } object Http4sTestUtils { - def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Unit = { val u: URL = new URL(url) val conn = u.openConnection.asInstanceOf[HttpURLConnection] - conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + conn.setDoOutput(true) + + conn.setRequestProperty("content-type", "text/plain") + if (addCSECHeader) { conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) } + + val stream = conn.getOutputStream + stream.write("body extract".getBytes) + conn.connect() - conn.getResponseCode + println(conn.getResponseCode) } } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala index 883528c80..72aa40728 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -5,12 +5,12 @@ import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, Securit import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} -import org.http4s.HttpRoutes -import org.http4s.Method.GET import org.http4s.dsl.io._ import org.http4s.implicits._ +import org.http4s.{Header, HttpRoutes} import org.junit.runner.RunWith import org.junit.{After, Assert, Before, Test} +import org.typelevel.ci.CIString import java.net.{HttpURLConnection, URL} import java.util @@ -26,8 +26,8 @@ class EmberServerBuilderTest { val emberServer = new Http4sTestServer(hostname, port, HttpRoutes.of[IO] { - case GET -> Root / "hello" / name => - Ok(s"Hello, $name.") + case _ -> Root / "hello" / name => + Ok(s"Hello, $name.").map(_.putHeaders(Header.Raw(CIString.apply("content-type"), contentType))) }.orNotFound) @Before @@ -87,32 +87,49 @@ class EmberServerBuilderTest { Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getRequest.getHeaders.isEmpty) + Assert.assertEquals("Invalid Request body", "body extract", operation.getRequest.getBody.toString) + + Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", operation.getResponse.getResponseBody.toString) + Assert.assertEquals("Invalid Response code", 200, operation.getResponse.getResponseCode) } private def assertMetaData(metaData: SecurityMetaData): Unit = { Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Request body", "body extract", metaData.getRequest.getBody.toString) + Assert.assertFalse("Headers should not be empty", metaData.getRequest.getHeaders.isEmpty) + + Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Response code", 200, metaData.getResponse.getResponseCode) + Assert.assertFalse("Headers should not be empty", metaData.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", metaData.getResponse.getResponseBody.toString) } } object Http4sTestUtils { - def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Unit = { val u: URL = new URL(url) val conn = u.openConnection.asInstanceOf[HttpURLConnection] - conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + conn.setDoOutput(true) + + conn.setRequestProperty("content-type", "text/plain") + if (addCSECHeader) { conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) } + + val stream = conn.getOutputStream + stream.write("body extract".getBytes) + conn.connect() - conn.getResponseCode + println(conn.getResponseCode) } } diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala index ad5f3736d..3fc5dc675 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/test/scala/com/nr/agent/security/instrumentation/blaze/server/BlazeServerBuilderTest.scala @@ -5,12 +5,12 @@ import com.newrelic.agent.security.introspec.{InstrumentationTestConfig, Securit import com.newrelic.api.agent.security.instrumentation.helpers.{GenericHelper, ServletHelper} import com.newrelic.api.agent.security.schema.operation.RXSSOperation import com.newrelic.api.agent.security.schema.{SecurityMetaData, VulnerabilityCaseType} -import org.http4s.HttpRoutes -import org.http4s.Method.GET import org.http4s.dsl.io._ import org.http4s.implicits._ +import org.http4s.{Header, HttpRoutes} import org.junit.runner.RunWith import org.junit.{After, Assert, Before, Test} +import org.typelevel.ci.CIString import java.net.{HttpURLConnection, URL} import java.util @@ -26,8 +26,8 @@ class EmberServerBuilderTest { val emberServer = new Http4sTestServer(hostname, port, HttpRoutes.of[IO] { - case GET -> Root / "hello" / name => - Ok(s"Hello, $name.") + case _ -> Root / "hello" / name => + Ok(s"Hello, $name.").map(_.putHeaders(Header.Raw(CIString.apply("content-type"), contentType))) }.orNotFound) @Before @@ -87,32 +87,49 @@ class EmberServerBuilderTest { Assert.assertFalse("request should not be empty", operation.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, operation.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", operation.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getRequest.getHeaders.isEmpty) + Assert.assertEquals("Invalid Request body", "body extract", operation.getRequest.getBody.toString) + + Assert.assertFalse("response should not be empty", operation.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, operation.getResponse.getResponseContentType) + Assert.assertFalse("Headers should not be empty", operation.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", operation.getResponse.getResponseBody.toString) + Assert.assertEquals("Invalid Response code", 200, operation.getResponse.getResponseCode) } private def assertMetaData(metaData: SecurityMetaData): Unit = { Assert.assertFalse("request should not be empty", metaData.getRequest.isEmpty) Assert.assertEquals("Invalid Request content-type.", contentType, metaData.getRequest.getContentType) Assert.assertEquals("Invalid protocol.", "http", metaData.getRequest.getProtocol) - - // Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) - // Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Request body", "body extract", metaData.getRequest.getBody.toString) + Assert.assertFalse("Headers should not be empty", metaData.getRequest.getHeaders.isEmpty) + + Assert.assertFalse("response should not be empty", metaData.getResponse.isEmpty) + Assert.assertEquals("Invalid response content-type.", contentType, metaData.getResponse.getResponseContentType) + Assert.assertEquals("Invalid Response code", 200, metaData.getResponse.getResponseCode) + Assert.assertFalse("Headers should not be empty", metaData.getResponse.getHeaders.isEmpty) + Assert.assertEquals("Invalid Response body", "Hello, bob.", metaData.getResponse.getResponseBody.toString) } } object Http4sTestUtils { - def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Int = { + def makeRequest(url: String, addCSECHeader: Boolean, headerValue: String): Unit = { val u: URL = new URL(url) val conn = u.openConnection.asInstanceOf[HttpURLConnection] - conn.setRequestProperty("content-type", "text/plain; charset=utf-8") + conn.setDoOutput(true) + + conn.setRequestProperty("content-type", "text/plain") + if (addCSECHeader) { conn.setRequestProperty(ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID, headerValue + "a") conn.setRequestProperty(GenericHelper.CSEC_PARENT_ID, headerValue + "b") conn.setRequestProperty(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER, String.format("%s;DUMMY_UUID/dummy-api-id/dummy-exec-id;", headerValue)) } + + val stream = conn.getOutputStream + stream.write("body extract".getBytes) + conn.connect() - conn.getResponseCode + println(conn.getResponseCode) } } From 87e123b040c5430ac894c852311887eb7dd74161 Mon Sep 17 00:00:00 2001 From: idawda Date: Thu, 5 Dec 2024 11:45:54 +0530 Subject: [PATCH 14/23] Do not generate RXSS events if RXSS is disabled --- .../agent/security/http4s/blaze/server/RequestProcessor.scala | 2 +- .../agent/security/http4s/blaze/server/RequestProcessor.scala | 2 +- .../agent/security/http4s/blaze/server/RequestProcessor.scala | 2 +- .../agent/security/http4s/blaze/server/RequestProcessor.scala | 2 +- .../agent/security/http4s/blaze/server/RequestProcessor.scala | 2 +- .../agent/security/http4s/blaze/server/RequestProcessor.scala | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 03886ed16..ceb9aa337 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -137,7 +137,7 @@ object RequestProcessor { private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getIastDetectionCategory.getRxssEnabled) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index e929f358b..5006fa96b 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -139,7 +139,7 @@ object RequestProcessor { private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getIastDetectionCategory.getRxssEnabled) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) diff --git a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 4a0f62ef5..f96537f72 100644 --- a/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.12_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -139,7 +139,7 @@ object RequestProcessor { private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getIastDetectionCategory.getRxssEnabled) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 8bb4cfd4a..5d249bf6e 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.21/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -136,7 +136,7 @@ object RequestProcessor { private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getIastDetectionCategory.getRxssEnabled) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 32ca014bc..9186f4613 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.22/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -139,7 +139,7 @@ object RequestProcessor { private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getIastDetectionCategory.getRxssEnabled) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) diff --git a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala index 8fbb5b53c..bbcc5281b 100644 --- a/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala +++ b/instrumentation-security/http4s-blaze-server-2.13_0.23/src/main/scala/com/newrelic/agent/security/http4s/blaze/server/RequestProcessor.scala @@ -140,7 +140,7 @@ object RequestProcessor { private def postProcessSecurityHook[F[_]: Sync](isLockAcquired:Boolean, response: Response[F], body: String): F[Unit] = construct { try { - if (NewRelicSecurity.isHookProcessingActive && isLockAcquired) { + if (NewRelicSecurity.isHookProcessingActive && isLockAcquired && !NewRelicSecurity.getAgent.getIastDetectionCategory.getRxssEnabled) { val securityResponse = NewRelicSecurity.getAgent.getSecurityMetaData.getResponse securityResponse.setResponseCode(response.status.code) processResponseHeaders(response.headers, securityResponse) From 9beb46d6c80bb4c0a8a0e906fa8b39c2b2f2788c Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Thu, 12 Dec 2024 21:30:19 +0530 Subject: [PATCH 15/23] Add changelog for CSEC release version 1.6.0 --- Changelog.md | 20 ++++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index a680acb6d..de089167f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,26 @@ Noteworthy changes to the agent are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0] - 2024-12-16 +### Adds +- Apache Pekko Server Support: The security agent now supports Apache Pekko Server version 1.0.0 and newer, compatible with Scala 2.13 and above. +- HTTP4s Ember Server Support: Added support for HTTP4s Ember Server version 0.23 and newer, compatible with Scala 2.12 and above. +- HTTP4s Blaze Server Support: The security agent now supports HTTP4s Blaze Server version 0.21 and newer, compatible with Scala 2.12 and above. +- HTTP4s Ember Client Support: Introduced support for HTTP4s Ember Client version 0.23 and above, compatible with Scala 2.12 and above. +- HTTP4s Blaze Client Support: Added support for HTTP4s Blaze Client version 0.21 and newer, compatible with Scala 2.12 and above. +- GraphQL Support: GraphQL support is now enabled by default. + +### Changes +- REST Client Update for IAST Request Replay: Migrated to utilize the Apache HTTP Client for enhanced request replay functionality. +- Status File Removed: The status file used for debugging has been eliminated. All debugging capabilities have been integrated into Init Logging or the Error Inbox. +- Code Optimization: Optimized code to minimize the overhead of the Security Agent in relation to the APM Agent. + + +### Fixes +- Corrected the issue regarding inaccurate user class details in the mule-demo-app. +- Improved logging for scenarios where delay is set to a negative value. + + ## [1.5.1] - 2024-11-9 ### New features - [PR-350](https://github.com/newrelic/csec-java-agent/pull/350) IAST support for CI/CD. diff --git a/gradle.properties b/gradle.properties index e642d6bf5..220b28d15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # The agent version. -agentVersion=1.5.1 +agentVersion=1.6.0 jsonVersion=1.2.9 # Updated exposed NR APM API version. nrAPIVersion=8.12.0 From 6241f5391ac40b09750891b2f9048c03203f528f Mon Sep 17 00:00:00 2001 From: idawda Date: Fri, 13 Dec 2024 13:00:09 +0530 Subject: [PATCH 16/23] Add Jira & PRs links to Changelogs for documentation --- Changelog.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Changelog.md b/Changelog.md index de089167f..4b1b049d6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,22 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.6.0] - 2024-12-16 ### Adds -- Apache Pekko Server Support: The security agent now supports Apache Pekko Server version 1.0.0 and newer, compatible with Scala 2.13 and above. -- HTTP4s Ember Server Support: Added support for HTTP4s Ember Server version 0.23 and newer, compatible with Scala 2.12 and above. -- HTTP4s Blaze Server Support: The security agent now supports HTTP4s Blaze Server version 0.21 and newer, compatible with Scala 2.12 and above. -- HTTP4s Ember Client Support: Introduced support for HTTP4s Ember Client version 0.23 and above, compatible with Scala 2.12 and above. -- HTTP4s Blaze Client Support: Added support for HTTP4s Blaze Client version 0.21 and newer, compatible with Scala 2.12 and above. -- GraphQL Support: GraphQL support is now enabled by default. +- [PR-329](https://github.com/newrelic/csec-java-agent/pull/329) Apache Pekko Server Support: The security agent now supports Apache Pekko Server version 1.0.0 and newer, compatible with Scala 2.13 and above. [NR-308780](https://new-relic.atlassian.net/browse/NR-308780), [NR-308781](https://new-relic.atlassian.net/browse/NR-308781), [NR-308791](https://new-relic.atlassian.net/browse/NR-308791), [NR-308792](https://new-relic.atlassian.net/browse/NR-308792) [NR-308782](https://new-relic.atlassian.net/browse/NR-308782) +- [PR-228](https://github.com/newrelic/csec-java-agent/pull/228) HTTP4s Ember Server Support: Added support for HTTP4s Ember Server version 0.23 and newer, compatible with Scala 2.12 and above. [NR-293957](https://new-relic.atlassian.net/browse/NR-293957), [NR-293847](https://new-relic.atlassian.net/browse/NR-293847), [NR-293844](https://new-relic.atlassian.net/browse/NR-293844) +- [PR-344](https://github.com/newrelic/csec-java-agent/pull/344) HTTP4s Blaze Server Support: The security agent now supports HTTP4s Blaze Server version 0.21 and newer, compatible with Scala 2.12 and above. [NR-325523](https://new-relic.atlassian.net/browse/NR-325523), [NR-325525](https://new-relic.atlassian.net/browse/NR-325525), [NR-293846](https://new-relic.atlassian.net/browse/NR-293846) +- [PR-228](https://github.com/newrelic/csec-java-agent/pull/228) HTTP4s Ember Client Support: Introduced support for HTTP4s Ember Client version 0.23 and above, compatible with Scala 2.12 and above. [NR-307676](https://new-relic.atlassian.net/browse/NR-307676) +- [PR-346](https://github.com/newrelic/csec-java-agent/pull/346) HTTP4s Blaze Client Support: Added support for HTTP4s Blaze Client version 0.21 and newer, compatible with Scala 2.12 and above. [NR-325526](https://new-relic.atlassian.net/browse/NR-325526), [NR-325527](https://new-relic.atlassian.net/browse/NR-325527) +- [PR-363](https://github.com/newrelic/csec-java-agent/pull/363) GraphQL Support: GraphQL support is now enabled by default. ### Changes -- REST Client Update for IAST Request Replay: Migrated to utilize the Apache HTTP Client for enhanced request replay functionality. -- Status File Removed: The status file used for debugging has been eliminated. All debugging capabilities have been integrated into Init Logging or the Error Inbox. -- Code Optimization: Optimized code to minimize the overhead of the Security Agent in relation to the APM Agent. - +- [PR-331](https://github.com/newrelic/csec-java-agent/pull/331) REST Client Update for IAST Request Replay: Migrated to utilize the Apache HTTP Client for enhanced request replay functionality. [NR-283130](https://new-relic.atlassian.net/browse/NR-283130) +- [PR-311](https://github.com/newrelic/csec-java-agent/pull/311) Status File Removed: The status file used for debugging has been eliminated. All debugging capabilities have been integrated into Init Logging or the Error Inbox. [NR-297214](https://new-relic.atlassian.net/browse/NR-297214) +- [PR-356](https://github.com/newrelic/csec-java-agent/pull/356) Code Optimization: Optimized code to minimize the overhead of the Security Agent in relation to the APM Agent. [NR-338596](https://new-relic.atlassian.net/browse/NR-338596) ### Fixes -- Corrected the issue regarding inaccurate user class details in the mule-demo-app. -- Improved logging for scenarios where delay is set to a negative value. +- [PR-352](https://github.com/newrelic/csec-java-agent/pull/352) Corrected the issue regarding inaccurate user class details in the mule-demo-app. [NR-336715](https://new-relic.atlassian.net/browse/NR-336715) +- [PR-355](https://github.com/newrelic/csec-java-agent/pull/355) Improved logging for scenarios where delay is set to a negative value. [NR-338578](https://new-relic.atlassian.net/browse/NR-338578) ## [1.5.1] - 2024-11-9 From 0cc841e26594325559e9e7e7218a9031c7d6998c Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Mon, 16 Dec 2024 10:26:28 +0530 Subject: [PATCH 17/23] this utility is invoked even before agent initiallization which makes logger null and may cause NPE. --- .../src/main/java/com/newrelic/api/agent/security/Agent.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java b/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java index 6e3885c64..a9b9de952 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java @@ -889,7 +889,9 @@ public void setApplicationConnectionConfig(int port, String scheme) { AppServerInfo appServerInfo = AppServerInfoHelper.getAppServerInfo(); ServerConnectionConfiguration serverConnectionConfiguration = new ServerConnectionConfiguration(port, scheme); appServerInfo.getConnectionConfiguration().put(port, serverConnectionConfiguration); - logger.log(LogLevel.FINER, String.format("Unconfirmed connection configuration for port %d and scheme %s added.", port, scheme), this.getClass().getName()); + if(logger != null) { + logger.log(LogLevel.FINER, String.format("Unconfirmed connection configuration for port %d and scheme %s added.", port, scheme), this.getClass().getName()); + } // verifyConnectionAndPut(port, scheme, appServerInfo); } From aaefbefdd817f93967e2c9baf9a2e30f6a639c87 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Mon, 16 Dec 2024 14:02:41 +0530 Subject: [PATCH 18/23] Disable url encoding --- .../apache/httpclient/ApacheHttpClientWrapper.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java index 7e9cac4a7..178c0fe6f 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java @@ -245,14 +245,7 @@ private HttpUriRequest buildIastFuzzRequest(HttpRequest httpRequest, String endp if (StringUtils.isBlank(requestUrl)) { throw new ApacheHttpExceptionWrapper("Request URL is empty"); } - String path = StringUtils.substringBefore(requestUrl, SEPARATOR_QUESTION_MARK); - uriBuilder.setPath(path); - String queryString = StringUtils.substringAfter(requestUrl, SEPARATOR_QUESTION_MARK); - if (StringUtils.isNotBlank(queryString)) { - //Use of this deprecated method is intentional as we are building the query string exactly as provided by SE. - uriBuilder.setQuery(queryString); - } - requestBuilder.setUri(uriBuilder.build()); + requestBuilder.setUri(requestUrl); if(StringUtils.startsWith(httpRequest.getContentType(), APPLICATION_X_WWW_FORM_URLENCODED)){ requestBuilder.setEntity(new UrlEncodedFormEntity(buildFormParameters(httpRequest.getParameterMap()))); } From 7275b996d24dddaa93261e267e6a90ba78d5eb4e Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Mon, 16 Dec 2024 14:56:27 +0530 Subject: [PATCH 19/23] Minor log fix --- .../intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java index 178c0fe6f..c53ff2f7b 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java @@ -233,7 +233,7 @@ public ReadResult execute(HttpRequest httpRequest, String endpoint, String fuzzR } catch (IOException hostConnectException) { String message = "IOException Error while executing request %s: %s message : %s"; logger.log(LogLevel.FINE, String.format(message, fuzzRequestId, request, hostConnectException.getMessage()), ApacheHttpClientWrapper.class.getName()); - logger.postLogMessageIfNecessary(LogLevel.WARNING, String.format(message, request, hostConnectException.getMessage()), hostConnectException, ApacheHttpClientWrapper.class.getName()); + logger.postLogMessageIfNecessary(LogLevel.WARNING, String.format(message, fuzzRequestId, request, hostConnectException.getMessage()), hostConnectException, ApacheHttpClientWrapper.class.getName()); throw hostConnectException; } } From c92389415584945c4fdc829f3de1b56a2267ef80 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Mon, 16 Dec 2024 16:28:07 +0530 Subject: [PATCH 20/23] fix endpoint selection --- .../httpclient/ApacheHttpClientWrapper.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java index c53ff2f7b..690a44dda 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java @@ -58,6 +58,7 @@ public class ApacheHttpClientWrapper { public static final String SEPARATOR_QUESTION_MARK = "?"; + public static final String SUFFIX_SLASH = "/"; private final ApacheProxyManager proxyManager; private final PoolingHttpClientConnectionManager connectionManager; private final CloseableHttpClient httpClient; @@ -240,12 +241,11 @@ public ReadResult execute(HttpRequest httpRequest, String endpoint, String fuzzR private HttpUriRequest buildIastFuzzRequest(HttpRequest httpRequest, String endpoint, boolean addEventIgnoreHeader) throws URISyntaxException, UnsupportedEncodingException, ApacheHttpExceptionWrapper { RequestBuilder requestBuilder = getRequestBuilder(httpRequest.getMethod()); - URIBuilder uriBuilder = new URIBuilder(endpoint); String requestUrl = httpRequest.getUrl(); if (StringUtils.isBlank(requestUrl)) { throw new ApacheHttpExceptionWrapper("Request URL is empty"); } - requestBuilder.setUri(requestUrl); + requestBuilder.setUri(createURL(endpoint, requestUrl)); if(StringUtils.startsWith(httpRequest.getContentType(), APPLICATION_X_WWW_FORM_URLENCODED)){ requestBuilder.setEntity(new UrlEncodedFormEntity(buildFormParameters(httpRequest.getParameterMap()))); } @@ -261,6 +261,19 @@ private HttpUriRequest buildIastFuzzRequest(HttpRequest httpRequest, String endp return requestBuilder.build(); } + private URI createURL(String endpoint, String requestUrl) { + if (StringUtils.isBlank(requestUrl)) { + return URI.create(endpoint); + } + if (StringUtils.endsWith(endpoint, SUFFIX_SLASH) && StringUtils.startsWith(requestUrl, SUFFIX_SLASH)) { + return URI.create(endpoint + requestUrl.substring(1)); + } else if (StringUtils.endsWith(endpoint, SUFFIX_SLASH) || StringUtils.startsWith(requestUrl, SUFFIX_SLASH)) { + return URI.create(endpoint + requestUrl); + } else { + return URI.create(endpoint + SUFFIX_SLASH + requestUrl); + } + } + private List buildFormParameters(Map parameterMap) { List formParameters = new ArrayList<>(); for (Map.Entry formData : parameterMap.entrySet()) { From 413c6b7a843613bfbb7f897ad9360ed150048a77 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Mon, 16 Dec 2024 18:56:04 +0530 Subject: [PATCH 21/23] Fix for status code check --- .../intcodeagent/apache/httpclient/IastHttpClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/IastHttpClient.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/IastHttpClient.java index 890f4b944..f858ed575 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/IastHttpClient.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/IastHttpClient.java @@ -77,7 +77,8 @@ public void tryToEstablishApplicationEndpoint(HttpRequest request) { for (Map.Entry endpoint : endpoints.entrySet()) { try { ReadResult result = httpClient.execute(request, endpoint.getValue(), null, true); - if(result.getStatusCode() >= 200 && result.getStatusCode() <= 500) { + int statusCode = result.getStatusCode(); + if ((statusCode >= 200 && statusCode < 300) || (statusCode >= 400 && statusCode < 500)) { ServerConnectionConfiguration serverConnectionConfiguration = new ServerConnectionConfiguration(serverPort, endpoint.getKey(), endpoint.getValue(), true); AppServerInfo appServerInfo = AppServerInfoHelper.getAppServerInfo(); appServerInfo.getConnectionConfiguration().put(serverPort, serverConnectionConfiguration); From 49e7eceeb7fa6d754807f89dfb9b1eb78303d881 Mon Sep 17 00:00:00 2001 From: lovesh-ap Date: Mon, 16 Dec 2024 19:01:42 +0530 Subject: [PATCH 22/23] Fix for status code check in 4xx list --- .../intcodeagent/apache/httpclient/IastHttpClient.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/IastHttpClient.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/IastHttpClient.java index f858ed575..97130132d 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/IastHttpClient.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/IastHttpClient.java @@ -78,7 +78,9 @@ public void tryToEstablishApplicationEndpoint(HttpRequest request) { try { ReadResult result = httpClient.execute(request, endpoint.getValue(), null, true); int statusCode = result.getStatusCode(); - if ((statusCode >= 200 && statusCode < 300) || (statusCode >= 400 && statusCode < 500)) { + if ((statusCode >= 200 && statusCode < 300) || + statusCode == 401 || statusCode == 402 || + statusCode == 406 || statusCode == 409) { ServerConnectionConfiguration serverConnectionConfiguration = new ServerConnectionConfiguration(serverPort, endpoint.getKey(), endpoint.getValue(), true); AppServerInfo appServerInfo = AppServerInfoHelper.getAppServerInfo(); appServerInfo.getConnectionConfiguration().put(serverPort, serverConnectionConfiguration); From a07377180c0db9a1e18a7c80d3e16f7155d13259 Mon Sep 17 00:00:00 2001 From: idawda Date: Mon, 16 Dec 2024 19:33:29 +0530 Subject: [PATCH 23/23] Use NoopHostname verifier in IAST Apache client --- .../apache/httpclient/ApacheHttpClientWrapper.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java index 690a44dda..5e2d549a4 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/apache/httpclient/ApacheHttpClientWrapper.java @@ -25,6 +25,7 @@ import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.StringEntity; @@ -98,7 +99,7 @@ public ApacheHttpClientWrapper(int requestTimeoutInMillis) { .disableCookieManagement() .disableAuthCaching() .disableConnectionState() - .setSSLHostnameVerifier(new DefaultHostnameVerifier()) + .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) .setDefaultRequestConfig(RequestConfig.custom() // Timeout in millis until a connection is established. .setConnectTimeout(requestTimeoutInMillis) @@ -137,7 +138,7 @@ private static PoolingHttpClientConnectionManager createHttpClientConnectionMana RegistryBuilder.create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslContext != null ? - new SSLConnectionSocketFactory(sslContext) : SSLConnectionSocketFactory.getSocketFactory()) + new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE) : SSLConnectionSocketFactory.getSocketFactory()) .build()); // We only allow one connection at a time to the backend.