From a8be511117dc145819cf92edb06faca0874c0a14 Mon Sep 17 00:00:00 2001 From: idawda Date: Wed, 6 Dec 2023 16:00:03 +0530 Subject: [PATCH] Instrumentation support for mule-3.7 --- .../mule-3.7/build.gradle | 49 +++++++ .../instrumentation/mule37/MuleHelper.java | 85 ++++++++++++ ...ttpRequestToMuleEvent_Instrumentation.java | 125 ++++++++++++++++++ .../async/RequestHandler_Instrumentation.java | 119 +++++++++++++++++ ...questDispatcherFilter_Instrumentation.java | 26 ++++ settings.gradle | 1 + 6 files changed, 405 insertions(+) create mode 100644 instrumentation-security/mule-3.7/build.gradle create mode 100644 instrumentation-security/mule-3.7/src/main/java/com/newrelic/agent/security/instrumentation/mule37/MuleHelper.java create mode 100644 instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java create mode 100644 instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java create mode 100644 instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/grizzly/GrizzlyRequestDispatcherFilter_Instrumentation.java diff --git a/instrumentation-security/mule-3.7/build.gradle b/instrumentation-security/mule-3.7/build.gradle new file mode 100644 index 000000000..9e554aa15 --- /dev/null +++ b/instrumentation-security/mule-3.7/build.gradle @@ -0,0 +1,49 @@ +repositories { + maven { + url 'https://repository.mulesoft.org/releases/' + } + maven { + url 'https://repository.mulesoft.org/snapshots/' + } + maven { + url 'https://repository.mulesoft.org/nexus/content/repositories/public/' + } +} + +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + + implementation("org.mule:mule-core:3.7.0") + implementation("org.mule.modules:mule-module-http:3.7.0") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.mule-3.7', + 'Implementation-Title-Alias': 'mule_instrumentation' } +} + +// This will still match [3.7.1,3.7.5], and [3.8.2,3.8.7] +// but we can't verify that because the artifacts are enterprise only (behind auth) +verifyInstrumentation { + passes('org.mule:mule-core:3.7.0') { + implementation("org.mule.modules:mule-module-http:3.7.0") + } + passes('org.mule:mule-core:[3.8.0,3.8.2)') { + implementation("org.mule.modules:mule-module-http:3.7.0") + } + + // these versions cause problems getting artifacts + exclude 'org.mule:mule-core:[0,3.4.0)' + exclude 'org.mule:mule-core:3.5.4' + exclude 'org.mule:mule-core:[3.6.2,3.7.0)' + exclude 'org.mule:mule-core:[3.7.1,3.8.0)' + exclude 'org.mule:mule-core:[3.8.2,)' + + excludeRegex 'org.mule:mule-core:.*-(EA|HF|RC|M|rc|bighorn|cascade).*[0-9]*.*' +} + +site { + title 'Mule' + type 'Appserver' +} \ No newline at end of file diff --git a/instrumentation-security/mule-3.7/src/main/java/com/newrelic/agent/security/instrumentation/mule37/MuleHelper.java b/instrumentation-security/mule-3.7/src/main/java/com/newrelic/agent/security/instrumentation/mule37/MuleHelper.java new file mode 100644 index 000000000..f05979db0 --- /dev/null +++ b/instrumentation-security/mule-3.7/src/main/java/com/newrelic/agent/security/instrumentation/mule37/MuleHelper.java @@ -0,0 +1,85 @@ +package com.newrelic.agent.security.instrumentation.mule37; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AgentMetaData; +import com.newrelic.api.agent.security.schema.policy.AgentPolicy; +import org.mule.module.http.api.HttpHeaders; +import org.mule.module.http.internal.domain.request.HttpRequest; + +import java.util.Map; + +import static org.mule.module.http.api.HttpHeaders.Names.X_FORWARDED_FOR; + +public class MuleHelper { + private static final String MULE_LOCK_CUSTOM_ATTRIB_NAME = "MULE_LOCK-"; + public static final String MULE_SERVER_PORT_ATTRIB_NAME = "MULE_SERVER_PORT"; + public static final String TRANSFORM_METHOD = "transform"; + public static final String HANDLE_REQUEST_METHOD = "handleRequest"; + private static final String EMPTY = ""; + public static final String LIBRARY_NAME = "MULE-SERVER"; + + public static void processHttpRequestHeader(HttpRequest httpRequest, com.newrelic.api.agent.security.schema.HttpRequest securityRequest) { + for (String headerName : httpRequest.getHeaderNames()) { + boolean takeNextValue = false; + String headerKey = headerName; + if (headerKey != null) { + headerKey = headerKey.toLowerCase(); + } + AgentPolicy agentPolicy = NewRelicSecurity.getAgent().getCurrentPolicy(); + AgentMetaData agentMetaData = NewRelicSecurity.getAgent().getSecurityMetaData().getMetaData(); + if (agentPolicy != null + && agentPolicy.getProtectionMode().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getEnabled() + && agentPolicy.getProtectionMode().getIpBlocking().getIpDetectViaXFF() + && X_FORWARDED_FOR.equals(headerKey)) { + takeNextValue = true; + } else if (ServletHelper.CSEC_IAST_FUZZ_REQUEST_ID.equals(headerKey)) { + // TODO: May think of removing this intermediate obj and directly create K2 Identifier. + NewRelicSecurity.getAgent() + .getSecurityMetaData() + .setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(httpRequest.getHeaderValue(headerKey))); + } else if (GenericHelper.CSEC_PARENT_ID.equals(headerKey)) { + NewRelicSecurity.getAgent().getSecurityMetaData() + .addCustomAttribute(GenericHelper.CSEC_PARENT_ID, httpRequest.getHeaderValue(headerKey)); + } + String headerFullValue = EMPTY; + for (String headerValue : httpRequest.getHeaderValues(headerKey)) { + if (headerValue != null && !headerValue.trim().isEmpty()) { + if (takeNextValue) { + agentMetaData.setClientDetectedFromXFF(true); + securityRequest.setClientIP(headerValue); + agentMetaData.getIps().add(securityRequest.getClientIP()); + securityRequest.setClientPort(EMPTY); + takeNextValue = false; + } + if (headerFullValue.trim().isEmpty()) { + headerFullValue = headerValue; + } else { + headerFullValue = String.join(";", headerFullValue, headerValue); + } + } + } + securityRequest.getHeaders().put(headerKey, headerFullValue); + } + } + + public static String getTraceHeader(Map headers) { + String data = EMPTY; + if (headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER) || headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase())) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER); + if (data == null || data.trim().isEmpty()) { + data = headers.get(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase()); + } + } + return data; + } + public static String getContentType(HttpRequest httpRequest) { + return httpRequest.getHeaderValue(HttpHeaders.Names.CONTENT_TYPE); + } + public static String getNrSecCustomAttribName(int hashcode) { + return MULE_LOCK_CUSTOM_ATTRIB_NAME + Thread.currentThread().getId() + hashcode; + } + +} 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 new file mode 100644 index 000000000..4354ce3dc --- /dev/null +++ b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/HttpRequestToMuleEvent_Instrumentation.java @@ -0,0 +1,125 @@ +package org.mule.module.http.internal.listener; + +import com.newrelic.agent.security.instrumentation.mule37.MuleHelper; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.LowSeverityHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AgentMetaData; +import com.newrelic.api.agent.security.schema.SecurityMetaData; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.RXSSOperation; +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.MuleContext; +import org.mule.api.MuleEvent; +import org.mule.api.construct.FlowConstruct; +import org.mule.module.http.internal.domain.request.HttpRequest; +import org.mule.module.http.internal.domain.request.HttpRequestContext; + +@Weave(type = MatchType.ExactClass, originalName = "org.mule.module.http.internal.listener.HttpRequestToMuleEvent") +public class HttpRequestToMuleEvent_Instrumentation { + public static MuleEvent transform(final HttpRequestContext requestContext, final MuleContext muleContext, final FlowConstruct flowConstruct, Boolean parseRequest, ListenerPath listenerPath) throws HttpRequestParsingException + { + boolean isLockAcquired = acquireLockIfPossible(requestContext.hashCode()); + MuleEvent event; + if (isLockAcquired) { + preprocessSecurityHook(requestContext); + } + try { + event = Weaver.callOriginal(); + } finally { + if (isLockAcquired) { + releaseLock(requestContext.hashCode()); + } + } + if (isLockAcquired) { + postProcessSecurityHook(); + } + return event; + } + + private static void preprocessSecurityHook(HttpRequestContext requestContext) { + try { + if (!NewRelicSecurity.isHookProcessingActive()) { + return; + } + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + + com.newrelic.api.agent.security.schema.HttpRequest securityRequest = securityMetaData.getRequest(); + if (securityRequest.isRequestParsed()) { + return; + } + + AgentMetaData securityAgentMetaData = securityMetaData.getMetaData(); + + HttpRequest httpRequest = requestContext.getRequest(); + securityRequest.setMethod(httpRequest.getMethod()); + securityRequest.setClientIP(requestContext.getClientConnection().getRemoteHostAddress().toString()); + securityRequest.setServerPort( + NewRelicSecurity + .getAgent() + .getSecurityMetaData() + .getCustomAttribute(MuleHelper.MULE_SERVER_PORT_ATTRIB_NAME, Integer.class) + ); + + if (securityRequest.getClientIP() != null && !securityRequest.getClientIP().trim().isEmpty()) { + securityAgentMetaData.getIps().add(securityRequest.getClientIP()); + securityRequest.setClientPort(String.valueOf(requestContext.getClientConnection().getRemoteHostAddress().getPort())); + } + + MuleHelper.processHttpRequestHeader(httpRequest, securityRequest); + securityMetaData.setTracingHeaderValue(MuleHelper.getTraceHeader(securityRequest.getHeaders())); + + securityRequest.setProtocol(requestContext.getScheme()); + securityRequest.setUrl(httpRequest.getUri()); + + // TODO: Create OutBoundHttp data here : Skipping for now. + + securityRequest.setContentType(MuleHelper.getContentType(httpRequest)); + + // TODO: need to update UserClassEntity + ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); + securityRequest.setRequestParsed(true); + } catch (Throwable ignored){} + } + + private static void postProcessSecurityHook() { + try { + if (!NewRelicSecurity.isHookProcessingActive() + ) { + return; + } + //Add request URI hash to low severity event filter + LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); + + RXSSOperation rxssOperation = new RXSSOperation( + NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(), + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse(), + HttpRequestToMuleEvent_Instrumentation.class.getName(), + MuleHelper.TRANSFORM_METHOD + ); + NewRelicSecurity.getAgent().registerOperation(rxssOperation); + ServletHelper.tmpFileCleanUp(NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getTempFiles()); + } catch (Throwable e) { + if(e instanceof NewRelicSecurityException){ + e.printStackTrace(); + throw e; + } + } + } + + private static boolean acquireLockIfPossible(int hashcode) { + try { + return GenericHelper.acquireLockIfPossible(MuleHelper.getNrSecCustomAttribName(hashcode)); + } catch (Throwable ignored) {} + return false; + } + + private static void releaseLock(int hashcode) { + try { + GenericHelper.releaseLock(MuleHelper.getNrSecCustomAttribName(hashcode)); + } catch (Throwable e) {} + } +} 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 new file mode 100644 index 000000000..aa4a4827b --- /dev/null +++ b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/async/RequestHandler_Instrumentation.java @@ -0,0 +1,119 @@ +package org.mule.module.http.internal.listener.async; + +import com.newrelic.agent.security.instrumentation.mule37.MuleHelper; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.LowSeverityHelper; +import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper; +import com.newrelic.api.agent.security.schema.AgentMetaData; +import com.newrelic.api.agent.security.schema.SecurityMetaData; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.RXSSOperation; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.mule.module.http.internal.domain.request.HttpRequest; +import org.mule.module.http.internal.domain.request.HttpRequestContext; + +@Weave(type = MatchType.Interface, originalName = "org.mule.module.http.internal.listener.async.RequestHandler") +public class RequestHandler_Instrumentation { + public void handleRequest(HttpRequestContext requestContext, HttpResponseReadyCallback responseCallback) { + boolean isLockAcquired = acquireLockIfPossible(requestContext.hashCode()); + if (isLockAcquired) { + preprocessSecurityHook(requestContext); + } + try { + Weaver.callOriginal(); + } finally { + if (isLockAcquired) { + releaseLock(requestContext.hashCode()); + } + } + if (isLockAcquired) { + postProcessSecurityHook(); + } + } + + private void preprocessSecurityHook(HttpRequestContext requestContext) { + try { + if (!NewRelicSecurity.isHookProcessingActive()) { + return; + } + SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData(); + + com.newrelic.api.agent.security.schema.HttpRequest securityRequest = securityMetaData.getRequest(); + if (securityRequest.isRequestParsed()) { + return; + } + + AgentMetaData securityAgentMetaData = securityMetaData.getMetaData(); + + HttpRequest httpRequest = requestContext.getRequest(); + securityRequest.setMethod(httpRequest.getMethod()); + securityRequest.setClientIP(requestContext.getClientConnection().getRemoteHostAddress().toString()); + securityRequest.setServerPort( + NewRelicSecurity + .getAgent() + .getSecurityMetaData() + .getCustomAttribute(MuleHelper.MULE_SERVER_PORT_ATTRIB_NAME, Integer.class) + ); + + if (securityRequest.getClientIP() != null && !securityRequest.getClientIP().trim().isEmpty()) { + securityAgentMetaData.getIps().add(securityRequest.getClientIP()); + securityRequest.setClientPort(String.valueOf(requestContext.getClientConnection().getRemoteHostAddress().getPort())); + } + + MuleHelper.processHttpRequestHeader(httpRequest, securityRequest); + securityMetaData.setTracingHeaderValue(MuleHelper.getTraceHeader(securityRequest.getHeaders())); + + securityRequest.setProtocol(requestContext.getScheme()); + securityRequest.setUrl(httpRequest.getUri()); + + // TODO: Create OutBoundHttp data here : Skipping for now. + + securityRequest.setContentType(MuleHelper.getContentType(httpRequest)); + + // TODO: need to update UserClassEntity + ServletHelper.registerUserLevelCode(MuleHelper.LIBRARY_NAME); + securityRequest.setRequestParsed(true); + } catch (Throwable ignored){} + } + + private void postProcessSecurityHook() { + try { + if (!NewRelicSecurity.isHookProcessingActive() + ) { + return; + } + //Add request URI hash to low severity event filter + LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest()); + + RXSSOperation rxssOperation = new RXSSOperation( + NewRelicSecurity.getAgent().getSecurityMetaData().getRequest(), + NewRelicSecurity.getAgent().getSecurityMetaData().getResponse(), + this.getClass().getName(), + MuleHelper.HANDLE_REQUEST_METHOD + ); + NewRelicSecurity.getAgent().registerOperation(rxssOperation); + ServletHelper.tmpFileCleanUp(NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getTempFiles()); + } catch (Throwable e) { + if(e instanceof NewRelicSecurityException){ + e.printStackTrace(); + throw e; + } + } + } + + private boolean acquireLockIfPossible(int hashcode) { + try { + return GenericHelper.acquireLockIfPossible(MuleHelper.getNrSecCustomAttribName(hashcode)); + } catch (Throwable ignored) {} + return false; + } + + private void releaseLock(int hashcode) { + try { + GenericHelper.releaseLock(MuleHelper.getNrSecCustomAttribName(hashcode)); + } catch (Throwable e) {} + } +} diff --git a/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/grizzly/GrizzlyRequestDispatcherFilter_Instrumentation.java b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/grizzly/GrizzlyRequestDispatcherFilter_Instrumentation.java new file mode 100644 index 000000000..fdacccb37 --- /dev/null +++ b/instrumentation-security/mule-3.7/src/main/java/org/mule/module/http/internal/listener/grizzly/GrizzlyRequestDispatcherFilter_Instrumentation.java @@ -0,0 +1,26 @@ +package org.mule.module.http.internal.listener.grizzly; + +import com.newrelic.agent.security.instrumentation.mule37.MuleHelper; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import org.glassfish.grizzly.filterchain.FilterChainContext; +import org.glassfish.grizzly.filterchain.NextAction; + +import java.io.IOException; +import java.net.InetSocketAddress; + +@Weave(type = MatchType.ExactClass, originalName = "org.mule.module.http.internal.listener.grizzly.GrizzlyRequestDispatcherFilter") +public class GrizzlyRequestDispatcherFilter_Instrumentation { + public NextAction handleRead(final FilterChainContext ctx) throws IOException { + try { + NewRelicSecurity.getAgent().getSecurityMetaData() + .addCustomAttribute( + MuleHelper.MULE_SERVER_PORT_ATTRIB_NAME, + ((InetSocketAddress)ctx.getConnection().getLocalAddress()).getPort() + ); + } catch (Exception ignored){} + return Weaver.callOriginal(); + } +} diff --git a/settings.gradle b/settings.gradle index 91de26086..977443552 100644 --- a/settings.gradle +++ b/settings.gradle @@ -155,3 +155,4 @@ include 'instrumentation:commons-jxpath' //include 'instrumentation:apache-wicket-8.0' include 'instrumentation:async-http-client-2.0.0' include 'instrumentation:sun-net-httpserver' +include 'instrumentation:mule-3.7' \ No newline at end of file