Skip to content

Commit

Permalink
Merge pull request #184 from newrelic/release/v1.1.1
Browse files Browse the repository at this point in the history
CSEC Release Target Version 1.1.1
  • Loading branch information
lovesh-ap authored Feb 16, 2024
2 parents 6e6dccd + 87923fe commit 4e71ee3
Show file tree
Hide file tree
Showing 42 changed files with 996 additions and 54 deletions.
10 changes: 10 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ Noteworthy changes to the agent are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.1] - 2024-2-16
### Changes
- [NR-223414](https://new-relic.atlassian.net/browse/NR-223414) Enable Low Priority Instrumentation by default [PR-179](https://github.com/newrelic/csec-java-agent/pull/179)
- [NR-219439](https://new-relic.atlassian.net/browse/NR-219439) Akka server v10.0+ Support: The security agent now supports Akka server version 10.0 and above (with scala 2.11 and above) [PR-175](https://github.com/newrelic/csec-java-agent/pull/175)

### Fixes
- [NR-222151](https://new-relic.atlassian.net/browse/NR-222151) Extract Server Configuration to resolve IAST localhost connection with application [PR-183](https://github.com/newrelic/csec-java-agent/pull/183)
- [NR-223852](https://new-relic.atlassian.net/browse/NR-223852) Retry IAST request with different endpoint, if failure reason is SSLException or 301 [PR-182](https://github.com/newrelic/csec-java-agent/pull/182)
- [NR-218729](https://new-relic.atlassian.net/browse/NR-218729) Add instrumentation of java.nio.file.Files#setPosixFilePermissions [PR-178](https://github.com/newrelic/csec-java-agent/pull/178)

## [1.1.0] - 2024-1-29
### Changes
- gRPC client v1.4.0+ Support: The security agent now supports gRPC client version 1.4.0 and above (with protobuf-java-utils version 3.0.0 and above)
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# The agent version.
agentVersion=1.1.0
agentVersion=1.1.1
jsonVersion=1.1.1
# Updated exposed NR APM API version.
nrAPIVersion=8.4.0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
apply plugin: 'scala'

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


sourceSets.test.scala.srcDir "src/test/java"
sourceSets.test.java.srcDirs = []

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.akka-http-2.11_2.4.5' }
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.akka-http-2.11_10.0.0' }
}

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_2.11:10.1.8")
implementation("com.typesafe.akka:akka-stream_2.11:2.5.19")
implementation("com.typesafe.akka:akka-actor_2.11:2.5.19")
implementation("com.typesafe.akka:akka-http_2.12:10.0.0")
implementation("com.typesafe.akka:akka-stream_2.12:2.5.19")
implementation("com.typesafe.akka:akka-actor_2.12:2.5.19")
}

verifyInstrumentation {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
*
* * 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.{HttpEntity, HttpResponse}
import akka.http.scaladsl.server.AkkaCoreUtils
import akka.stream.Materializer
import akka.stream.javadsl.Source
import akka.stream.scaladsl.Sink
import akka.util.ByteString
import com.newrelic.api.agent.security.NewRelicSecurity
import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper
import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException
import com.newrelic.api.agent.security.utils.logging.LogLevel
import com.newrelic.api.agent.{NewRelic, Token}

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

class AkkaResponseHelper extends AbstractFunction1[HttpResponse, HttpResponse] {

override def apply(httpResponse: HttpResponse): HttpResponse = {
try {
val stringResponse = new lang.StringBuilder()
val isLockAquired = AkkaCoreUtils.acquireServletLockIfPossible()
stringResponse.append(httpResponse.entity.asInstanceOf[HttpEntity.Strict].getData().decodeString("utf-8"))
AkkaCoreUtils.postProcessHttpRequest(isLockAquired, stringResponse, httpResponse.entity.contentType.toString(), this.getClass.getName, "apply", NewRelic.getAgent.getTransaction.getToken())
} catch {
case t: NewRelicSecurityException =>
NewRelicSecurity.getAgent.log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, AkkaCoreUtils.AKKA_HTTP_10_0_0, t.getMessage), t, classOf[AkkaCoreUtils].getName)
throw t
case _: Throwable =>
}

httpResponse
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package akka.http.scaladsl.marshalling;

import akka.http.scaladsl.AkkaResponseHelper;
import akka.http.scaladsl.model.HttpResponse;
import akka.stream.scaladsl.Flow;
import com.newrelic.api.agent.Token;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.NewField;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

@Weave(type = MatchType.Interface, originalName = "akka.http.scaladsl.marshalling.ToResponseMarshallable")
public abstract class AkkaHttpToResponseMarshallable {

@NewField
public Token token;

public Marshaller<Object, HttpResponse> marshaller() {
Marshaller<Object, HttpResponse> marshaller = Weaver.callOriginal();
return marshaller.map(new AkkaResponseHelper());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package akka.http.scaladsl.server;

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.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.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

public class AkkaCoreUtils {

public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "HTTPREQUEST_OPERATION_LOCK_AKKA-";
public static final String AKKA_HTTP_10_0_0 = "AKKA_HTTP_10.0.0";
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());

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) {
NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AKKA_HTTP_10_0_0, e.getMessage()), e, AkkaCoreUtils.class.getName());
NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, AKKA_HTTP_10_0_0, e.getMessage()), e, AkkaCoreUtils.class.getName());
if(e instanceof NewRelicSecurityException){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, AKKA_HTTP_10_0_0, e.getMessage()), e, AkkaCoreUtils.class.getName());
throw e;
}
} 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().port());

processHttpRequestHeader(httpRequest, securityRequest);

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

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

securityRequest.setUrl(httpRequest.getUri().path());
String queryString = null;
try {
queryString = httpRequest.getUri().rawQueryString().get();
} catch (NoSuchElementException ignored) {
// ignore NoSuchElementException – there is no value present in rawQueryString
} finally {
if (queryString != null && !queryString.trim().isEmpty()) {
securityRequest.setUrl(securityRequest.getUrl() + QUESTION_MARK + queryString);
}
}

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

securityAgentMetaData.setServiceTrace(Thread.currentThread().getStackTrace());
securityRequest.setBody(requestBody);
securityRequest.setRequestParsed(true);
} catch (Throwable ignored){
NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.ERROR_GENERATING_HTTP_REQUEST, AKKA_HTTP_10_0_0, 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());
}
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;
}
}
}
Loading

0 comments on commit 4e71ee3

Please sign in to comment.