Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instrumentation of Netty Server Support #202

Merged
merged 11 commits into from
Apr 1, 2024
2 changes: 1 addition & 1 deletion instrumentation-security/netty-4.0.0/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jar {
}

verifyInstrumentation {
passesOnly 'io.netty:netty-all:[4.0.0.Final,5.0.0.Alpha1)'
passesOnly 'io.netty:netty-all:[4.0.0.Final,4.0.8.Final)'
}

site {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package security.io.netty400.bootstrap;

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 io.netty.channel.ChannelFuture;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

@Weave(type = MatchType.ExactClass, originalName = "io.netty.bootstrap.AbstractBootstrap")
abstract class AbstractBootstrap_Instrumentation {

@SuppressWarnings("unused")
private ChannelFuture doBind(final SocketAddress localAddress) {
if (localAddress instanceof InetSocketAddress) {
int port = ((InetSocketAddress) localAddress).getPort();
NewRelicSecurity.getAgent().setApplicationConnectionConfig(port, "http");
}
return Weaver.callOriginal();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpRequest;
import security.io.netty400.utils.NettyUtils;

@Weave(type = MatchType.Interface, originalName = "io.netty.channel.ChannelInboundHandler")
public abstract class ChannelInboundHandler_Instrumentation {

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean isLockAcquired = NettyUtils.acquireNettyLockIfPossible();
boolean isLockAcquired = false;
if (msg instanceof HttpRequest || msg instanceof HttpContent){
isLockAcquired = NettyUtils.acquireNettyLockIfPossible(NettyUtils.NR_SEC_NETTY_OPERATIONAL_LOCK);
}
if (isLockAcquired) {
NettyUtils.processSecurityRequest(ctx, msg, getClass().getName());
if (!StringUtils.startsWith(getClass().getName(), NettyUtils.IO_NETTY)) {
Expand All @@ -30,7 +35,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
Weaver.callOriginal();
} finally {
if (isLockAcquired) {
NettyUtils.releaseNettyLock();
NettyUtils.releaseNettyLock(NettyUtils.NR_SEC_NETTY_OPERATIONAL_LOCK);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@
import com.newrelic.api.agent.weaver.Weaver;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpRequest;
import security.io.netty400.utils.NettyUtils;

@Weave(type = MatchType.Interface, originalName = "io.netty.channel.ChannelOutboundHandler")
public abstract class ChannelOutboundHandler_Instrumentation {

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
boolean isLockAcquired = NettyUtils.acquireNettyLockIfPossible();
boolean isLockAcquired = false;
if (msg instanceof FullHttpResponse){
isLockAcquired = NettyUtils.acquireNettyLockIfPossible(NettyUtils.NR_SEC_NETTY_OPERATIONAL_LOCK_OUTBOUND);
}
if (isLockAcquired) {
NettyUtils.processSecurityResponse(ctx, msg);
NettyUtils.sendRXSSEvent(ctx, msg, getClass().getName(), NettyUtils.WRITE_METHOD_NAME);
Expand All @@ -27,7 +33,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
Weaver.callOriginal();
} finally {
if (isLockAcquired) {
NettyUtils.releaseNettyLock();
NettyUtils.releaseNettyLock(NettyUtils.NR_SEC_NETTY_OPERATIONAL_LOCK_OUTBOUND);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package security.io.netty400.channel;

import com.newrelic.api.agent.security.instrumentation.helpers.ServletHelper;
import com.newrelic.api.agent.security.schema.StringUtils;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import io.netty.channel.ChannelHandlerContext;
import security.io.netty400.utils.NettyUtils;

@Weave(type = MatchType.BaseClass, originalName = "io.netty.channel.SimpleChannelInboundHandler")
public class SimpleChannelInboundHandler_Instrumentation<I> {

protected void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception {
try {
if (!StringUtils.startsWith(getClass().getName(), NettyUtils.IO_NETTY)) {
ServletHelper.registerUserLevelCode(NettyUtils.IO_NETTY);
}
} catch (Exception e){
}
Weaver.callOriginal();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package security.io.netty400.utils;

import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Transaction;
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;
Expand All @@ -12,9 +10,11 @@
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 io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;

Expand All @@ -25,23 +25,24 @@
import java.util.Set;

public class NettyUtils {
public static final String NETTY_4_0_0 = "NETTY-4.0.0";
public static String NR_SEC_CUSTOM_ATTRIB_NAME = "NETTY-4.8-REQ-BODY-TRACKER";
public static String NR_SEC_NETTY_OPERATIONAL_LOCK = "NR_SEC_NETTY_OPERATIONAL_LOCK";
public static String NR_SEC_NETTY_OPERATIONAL_LOCK = "NR_SEC_NETTY_OPERATIONAL_LOCK_INBOUND";
public static String NR_SEC_NETTY_OPERATIONAL_LOCK_OUTBOUND = "NR_SEC_NETTY_OPERATIONAL_LOCK_OUTBOUND";
private static final String X_FORWARDED_FOR = "x-forwarded-for";
private static final String EMPTY = "";
public static final String WRITE_METHOD_NAME = "write";

public static final String IO_NETTY = "io.netty.";
private static final String ERROR_GETTING_SERVER_PORT = "Instrumentation library: %s , error while getting server port %s";
private static final String ERROR_PARSING_HTTP_RESPONSE_DATA = "Instrumentation library: %s , error while parsing HTTP response data : %s";

public static void processSecurityRequest(ChannelHandlerContext ctx, Object msg, String className) {
try {
Transaction tx = NewRelic.getAgent().getTransaction();
Object secMetaObj = tx.getSecurityMetaData();
if (!NewRelicSecurity.isHookProcessingActive()) {
return;
}
if (msg instanceof HttpRequest) {
if (!(secMetaObj instanceof SecurityMetaData) ||
NewRelicSecurity.getAgent().getSecurityMetaData() == null) {
return;
}
SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData();
com.newrelic.api.agent.security.schema.HttpRequest securityRequest =
securityMetaData.getRequest();
Expand All @@ -56,16 +57,15 @@ public static void processSecurityRequest(ChannelHandlerContext ctx, Object msg,
processHttpRequestHeader((HttpRequest)msg, securityRequest);
securityMetaData.setTracingHeaderValue(getTraceHeader(securityRequest.getHeaders()));

securityRequest.setProtocol(((HttpRequest) msg).getProtocolVersion().protocolName());
securityRequest.setProtocol(((HttpRequest) msg).getProtocolVersion().protocolName().toLowerCase());
securityRequest.setContentType(securityRequest.getHeaders().get("content-type"));
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(stack, 2, stack.length));
securityRequest.setRequestParsed(true);
} else if (msg instanceof HttpContent) {
if (!(secMetaObj instanceof SecurityMetaData) ||
NewRelicSecurity.getAgent().getSecurityMetaData() == null) {
return;
if (!securityMetaData.getMetaData().isUserLevelServiceMethodEncountered(IO_NETTY)){
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(stack, 2, stack.length));
}
securityRequest.setRequestParsed(true);
}
if (msg instanceof HttpContent) {
Integer reqBodyTrackerContextId = NewRelicSecurity.getAgent().getSecurityMetaData()
.getCustomAttribute(NR_SEC_CUSTOM_ATTRIB_NAME, Integer.class);
if (reqBodyTrackerContextId == null) {
Expand All @@ -80,7 +80,7 @@ public static void processSecurityRequest(ChannelHandlerContext ctx, Object msg,
}
}
} catch (Throwable ignored) {
ignored.printStackTrace();
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_PARSING_HTTP_REQUEST_DATA, NETTY_4_0_0, ignored.getMessage()), ignored, NettyUtils.class.getName());
}
}

Expand All @@ -91,7 +91,9 @@ private static void setServerPortDetails(com.newrelic.api.agent.security.schema.
return;
}
securityRequest.setServerPort(Integer.parseInt(port));
} catch (Throwable throwable) {}
} catch (Throwable throwable) {
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(ERROR_GETTING_SERVER_PORT, NETTY_4_0_0, throwable.getMessage()), throwable, NettyUtils.class.getName());
}
}

private static void setClientAddressDetails(SecurityMetaData securityMetaData, String address) {
Expand Down Expand Up @@ -167,22 +169,16 @@ public static String getTraceHeader(Map<String, String> headers) {

public static void processSecurityResponse(ChannelHandlerContext ctx, Object msg) {
try {
Transaction tx = NewRelic.getAgent().getTransaction();
Object secMetaObj = tx.getSecurityMetaData();
if (msg instanceof FullHttpResponse) {
if (!(secMetaObj instanceof SecurityMetaData) ||
NewRelicSecurity.getAgent().getSecurityMetaData() == null) {
return;
}
if (NewRelicSecurity.isHookProcessingActive() && msg instanceof FullHttpResponse) {
SecurityMetaData securityMetaData = NewRelicSecurity.getAgent().getSecurityMetaData();
com.newrelic.api.agent.security.schema.HttpResponse securityResponse =
securityMetaData.getResponse();
processResponseHeaders((HttpResponse) msg, securityResponse);
securityResponse.setResponseContentType(((FullHttpResponse) msg).headers().get("content-type"));
securityResponse.setResponseContentType(((FullHttpResponse) msg).headers().get(HttpHeaders.Names.CONTENT_TYPE));
securityResponse.getResponseBody().append(((FullHttpResponse) msg).content().toString(StandardCharsets.UTF_8));
}
} catch (Throwable e) {
e.printStackTrace();
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(ERROR_PARSING_HTTP_RESPONSE_DATA, NETTY_4_0_0, e.getMessage()), e, NettyUtils.class.getName());
}
}

Expand All @@ -203,9 +199,11 @@ public static void sendRXSSEvent(ChannelHandlerContext ctx, Object msg, String c
ServletHelper.tmpFileCleanUp(NewRelicSecurity.getAgent().getSecurityMetaData().getFuzzRequestIdentifier().getTempFiles());
} catch (Throwable e) {
if (e instanceof NewRelicSecurityException) {
e.printStackTrace();
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, NETTY_4_0_0, e.getMessage()), e, NettyUtils.class.getName());
throw e;
}
NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, NETTY_4_0_0, e.getMessage()), e, NettyUtils.class.getName());
NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, NETTY_4_0_0, e.getMessage()), e, NettyUtils.class.getName());
}
}

Expand All @@ -217,34 +215,30 @@ private static void processResponseHeaders(HttpResponse response, com.newrelic.a
}
}

public static boolean isNettyLockAcquired() {
public static boolean isNettyLockAcquired(String operationLock) {
try {
return NewRelicSecurity.isHookProcessingActive() &&
Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(getNrSecOperationalLockName(), Boolean.class));
Boolean.TRUE.equals(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(operationLock + Thread.currentThread().getId(), Boolean.class));
} catch (Throwable ignored) {}
return false;
}

public static boolean acquireNettyLockIfPossible() {
public static boolean acquireNettyLockIfPossible(String operationLock) {
try {
if (NewRelicSecurity.isHookProcessingActive() &&
!isNettyLockAcquired()) {
NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecOperationalLockName(), true);
!isNettyLockAcquired(operationLock)) {
NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(operationLock + Thread.currentThread().getId(), true);
return true;
}
} catch (Throwable ignored){}
return false;
}

public static void releaseNettyLock() {
public static void releaseNettyLock(String operationLock) {
try {
if(NewRelicSecurity.isHookProcessingActive()) {
NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecOperationalLockName(), null);
NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(operationLock + Thread.currentThread().getId(), null);
}
} catch (Throwable ignored){}
}

private static String getNrSecOperationalLockName() {
return NR_SEC_NETTY_OPERATIONAL_LOCK + Thread.currentThread().getId();
}
}
30 changes: 30 additions & 0 deletions instrumentation-security/netty-4.0.8/.snyk
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
SNYK-JAVA-IONETTY-559515:
- '*':
reason: Instrumentation false positive
expires: 2038-01-19T00:00:00.000Z
created: 2022-11-03T16:37:15.223Z
SNYK-JAVA-IONETTY-559516:
- '*':
reason: Instrumentation false positive
expires: 2038-01-19T00:00:00.000Z
created: 2022-11-03T16:37:19.008Z
SNYK-JAVA-IONETTY-73571:
- '*':
reason: Instrumentation false positive
expires: 2038-01-19T00:00:00.000Z
created: 2022-11-03T16:37:22.194Z
SNYK-JAVA-ORGSCALALANG-3032987:
- '*':
reason: Instrumentation false positive
expires: 2038-01-19T00:00:00.000Z
created: 2022-11-03T16:37:25.428Z
SNYK-JAVA-IONETTY-473214:
- '*':
reason: Instrumentation false positive
expires: 2038-01-19T00:00:00.000Z
created: 2022-11-11T14:10:14.681Z
patch: {}
21 changes: 21 additions & 0 deletions instrumentation-security/netty-4.0.8/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
dependencies {
implementation(project(":newrelic-security-api"))
implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}")
implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}")
implementation("io.netty:netty-all:4.0.8.Final")
}

jar {
manifest {
attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.netty-4.0.8'
}
}

verifyInstrumentation {
passesOnly 'io.netty:netty-all:[4.0.8.Final,5.0.0.Alpha1)'
}

site {
title 'Netty'
type 'Appserver'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package security.io.netty400.bootstrap;

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 io.netty.channel.ChannelFuture;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

@Weave(type = MatchType.ExactClass, originalName = "io.netty.bootstrap.AbstractBootstrap")
public abstract class AbstractBootstrap_Instrumentation {

@SuppressWarnings("unused")
private ChannelFuture doBind(final SocketAddress localAddress) {
if (localAddress instanceof InetSocketAddress) {
int port = ((InetSocketAddress) localAddress).getPort();
NewRelicSecurity.getAgent().setApplicationConnectionConfig(port, "http");
}
return Weaver.callOriginal();
}

}
Loading
Loading