Skip to content

Commit

Permalink
[NR-277770] Response interception support in mule server (#298)
Browse files Browse the repository at this point in the history
* NR-277770 : Response interception in mule server
  • Loading branch information
IshikaDawda authored Nov 6, 2024
1 parent d8aa936 commit 3873293
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,39 @@
import com.newrelic.api.agent.security.instrumentation.helpers.URLMappingsHelper;
import com.newrelic.api.agent.security.schema.AgentMetaData;
import com.newrelic.api.agent.security.schema.ApplicationURLMapping;
import com.newrelic.api.agent.security.schema.Framework;
import com.newrelic.api.agent.security.schema.policy.AgentPolicy;
import com.newrelic.api.agent.security.utils.logging.LogLevel;
import org.mule.api.processor.MessageProcessor;
import org.mule.module.http.api.HttpHeaders;
import org.mule.module.http.api.listener.HttpListener;
import org.mule.module.http.internal.domain.request.HttpRequest;
import org.mule.module.http.internal.domain.response.HttpResponse;
import org.mule.processor.InvokerMessageProcessor;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.mule.module.http.api.HttpHeaders.Names.X_FORWARDED_FOR;

public class MuleHelper {
public static final String MULE_36 = "MULE-3.6";
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 RESPONSE_OUTPUTSTREAM_HASH = "RESPONSE_OUTPUTSTREAM_HASH";
public static final String REQUEST_INPUTSTREAM_HASH = "REQUEST_INPUTSTREAM_HASH";
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";
private static final Map<Integer, String> handlerMap = new HashMap<>();
public static final String MULE_3_6 = "MULE-3.6";
public static final String RESPONSE_ENTITY_STREAM = "RESPONSE_ENTITY_STREAM";
public static final String REQUEST_ENTITY_STREAM = "REQUEST_ENTITY_STREAM";
public static final String MULE_ENCODING = "MULE_ENCODING";

public static void processHttpRequestHeader(HttpRequest httpRequest, com.newrelic.api.agent.security.schema.HttpRequest securityRequest){

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;
Expand All @@ -48,25 +52,21 @@ public static void processHttpRequestHeader(HttpRequest httpRequest,
&& agentPolicy.getProtectionMode().getEnabled()
&& agentPolicy.getProtectionMode().getIpBlocking().getEnabled()
&& agentPolicy.getProtectionMode().getIpBlocking().getIpDetectViaXFF()
&& X_FORWARDED_FOR.equals(headerKey)) {
&& X_FORWARDED_FOR.toLowerCase().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)));
NewRelicSecurity.getAgent().getSecurityMetaData()
.setFuzzRequestIdentifier(ServletHelper.parseFuzzRequestIdentifierHeader(httpRequest.getHeaderValue(headerName)));
} else if (GenericHelper.CSEC_PARENT_ID.equals(headerKey)) {
NewRelicSecurity
.getAgent()
.getSecurityMetaData()
.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, httpRequest.getHeaderValue(headerKey));
NewRelicSecurity.getAgent().getSecurityMetaData()
.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, httpRequest.getHeaderValue(headerName));
} else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST.equals(headerKey)) {
NewRelicSecurity.getAgent().getSecurityMetaData()
.addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true);
}
String headerFullValue = EMPTY;
for (String headerValue : httpRequest.getHeaderValues(headerKey)) {
for (String headerValue : httpRequest.getHeaderValues(headerName)) {
if (headerValue != null && !headerValue.trim().isEmpty()) {
if (takeNextValue) {
agentMetaData.setClientDetectedFromXFF(true);
Expand All @@ -86,6 +86,10 @@ public static void processHttpRequestHeader(HttpRequest httpRequest,
}
}

public static String getNrSecCustomAttribName(String customAttribute) {
return customAttribute + Thread.currentThread().getId();
}

public static String getTraceHeader(Map<String, String> headers) {
String data = EMPTY;
if (headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER) || headers.containsKey(ServletHelper.CSEC_DISTRIBUTED_TRACING_HEADER.toLowerCase())) {
Expand All @@ -96,13 +100,20 @@ public static String getTraceHeader(Map<String, String> headers) {
}
return data;
}
public static String getContentType(HttpRequest httpRequest) {
return httpRequest.getHeaderValue(HttpHeaders.Names.CONTENT_TYPE);

public static String getContentType(Map<String, String> headers) {
String data = EMPTY;
if (headers.containsKey(HttpHeaders.Names.CONTENT_TYPE.toLowerCase())) {
data = headers.get(HttpHeaders.Names.CONTENT_TYPE.toLowerCase());
}
return data;
}
public static String getNrSecCustomAttribName(int hashcode) {
return MULE_LOCK_CUSTOM_ATTRIB_NAME + Thread.currentThread().getId() + hashcode;

public static String getNrSecCustomAttribName() {
return MULE_LOCK_CUSTOM_ATTRIB_NAME + Thread.currentThread().getId();
}


public static void gatherURLMappings(HttpListener messageSource, List<MessageProcessor> messageProcessors) {
try {
String path = messageSource.getPath();
Expand All @@ -117,24 +128,47 @@ public static void gatherURLMappings(HttpListener messageSource, List<MessagePro
URLMappingsHelper.addApplicationURLMapping(new ApplicationURLMapping(method, path, handlerClass));
}
}
} catch (Exception ignored){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_WHILE_GETTING_APP_ENDPOINTS, MULE_3_6, ignored.getMessage()), ignored, MuleHelper.class.getName());
}
}catch (Exception ignored){}
}

public static Map<Integer, String> getHandlerMap() {
return handlerMap;
}

// route detection
public static void setRequestRoute(String listenerPath) {
if (NewRelicSecurity.isHookProcessingActive()) {
try {
NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().setRoute(listenerPath);
NewRelicSecurity.getAgent().getSecurityMetaData().getMetaData().setFramework(Framework.MULE);
} catch (Exception e) {
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_WHILE_GETTING_ROUTE_FOR_INCOMING_REQUEST, MULE_3_6, e.getMessage()), e, MuleHelper.class.getName());
public static void registerStreamHashIfNeeded(int streamHash, String key){
try {
Set<Integer> hashSet = NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(key, Set.class);
if (hashSet == null) {
hashSet = new HashSet<>();
NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(key, hashSet);
}
hashSet.add(streamHash);
} catch (Throwable ignored) {}
}

public static boolean preprocessStream(int streamHash, String key){
try {
Set<Integer> hashSet = NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(key, Set.class);
if(hashSet != null && hashSet.contains(streamHash)){
return true;
}
} catch (Throwable ignored) {}
return false;
}

public static void processHttpResponseHeaders(com.newrelic.api.agent.security.schema.HttpResponse securityResponse, HttpResponse response){
for (String headerKey : response.getHeaderNames()) {
String headerFullValue = EMPTY;
for (String headerValue : response.getHeaderValues(headerKey)) {
if (headerValue != null && !headerValue.trim().isEmpty()) {
if (headerFullValue.trim().isEmpty()) {
headerFullValue = headerValue;
} else {
headerFullValue = String.join(";", headerFullValue, headerValue);
}
}
}
securityResponse.getHeaders().put(headerKey.toLowerCase(), headerFullValue);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.mule.module.http.internal.domain;

import com.newrelic.agent.security.instrumentation.mule36.MuleHelper;
import com.newrelic.api.agent.security.NewRelicSecurity;
import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper;
import com.newrelic.api.agent.security.schema.SecurityMetaData;
import com.newrelic.api.agent.security.utils.logging.LogLevel;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import org.apache.commons.io.Charsets;
import org.mule.util.IOUtils;

import java.io.IOException;
import java.util.Objects;

@Weave(originalName = "org.mule.module.http.internal.domain.ByteArrayHttpEntity")
public class ByteArrayHttpEntity_Instrumentation {

public byte[] getContent() {
byte[] content = Weaver.callOriginal();
try {
extractResponseBody(content);
} catch (Exception e) {
NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.ERROR_PARSING_HTTP_RESPONSE_BODY, MuleHelper.MULE_36, e.getMessage()), e, this.getClass().getName());
NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE , String.format(GenericHelper.ERROR_PARSING_HTTP_RESPONSE_BODY, MuleHelper.MULE_36, e.getMessage()), e, this.getClass().getName());
}
return content;
}

private void extractResponseBody(byte[] content) throws IOException {
if (NewRelicSecurity.isHookProcessingActive()){
String encoding = NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(MuleHelper.MULE_ENCODING, String.class);
if (encoding == null || encoding.isEmpty()){
encoding = Charsets.UTF_8.name();
}
String body = IOUtils.toString(content, encoding);

SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData();
if (MuleHelper.preprocessStream(this.hashCode(), MuleHelper.RESPONSE_ENTITY_STREAM)) {
securityMetaData.getResponse().getResponseBody().append(body);
} else if (MuleHelper.preprocessStream(this.hashCode(), MuleHelper.REQUEST_ENTITY_STREAM)) {
securityMetaData.getRequest().getBody().append(body);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.mule.module.http.internal.domain;

import com.newrelic.agent.security.instrumentation.mule36.MuleHelper;
import com.newrelic.api.agent.security.NewRelicSecurity;
import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper;
import com.newrelic.api.agent.security.schema.SecurityMetaData;
import com.newrelic.api.agent.security.utils.logging.LogLevel;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

import java.io.InputStream;
import java.util.Objects;

@Weave(originalName = "org.mule.module.http.internal.domain.InputStreamHttpEntity")
public class InputStreamHttpEntity_Instrumentation {

public InputStream getInputStream() {
InputStream stream = Weaver.callOriginal();
try {
extractResponseBody(stream);
} catch (Exception e) {
NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.ERROR_PARSING_HTTP_RESPONSE_BODY, MuleHelper.MULE_36, e.getMessage()), e, this.getClass().getName());
NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE , String.format(GenericHelper.ERROR_PARSING_HTTP_RESPONSE_BODY, MuleHelper.MULE_36, e.getMessage()), e, this.getClass().getName());
}
return stream;
}

private void extractResponseBody(InputStream stream) {
if (NewRelicSecurity.isHookProcessingActive() && stream != null) {
// check if it is an input or output stream
// outputBody stream
if (MuleHelper.preprocessStream(this.hashCode(), MuleHelper.RESPONSE_ENTITY_STREAM)) {
MuleHelper.registerStreamHashIfNeeded(stream.hashCode(), MuleHelper.RESPONSE_OUTPUTSTREAM_HASH);
}
// inputBody stream
else if (MuleHelper.preprocessStream(this.hashCode(), MuleHelper.REQUEST_ENTITY_STREAM)) {
MuleHelper.registerStreamHashIfNeeded(stream.hashCode(), MuleHelper.REQUEST_INPUTSTREAM_HASH);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,43 @@

package org.mule.module.http.internal.domain.response;

import com.newrelic.agent.security.instrumentation.mule36.MuleHelper;
import com.newrelic.api.agent.security.NewRelicSecurity;
import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper;
import com.newrelic.api.agent.security.utils.logging.LogLevel;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import org.mule.module.http.internal.domain.HttpEntity;

@Weave(type = MatchType.ExactClass, originalName = "org.mule.module.http.internal.domain.response.HttpResponseBuilder")
public class HttpResponseBuilder_Instrumentation {

private ResponseStatus responseStatus = Weaver.callOriginal();
private HttpEntity body = Weaver.callOriginal();

public HttpResponse build() {
NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseCode(responseStatus.getStatusCode());
return Weaver.callOriginal();
HttpResponse response = Weaver.callOriginal();
postProcessSecurityHook(response);
return response;
}



private void postProcessSecurityHook(HttpResponse response) {
try {
if (!NewRelicSecurity.isHookProcessingActive()) {
return;
}
if (body != null) {
MuleHelper.registerStreamHashIfNeeded(body.hashCode(), MuleHelper.RESPONSE_ENTITY_STREAM);
}
com.newrelic.api.agent.security.schema.HttpResponse securityResponse = NewRelicSecurity.getAgent().getSecurityMetaData().getResponse();

MuleHelper.processHttpResponseHeaders(securityResponse, response);
securityResponse.setResponseCode(response.getStatusCode());
securityResponse.setResponseContentType(MuleHelper.getContentType(securityResponse.getHeaders()));
} catch (Throwable e) {
NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.ERROR_PARSING_HTTP_RESPONSE, MuleHelper.MULE_36, e.getMessage()), e, this.getClass().getName());
NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE , String.format(GenericHelper.ERROR_PARSING_HTTP_RESPONSE, MuleHelper.MULE_36, e.getMessage()), e, this.getClass().getName());
}
}
}
Loading

0 comments on commit 3873293

Please sign in to comment.