Skip to content

Commit

Permalink
Merge pull request #268 from newrelic/fix/play-crash-NR-273623
Browse files Browse the repository at this point in the history
NR-273623 Fix for Play Framework Application Crash
  • Loading branch information
IshikaDawda authored Jun 18, 2024
2 parents 3bac53b + 1e12976 commit 84899b3
Show file tree
Hide file tree
Showing 14 changed files with 1,094 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apply plugin: 'scala'

isScalaProjectEnabled(project, "scala-2.12")
isScalaProjectEnabled(project, "scala-2.11")

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.akka-http-core-10.0.11' }
Expand All @@ -10,14 +10,14 @@ dependencies {
implementation(project(":newrelic-security-api"))
implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}")
implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}")
implementation("com.typesafe.akka:akka-http-core_2.12:10.0.11")
implementation("com.typesafe.akka:akka-stream_2.12:2.5.23")
implementation("com.typesafe.akka:akka-http-core_2.11:10.0.11")
implementation("com.typesafe.akka:akka-stream_2.11:2.5.23")
}

verifyInstrumentation {
fails('com.typesafe.akka:akka-http-core-experimental_2.11:[1.0,10.0.11)')
fails('com.typesafe.akka:akka-http-core-experimental_2.10:[1.0,10.0.11)')
passesOnly('com.typesafe.akka:akka-http-core_2.13:[10.1.8,10.2.0-RC1)') {
fails('com.typesafe.akka:akka-http-core_2.13:[10.1.8,10.2.0-RC1)') {
implementation("com.typesafe.akka:akka-stream_2.13:2.5.23")
}
passesOnly('com.typesafe.akka:akka-http-core_2.11:[10.0.11,)') {
Expand Down
32 changes: 32 additions & 0 deletions instrumentation-security/akka-http-core-2.13_10.1.8/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
apply plugin: 'scala'

isScalaProjectEnabled(project, "scala-2.13")

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.akka-http-core-2.13_10.1.8' }
}

dependencies {
implementation(project(":newrelic-security-api"))
implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}")
implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}")
implementation("com.typesafe.akka:akka-http-core_2.13:10.1.8")
implementation("com.typesafe.akka:akka-stream_2.13:2.5.23")
}

verifyInstrumentation {
passesOnly('com.typesafe.akka:akka-http-core_2.13:[10.1.8,10.2.0-RC1)') {
implementation("com.typesafe.akka:akka-stream_2.13:2.5.23")
}
fails('com.typesafe.akka:akka-http-core_2.11:[10.0.11,)') {
implementation("com.typesafe.akka:akka-stream_2.11:2.5.11")
}
fails('com.typesafe.akka:akka-http-core_2.12:[10.0.11,10.2.0-RC1)') {
implementation("com.typesafe.akka:akka-stream_2.12:2.5.11")
}
}

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

package akka.http.scaladsl

import akka.Done
import akka.http.scaladsl.model.{HttpRequest, HttpResponse}
import akka.stream.Materializer
import akka.stream.javadsl.Source
import akka.stream.scaladsl.Sink
import akka.util.ByteString
import com.newrelic.api.agent.{NewRelic, Trace}

import java.lang
import scala.concurrent.{ExecutionContext, Future}
import scala.runtime.AbstractFunction1

class AkkaAsyncRequestHandler(handler: HttpRequest Future[HttpResponse])(implicit ec: ExecutionContext, materializer: Materializer) extends AbstractFunction1[HttpRequest, Future[HttpResponse]] {

@Trace(dispatcher = true)
override def apply(param: HttpRequest): Future[HttpResponse] = {
val body: lang.StringBuilder = new lang.StringBuilder();
val dataBytes: Source[ByteString, AnyRef] = param.entity.getDataBytes()
val isLockAquired = AkkaCoreUtils.acquireServletLockIfPossible();
val sink: Sink[ByteString, Future[Done]] = Sink.foreach[ByteString] { byteString =>
val chunk = byteString.utf8String
body.append(chunk)
}
val processingResult: Future[Done] = dataBytes.runWith(sink, materializer)
AkkaCoreUtils.preProcessHttpRequest(isLockAquired, param, body, NewRelic.getAgent.getTransaction.getToken);
val futureResponse: Future[HttpResponse] = handler.apply(param)
futureResponse.flatMap(ResponseFutureHelper.wrapResponseAsync(NewRelic.getAgent.getTransaction.getToken, materializer))
futureResponse
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package akka.http.scaladsl;

import akka.http.javadsl.model.HttpHeader;
import akka.http.scaladsl.model.HttpRequest;
import com.newrelic.api.agent.Token;
import com.newrelic.api.agent.security.NewRelicSecurity;
import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper;
import com.newrelic.api.agent.security.instrumentation.helpers.ICsecApiConstants;
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.StringUtils;
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 java.util.*;

public class AkkaCoreUtils {

public static final String METHOD_SINGLE_REQUEST_IMPL = "singleRequestImpl";

public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "HTTPREQUEST_OPERATION_LOCK_AKKA-";

public static final String AKKA_HTTP_CORE_10_0_11 = "AKKA_HTTP_CORE_10.0.11";

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

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 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 boolean acquireServletLockIfPossible() {
try {
if (NewRelicSecurity.isHookProcessingActive() &&
!isServletLockAcquired()) {
NewRelicSecurity.getAgent().getSecurityMetaData().addCustomAttribute(getNrSecCustomAttribName(), true);
return true;
}
} catch (Throwable ignored){}
return false;
}

public static void postProcessHttpRequest(Boolean isServletLockAcquired, StringBuilder responseBody, String contentType, String className, String methodName, Token token) {
try {
token.linkAndExpire();
if(!isServletLockAcquired || !NewRelicSecurity.isHookProcessingActive()){
return;
}
NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseContentType(contentType);
NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().setResponseBody(responseBody);
LowSeverityHelper.addRrequestUriToEventFilter(NewRelicSecurity.getAgent().getSecurityMetaData().getRequest());

if(!ServletHelper.isResponseContentTypeExcluded(NewRelicSecurity.getAgent().getSecurityMetaData().getResponse().getResponseContentType())) {
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){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, AkkaCoreUtils.class.getName());
throw e;
}
NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, AkkaCoreUtils.class.getName());
NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AKKA_HTTP_CORE_10_0_11, e.getMessage()), e, AkkaCoreUtils.class.getName());
} finally {
if(isServletLockAcquired){
releaseServletLock();
}
}
}

public static void preProcessHttpRequest (Boolean isServletLockAcquired, HttpRequest httpRequest, StringBuilder requestBody, Token token) {
if(!isServletLockAcquired) {
return;
}

try {
token.linkAndExpire();
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();

securityRequest.setMethod(httpRequest.method().value());
//TODO Client IP and PORT extraction is pending

// securityRequest.setClientIP();
securityRequest.setServerPort(httpRequest.getUri().getPort());

processHttpRequestHeader(httpRequest, securityRequest);

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

securityRequest.setProtocol(getProtocol(httpRequest.protocol().value()));

securityRequest.setUrl(httpRequest.getUri().path());
String queryString = null;
Optional<String> rawQueryString = httpRequest.getUri().rawQueryString();
if(rawQueryString.isPresent()) {
queryString = rawQueryString.get();
}
if (queryString != null && !queryString.trim().isEmpty()) {
securityRequest.setUrl(securityRequest.getUrl() + QUESTION_MARK + queryString);
}

securityRequest.setContentType(httpRequest.entity().getContentType().toString());

StackTraceElement[] trace = Thread.currentThread().getStackTrace();
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length));
securityRequest.setBody(requestBody);
securityRequest.setRequestParsed(true);
} catch (Throwable ignored){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, AKKA_HTTP_CORE_10_0_11, ignored.getMessage()), ignored, AkkaCoreUtils.class.getName());
}
finally {
if(isServletLockAcquired()){
releaseServletLock();
}
}
}

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 void processHttpRequestHeader(HttpRequest request, com.newrelic.api.agent.security.schema.HttpRequest securityRequest){
Iterator<HttpHeader> headers = request.getHeaders().iterator();
while (headers.hasNext()) {
boolean takeNextValue = false;
HttpHeader nextHeader = headers.next();
String headerKey = nextHeader.name();
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(nextHeader.value()));
} else if(GenericHelper.CSEC_PARENT_ID.equals(headerKey)) {
NewRelicSecurity.getAgent().getSecurityMetaData()
.addCustomAttribute(GenericHelper.CSEC_PARENT_ID, request.getHeader(headerKey).get().value());
} else if (ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST.equals(headerKey)) {
NewRelicSecurity.getAgent().getSecurityMetaData()
.addCustomAttribute(ICsecApiConstants.NR_CSEC_JAVA_HEAD_REQUEST, true);
}
String headerFullValue = nextHeader.value();
if (headerFullValue != null && !headerFullValue.trim().isEmpty()) {
if (takeNextValue) {
agentMetaData.setClientDetectedFromXFF(true);
securityRequest.setClientIP(headerFullValue);
agentMetaData.getIps()
.add(securityRequest.getClientIP());
securityRequest.setClientPort(EMPTY);
takeNextValue = false;
}
}
securityRequest.getHeaders().put(headerKey, headerFullValue);
}

}

private static String getProtocol(String value) {
if(StringUtils.containsIgnoreCase(value, "https")){
return "https";
} else if (StringUtils.containsIgnoreCase(value, "http")) {
return "http";
} else {
return value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package akka.http.scaladsl

import akka.Done
import akka.http.scaladsl.model.{HttpRequest, HttpResponse}
import akka.stream.Materializer
import akka.stream.javadsl.Source
import akka.stream.scaladsl.Sink
import akka.util.ByteString
import com.newrelic.api.agent.{NewRelic, Trace}

import java.lang
import scala.concurrent.Future
import scala.runtime.AbstractFunction1

class AkkaSyncRequestHandler(handler: HttpRequest HttpResponse)(implicit materializer: Materializer) extends AbstractFunction1[HttpRequest, HttpResponse] {

@Trace(dispatcher = true)
override def apply(param: HttpRequest): HttpResponse = {
val body: lang.StringBuilder = new lang.StringBuilder();
val dataBytes: Source[ByteString, AnyRef] = param.entity.getDataBytes()
val isLockAquired = AkkaCoreUtils.acquireServletLockIfPossible();
val sink: Sink[ByteString, Future[Done]] = Sink.foreach[ByteString] { byteString =>
val chunk = byteString.utf8String
body.append(chunk)
}
val processingResult: Future[Done] = dataBytes.runWith(sink, materializer)
AkkaCoreUtils.preProcessHttpRequest(isLockAquired, param, body, NewRelic.getAgent.getTransaction.getToken);
val response: HttpResponse = handler.apply(param)
ResponseFutureHelper.wrapResponseSync(response, materializer)
response
}
}
Loading

0 comments on commit 84899b3

Please sign in to comment.