Skip to content

Commit

Permalink
Merge pull request #106 from newrelic/feature/jetty-12
Browse files Browse the repository at this point in the history
Support for Jetty-12
  • Loading branch information
lovesh-ap authored Dec 26, 2023
2 parents 98ad750 + 955f2fe commit 5bf2cd5
Show file tree
Hide file tree
Showing 6 changed files with 622 additions and 0 deletions.
28 changes: 28 additions & 0 deletions instrumentation-security/jetty-12/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
dependencies {
implementation(project(":newrelic-security-api"))
implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}")
implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}")
implementation("org.eclipse.jetty:jetty-server:12.0.0")
testImplementation('jakarta.servlet:jakarta.servlet-api:6.0.0')
testImplementation("org.eclipse.jetty:jetty-servlet:11.0.16")
}

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.jetty-12', 'Priority': '10' }
}

verifyInstrumentation {
passesOnly 'org.eclipse.jetty:jetty-server:[12.0.0,)'
excludeRegex '.*(alpha|beta|rc).*'
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}

site {
title 'Jetty'
type 'Appserver'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package com.newrelic.agent.security.instrumentation.jetty12.server;

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.HttpRequest;
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.security.schema.policy.AgentPolicy;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class HttpServletHelper {

private static final String X_FORWARDED_FOR = "x-forwarded-for";
private static final String EMPTY = "";
public static final String QUESTION_MARK = "?";
public static final String SERVICE_METHOD_NAME = "handle";
public static final String SERVICE_ASYNC_METHOD_NAME = "handleAsync";

public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "JETTY_SERVLET_LOCK-";

public static void processHttpRequestHeader(Request request, HttpRequest securityRequest) {
HttpFields headers = request.getHeaders();
if (headers!=null){
Set<String> headerKeys = headers.getFieldNamesCollection();
Iterator<String> headerKeysIterator = headerKeys.iterator();
while(headerKeysIterator.hasNext()){
boolean takeNextValue = false;
String headerKey = headerKeysIterator.next();
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(request.getHeaders().get(headerKey)));
} else if(GenericHelper.CSEC_PARENT_ID.equals(headerKey)) {
NewRelicSecurity.getAgent().getSecurityMetaData()
.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, request.getHeaders().get(headerKey));
}

String headerFullValue = EMPTY;
String headerValue = request.getHeaders().get(headerKey);

if (headerValue != null && !headerValue.trim().isEmpty()) {
if (takeNextValue) {
agentMetaData.setClientDetectedFromXFF(true);
securityRequest.setClientIP(headerValue);
agentMetaData.getIps()
.add(securityRequest.getClientIP());
securityRequest.setClientPort(EMPTY);
}
if (headerFullValue.trim().isEmpty()) {
headerFullValue = headerValue;
} else {
headerFullValue = String.join(";", headerFullValue, headerValue);
}
}
securityRequest.getHeaders().put(headerKey, headerFullValue);
}
}
}

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())) {
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 boolean isServletLockAcquired() {
try {
return NewRelicSecurity.isHookProcessingActive() &&
Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecCustomAttribName(), Boolean.class));
} catch (Throwable ignored) {
}
return false;
}

public static boolean acquireServletLockIfPossible() {
try {
if (NewRelicSecurity.isHookProcessingActive() &&
!isServletLockAcquired()) {
NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true);
return true;
}
} catch (Throwable ignored) {
}
return false;
}

public static void releaseServletLock() {
try {
if (NewRelicSecurity.isHookProcessingActive()) {
NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), null);
}
} catch (Throwable ignored) {
}
}

private static String getNrSecCustomAttribName() {
return NR_SEC_CUSTOM_ATTRIB_NAME;
}

public static void preprocessSecurityHook(Request request) {
try {
if (!NewRelicSecurity.isHookProcessingActive() || request == null) {
return;
}
SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData();

HttpRequest securityRequest = securityMetaData.getRequest();
if (securityRequest.isRequestParsed()) {
return;
}

AgentMetaData securityAgentMetaData = securityMetaData.getMetaData();

securityRequest.setMethod(request.getMethod());
securityRequest.setClientIP(Request.getRemoteAddr(request));
securityRequest.setServerPort(Request.getLocalPort(request));

if (securityRequest.getClientIP() != null && !securityRequest.getClientIP().trim().isEmpty()) {
securityAgentMetaData.getIps().add(securityRequest.getClientIP());
securityRequest.setClientPort(String.valueOf(Request.getRemotePort(request)));
}

HttpServletHelper.processHttpRequestHeader(request, securityRequest);

securityMetaData.setTracingHeaderValue(HttpServletHelper.getTraceHeader(securityRequest.getHeaders()));

securityRequest.setProtocol(request.getHttpURI().getScheme());

// TODO: Create OutBoundHttp data here : Skipping for now.

String url = request.getHttpURI().asString();
if (url != null && !url.trim().isEmpty()) {
securityRequest.setUrl(url);
}
securityRequest.setContentType(request.getHeaders().get(HttpHeader.CONTENT_TYPE));

StackTraceElement[] trace = Thread.currentThread().getStackTrace();
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length));
securityRequest.setRequestParsed(true);
} catch (Throwable ignored) {
}
}

public static void postProcessSecurityHook(Request request, Response response, String className, String methodName) {
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(),
className, methodName);
NewRelicSecurity.getAgent().registerOperation(rxssOperation);
ServletHelper.tmpFileCleanUp(NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getTempFiles());
} catch (Throwable e) {
if (e instanceof NewRelicSecurityException) {
e.printStackTrace();
throw e;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.newrelic.agent.security.instrumentation.jetty12.server;

import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;

@Weave(type = MatchType.Interface, originalName = "org.eclipse.jetty.server.Request$Handler")
public class RequestHandler_Instrumentation {

public boolean handle(Request request, Response response, Callback callback) {
ServletHelper.registerUserLevelCode("jetty-handle");
boolean isServletLockAcquired = HttpServletHelper.acquireServletLockIfPossible();
if (isServletLockAcquired) {
HttpServletHelper.preprocessSecurityHook(request);
}
boolean result;
try {
result = Weaver.callOriginal();
} finally {
if (isServletLockAcquired) {
HttpServletHelper.releaseServletLock();
}
}
if (isServletLockAcquired) {
HttpServletHelper.postProcessSecurityHook(request, response, this.getClass().getName(), HttpServletHelper.SERVICE_METHOD_NAME);
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.nr.agent.security.instrumentation.jetty12.test;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;

import java.io.BufferedReader;
import java.io.IOException;

public class MyServlet extends Handler.Abstract {
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception {
System.out.println("Request completed!");
callback.succeeded();
return true;
}
}
Loading

0 comments on commit 5bf2cd5

Please sign in to comment.