Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2056c9f
Implemented hooks for dynamodb
sumitsuthar May 11, 2023
b363949
fix for IAST scanning in node 18 (#22)
sumitsuthar May 11, 2023
193ccc0
fix: updated npm-publish workflow to use `--follow-tags` which will p…
bizob2828 May 12, 2023
3135f53
dynamo db event generation
sumitsuthar May 12, 2023
a392b9e
support for lib-dynamodb
sumitsuthar May 15, 2023
08f38a4
fix: added open source badge and status badges to readme (#24)
bizob2828 May 13, 2023
913d9ba
Initlog update (#25)
sumitsuthar May 16, 2023
e276734
Change logs (#27)
sumitsuthar May 16, 2023
448b76b
Host fix (#28)
sumitsuthar May 17, 2023
f58a924
0.0.6 (#29)
sumitsuthar May 17, 2023
0bed3dc
chore: updated repolinter config to use community-plus rulesets (#30)
bizob2828 May 18, 2023
e7defdd
Update nr-mysql.test.js (#31)
pratik-k2 May 18, 2023
778e18b
Update README.md (#32)
toobagrrl May 19, 2023
d1d09c6
Implemented hooks for dynamodb
sumitsuthar May 11, 2023
a765566
dynamo db event generation
sumitsuthar May 12, 2023
5c9cad3
support for lib-dynamodb
sumitsuthar May 15, 2023
094b52a
resolved merge conflict
sumitsuthar May 24, 2023
985927b
Merge branch 'main' into dynamodb_support
sumitsuthar May 26, 2023
2a57dd3
Merge branch 'main' into dynamodb_support
sumitsuthar May 29, 2023
803e907
Merge branch 'main' into dynamodb_support
sumitsuthar Jul 31, 2023
33a5247
handling to convert keys in camelCase
sumitsuthar Aug 3, 2023
04400e8
exception handling in hook
sumitsuthar Aug 3, 2023
04b55ac
Handling to convert nested keys to camelcase
sumitsuthar Aug 3, 2023
8888f5d
code cleanup
sumitsuthar Aug 3, 2023
542f824
Merge branch 'main' into dynamodb_support
sumitsuthar Aug 3, 2023
3fedef7
handling to avoid keys with colon in converting camelCase for dynamodb
sumitsuthar Aug 3, 2023
06966dd
version logging added
sumitsuthar Aug 4, 2023
9033cd8
Merge branch 'main' into dynamodb_support
sumitsuthar Aug 8, 2023
e6a1f8f
Merge branch 'main' into dynamodb_support
sumitsuthar Aug 9, 2023
788cb35
Merge branch 'main' into dynamodb_support
sumitsuthar Aug 10, 2023
f61b01b
Merge branch 'main' into dynamodb_support
sumitsuthar Aug 11, 2023
b661e3c
Merge branch 'main' into dynamodb_support
sumitsuthar Sep 29, 2023
f6e2adf
added payload type for batch operations
sumitsuthar Sep 29, 2023
dc31de2
minor fix
sumitsuthar Sep 30, 2023
7901320
test: DynamoDB unit test cases (#101)
pratik-k2 Oct 9, 2023
2d8453c
Merge branch 'main' into dynamodb_support
sumitsuthar Oct 17, 2023
b1b9450
merge main
sumitsuthar Oct 25, 2023
c307a78
Merge branch 'main' into dynamodb_support
sumitsuthar Oct 27, 2023
a788bf4
merged main
sumitsuthar Nov 20, 2023
b662e65
pacakge-lock update
sumitsuthar Nov 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,3 @@ jobs:
git tag v$VERSION
git push --tags


2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ The `@newrelic/security-agent` also uses source code from third-party libraries.
[1]: https://img.shields.io/npm/v/@newrelic/security-agent.svg
[2]: https://www.npmjs.com/package/@newrelic/security-agent
[3]: https://github.com/newrelic/csec-node-agent/workflows/CSEC%20Node%20Agent%20CI/badge.svg
[4]: https://github.com/newrelic/csec-node-agent/actions?query=workflow%3A%22CSEC+Node+Agent+CI%22
[4]: https://github.com/newrelic/csec-node-agent/actions?query=workflow%3A%22CSEC+Node+Agent+CI%22
2 changes: 2 additions & 0 deletions lib/instrumentation-security/core/event-constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const EVENT_TYPE = {
FILE_INTEGRITY: 'FILE_INTEGRITY',
DB_COMMAND: 'SQL_DB_COMMAND',
NOSQL_DB_COMMAND: 'NOSQL_DB_COMMAND',
DYNAMO_DB_COMMAND:'DYNAMO_DB_COMMAND',
HTTP_REQUEST: 'HTTP_REQUEST',
CODE_INJECTION: 'CODE_INJECTION',
XXE: 'XXE',
Expand All @@ -29,6 +30,7 @@ const EVENT_CATEGORY = {
MONGO: 'MONGO',
MSSQL: 'MSSQL',
SQLITE: 'SQLITE',
DQL: 'DQL',
FILE: 'FILE',
SYS: 'SYSTEM',
HTTP: 'HTTP',
Expand Down
135 changes: 79 additions & 56 deletions lib/instrumentation-security/core/sec-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ const secTrace = require('./sec-trace');
const requestManager = require('./request-manager');
const routeManager = require('./route-manager');
const async_hooks = require('async_hooks');
const lodash = require('lodash');

let AGENT_DIR = path.join(__dirname, '../../');
let AGENT_DIR = path.join(__dirname, '../../');

if(process.platform == 'win32'){
if (process.platform == 'win32') {
AGENT_DIR = path.join(__dirname, "..\\..\\");
}
const NR_LIB = '/newrelic/lib';
Expand All @@ -36,7 +37,7 @@ const API = require("../../nr-security-api");
const logger = API.getLogger();

createSkipList();
const fs = require('fs');
const fs = require('fs');
const cp = require('child_process');
const requestIp = require('request-ip');
const URL = require('url')
Expand All @@ -47,7 +48,7 @@ const URL = require('url')
*
* @returns {Array} loadList
*/
function createSkipList () {
function createSkipList() {
let loadList = [];
process.moduleLoadList.forEach(function (loadString) {
loadList.push(loadString.split(SPACE_CHARACTER).pop());
Expand All @@ -63,7 +64,7 @@ const URL = require('url')
}


function getTraceObject (shim) {
function getTraceObject(shim) {
const trace = stackTraceModule.get();
const traceLength = 10;
const stkTrace = [];
Expand All @@ -81,18 +82,18 @@ function getTraceObject (shim) {
const request = requestManager.getRequest(shim);
const key = request.method + ATTHERATE + request.uri;
const routeFile = routeManager.getRoute(key);
if(routeFile){
if (routeFile) {
stkTrace.push(routeFile);
}
const sourceDetails = secTrace.getSourceDetailsFromTrace(trace,__filename, stkTrace);
const sourceDetails = secTrace.getSourceDetailsFromTrace(trace, __filename, stkTrace);
const traceObject = {
sourceDetails:sourceDetails,
sourceDetails: sourceDetails,
stacktrace: stkTrace
}
return traceObject;
}

function getTraceObjectFallback (request) {
function getTraceObjectFallback(request) {
const trace = stackTraceModule.get();
const traceLength = 10;
const stkTrace = [];
Expand All @@ -109,19 +110,19 @@ function getTraceObjectFallback (request) {
}
const key = request.method + ATTHERATE + request.uri;
const routeFile = routeManager.getRoute(key);
if(routeFile){
if (routeFile) {
stkTrace.push(routeFile);
}
const sourceDetails = secTrace.getSourceDetailsFromTrace(trace,__filename, stkTrace);
const sourceDetails = secTrace.getSourceDetailsFromTrace(trace, __filename, stkTrace);
const traceObject = {
sourceDetails:sourceDetails,
sourceDetails: sourceDetails,
stacktrace: stkTrace
}
return traceObject;
}


function traceElementForRoute () {
function traceElementForRoute() {
const stkTrace = [];
const trace = stackTraceModule.get();
let methodName = null;
Expand All @@ -130,7 +131,7 @@ function traceElementForRoute () {
const funcName = trace[i].getFunctionName();
const fileName = trace[i].getFileName();
const lineNumber = trace[i].getLineNumber();
if(i>0){
if (i > 0) {
methodName = trace[i - 1].getMethodName();
}

Expand All @@ -143,11 +144,11 @@ function traceElementForRoute () {
return stkTrace;
}

function getExecutionId(){
function getExecutionId() {
return async_hooks.executionAsyncId();
}

function createPathIfNotExist (dir) {
function createPathIfNotExist(dir) {
try {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, 777, { recursive: true });
Expand All @@ -169,54 +170,76 @@ function createPathIfNotExist (dir) {
*/
function addRequestData(shim, request) {
try {
const data = Object.assign({});
const segment = shim.getActiveSegment();
if (segment && segment.transaction) {
data.body = null;
data.headers = request.headers;
data.url = request.url;
data.method = request.method;
data.httpVersion = request.httpVersion;
data.serverPort = segment.transaction.port;
data.contextPath = '/';
const queryObject = URL.parse(request.url, true).query;
data.parameterMap = {};
if (queryObject) {
Object.keys(queryObject).forEach(function (key) {
if (queryObject[key]) {
if (!data.parameterMap[key]) {
data.parameterMap[key] = new Array(queryObject[key].toString());
}
const data = Object.assign({});
const segment = shim.getActiveSegment();
if (segment && segment.transaction) {
data.body = null;
data.headers = request.headers;
data.url = request.url;
data.method = request.method;
data.httpVersion = request.httpVersion;
data.serverPort = segment.transaction.port;
data.contextPath = '/';
const queryObject = URL.parse(request.url, true).query;
data.parameterMap = {};
if (queryObject) {
Object.keys(queryObject).forEach(function (key) {
if (queryObject[key]) {
if (!data.parameterMap[key]) {
data.parameterMap[key] = new Array(queryObject[key].toString());
}
}
});
}
});
}
data.clientIP = requestIp.getClientIp(request);
const transactionId = segment.transaction.id;
const storedRequest = requestManager.getRequestFromId(transactionId);
if (storedRequest && storedRequest.uri) {
data.uri = storedRequest.uri;
data.parameterMap = storedRequest.parameterMap;
}
requestManager.setRequest(transactionId, data);
if(shim.agent.getLinkingMetadata()){
let linkingMetadata = shim.agent.getLinkingMetadata();
if(linkingMetadata['trace.id']){
let traceId = linkingMetadata['trace.id'];
requestManager.setRequest(traceId, data)
data.clientIP = requestIp.getClientIp(request);
const transactionId = segment.transaction.id;
const storedRequest = requestManager.getRequestFromId(transactionId);
if (storedRequest && storedRequest.uri) {
data.uri = storedRequest.uri;
data.parameterMap = storedRequest.parameterMap;
}
requestManager.setRequest(transactionId, data);
if (shim.agent.getLinkingMetadata()) {
let linkingMetadata = shim.agent.getLinkingMetadata();
if (linkingMetadata['trace.id']) {
let traceId = linkingMetadata['trace.id'];
requestManager.setRequest(traceId, data)
}
}
}
}
}
} catch (error) {
logger.debug("Error while preparing incoming request:", error);
}
}

logger.debug("Error while preparing incoming request:", error);
}
}


/**
* Utility to get camelCase keys of object
* @param {*} object
* @returns
*/
const camelCaseKeys = (obj) => {
if (Array.isArray(obj)) {
return obj.map(v => camelCaseKeys(v));
} else if (obj != null && obj.constructor === Object) {
return Object.keys(obj).reduce(
(result, key) => ({
...result,
[key.includes(":") ? key : lodash.camelCase(key)]: camelCaseKeys(obj[key]),
}),
{},
);
}
return obj;
};


module.exports = {
getTraceObject,
traceElementForRoute,
getExecutionId,
createPathIfNotExist,
getTraceObjectFallback,
addRequestData
addRequestData,
camelCaseKeys
}
89 changes: 89 additions & 0 deletions lib/instrumentation-security/hooks/dynamodb/v2/nr-dynamodb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/


'use strict'

const requestManager = require('../../../core/request-manager');
const secUtils = require('../../../core/sec-utils');
const API = require("../../../../nr-security-api");
const securityMetaData = require('../../../core/security-metadata');
const { EVENT_TYPE, EVENT_CATEGORY } = require('../../../core/event-constants');
const logger = API.getLogger();
const { NR_CSEC_FUZZ_REQUEST_ID } = require('../../../core/constants');


const typeMap = {
'scan': 'read',
'get': 'read',
'put': 'write',
'update': 'update',
'delete': 'delete',
'batchGet': 'read',
'batchWrite': 'write',
'transactGet': 'read',
'transactWrite': 'write',
'batchGetItem': 'read',
'batchWriteItem': 'write',
'transactGetItems': 'read',
'transactWriteItems': 'write',
'query': 'read',
'putItem': 'write',
'getItem': 'read',
'updateItem': 'update',
'deleteItem': 'delete',
'createTable': 'unknown',
'deleteTable': 'unknown'
}

module.exports = function initialize(shim, AWS, moduleName) {
logger.info('Instrumenting ' + moduleName);
const dynamoDBVersion = shim.require("./package.json").version;
logger.debug(`${moduleName} version:`,dynamoDBVersion)
const dynamoDB = AWS.DynamoDB;
makeRequestHook(shim, dynamoDB && dynamoDB.prototype, 'makeRequest');
}

/**
* Wrapper to wrap makeRequest function.
* @param {*} shim
* @param {*} mod
* @param {*} method
*/
function makeRequestHook(shim, mod, method) {
shim.wrap(mod, method, function makeQueryWrapper(shim, fn) {
return function queryWrapper() {
const request = requestManager.getRequest(shim);
try {
let operation = arguments[0];
let payloadType = typeMap[operation] ? typeMap[operation] : "unknown";
const parameters = {
payloadType: payloadType,
payload: secUtils.camelCaseKeys(arguments[1])
}

shim.interceptedArgs = parameters;
if (request) {
const traceObject = secUtils.getTraceObject(shim);
const secMetadata = securityMetaData.getSecurityMetaData(request, parameters, traceObject, secUtils.getExecutionId(), EVENT_TYPE.DYNAMO_DB_COMMAND, EVENT_CATEGORY.DQL)
const secEvent = API.generateSecEvent(secMetadata);
this.secEvent = secEvent;
API.sendEvent(secEvent);
}
} catch (error) {
logger.warn("Error while intercepting DynamoDB", error);
}
const result = fn.apply(this, arguments);
if (result && request && request.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
API.generateExitEvent(this.secEvent);
delete this.secEvent
}
return result;
};
});
}



48 changes: 48 additions & 0 deletions lib/instrumentation-security/hooks/dynamodb/v3/client-dynamodb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'


const API = require("../../../../nr-security-api");
const logger = API.getLogger();

const { getExport, wrapPostClientConstructor, wrapReturn } = require('./util')
const { dynamoMiddleware } = require('./dynamodb-util')

const CLIENT = 'DynamoDBClient'

const postClientConstructor = wrapPostClientConstructor(getPlugin)
const wrappedReturn = wrapReturn(postClientConstructor)

module.exports = function instrument(shim, name, resolvedName) {
const dynamoClientExport = getExport(shim, resolvedName, CLIENT)
const dynamoDBVersion = shim.require("./package.json").version;
logger.debug(`${name} version:`,dynamoDBVersion)
if (!shim.isFunction(dynamoClientExport[CLIENT])) {
logger.debug(`Could not find ${CLIENT}, not instrumenting.`)
} else {
logger.info(`Instrumenting ${name}`);
shim.wrapReturn(dynamoClientExport, CLIENT, wrappedReturn)
}
}

/**
* Returns the plugin object that adds middleware
*
* @param {Shim} shim
* @returns {object}
*/
function getPlugin(shim, config) {
return {
applyToStack: (clientStack) => {
clientStack.add(dynamoMiddleware.bind(null, shim, config), {
name: 'NewRelicSecurityDynamoMiddleware',
step: 'initialize',
priority: 'low'
})
}
}
}
Loading