Skip to content

Commit

Permalink
feat: support for sns (issue #1)
Browse files Browse the repository at this point in the history
  • Loading branch information
glicht committed Mar 12, 2018
1 parent 3e6b7ca commit ee16055
Show file tree
Hide file tree
Showing 13 changed files with 495 additions and 69 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ aws-least-privilege-*.tgz
*.policy.json
#ignore log
xray-scan.log
#ignore excessive report
excessive_permissions.json


14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,16 @@ Follow the [AWS X-Ray Developer Guide](https://docs.aws.amazon.com/xray/latest/d

## X-Ray Parameter Whitelist Configuration

In order to capture what resources are accessed by AWS service calls, AWS X-Ray needs to be configured to send out parameter information as part of the subsegment traces. For example for DynamoDB we would want to know the Table being accessed and for S3 the Bucket and Key. AWS X-Ray currently provides default parameter info only for a limited set of services: DynamoDB, SQS, and Lambda. X-Ray does provide support for configuring additional services (such as S3) to include parameter info through a parameter whitelist configuration file. We've created separate projects to manage parameter whitelist configurations for the Java and Node X-Ray SDKs. For Python the same parameter whitelist file of Node can be used as specified below.
In order to capture what resources are accessed by AWS service calls, AWS X-Ray needs to be configured to send out parameter information as part of the subsegment traces. For example for DynamoDB we would want to know the Table being accessed and for S3 the Bucket and Key. AWS X-Ray currently provides default parameter info only for a limited set of services:

* DynamoDB: All X-Ray SDKs
* SQS: All X-Ray SDKs
* Lambda: All X-Ray SDKs
* S3:
* Node SDK: version 1.2.0 and higher.
* Java SDK: As of Jan 30, 2018 has support as part of main source code, which is still not released. See [merged pull request](https://github.com/aws/aws-xray-sdk-java/pull/9).

X-Ray does provide support for configuring additional services (such as S3 and SNS) to include parameter info through a parameter whitelist configuration file. We've created separate projects to manage parameter whitelist configurations for the Java and Node X-Ray SDKs. For Python the same parameter whitelist file of Node can be used as specified below.

### X-Ray SDK for Node

Expand All @@ -69,7 +78,7 @@ Then during the initialization of your application add the following configurati
const AWSXRay = require('aws-xray-sdk');
const whitelists = require('aws-xray-parameter-whitelist');
XRay.captureAWS(require('aws-sdk')); //standard capture code of X-Ray to catch AWS SDK calls
AWSXRay.appendAWSWhitelist(whitelists.s3_whitelist);
AWSXRay.appendAWSWhitelist(whitelists.sns_whitelist);
```

More info available at the project page: https://github.com/functionalone/aws-xray-parameter-whitelist-node
Expand Down Expand Up @@ -160,6 +169,7 @@ The following services are supported as part of an X-Ray scan (unsupported servi
* S3
* SQS
* Lambda
* SNS

If you would like to see additional services, please let us know by openning an [issue](https://github.com/functionalone/aws-least-privilege/issues).

Expand Down
68 changes: 33 additions & 35 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"bluebird": "^3.5.1",
"commander": "^2.14.1",
"lodash": "^4.17.5",
"winston": "^2.4.0"
"winston": "^2.4.1"
},
"devDependencies": {
"@types/bluebird": "^3.5.20",
Expand Down
7 changes: 1 addition & 6 deletions src/lib/aws-segments/dynamo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,7 @@ const parseSegment: SegmentParseFunc = function(segmentDoc: any, actionsMap: Res
const region = aws.region || extractRegion(functionArn);
//construct the arn
const arn = `arn:aws:dynamodb:${region}:*:table/${aws.table_name}`;
let actions = actionsMap.get(arn);
if(!actions) {
actions = new Set();
actionsMap.set(arn, actions);
}
actions.add(aws.operation);
actionsMap.addActionToResource(aws.operation, arn);
};
// tslint:enable:jsdoc-format

Expand Down
2 changes: 2 additions & 0 deletions src/lib/aws-segments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import dynamoParser from './dynamo';
import s3Parser from './s3';
import lambdaParser from './lambda';
import sqsParser from './sqs';
import snsParser from './sns';

export type SegmentParseFunc = (segmentDoc: any, actionsMap: ResourceActionMap, functionArn: string) => void;

Expand All @@ -11,6 +12,7 @@ export const AWSSegmentParsers: {[i: string]: SegmentParseFunc} = {
S3: s3Parser,
Lambda: lambdaParser,
SQS: sqsParser,
SNS: snsParser,
};

export const SUPPORTED_SERVICES = Object.getOwnPropertyNames(AWSSegmentParsers);
9 changes: 2 additions & 7 deletions src/lib/aws-segments/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,12 @@ const parseSegment: SegmentParseFunc = function(segmentDoc: any, actionsMap: Res
if(isEmpty(aws) || !aws.function_name) {
logger.warn("Couldn't extract Lambda info for segment document: ", segmentDoc);
return;
}
}
const op = OPS_REPLACERS[aws.operation] || aws.operation;
const region = aws.region || extractRegion(functionArn);
//construct the arn
const arn = `arn:aws:lambda:${region}:*:function:${aws.function_name}`;
let actions = actionsMap.get(arn);
if(!actions) {
actions = new Set();
actionsMap.set(arn, actions);
}
actions.add(op);
actionsMap.addActionToResource(op, arn);
};
// tslint:enable:jsdoc-format

Expand Down
7 changes: 1 addition & 6 deletions src/lib/aws-segments/s3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,7 @@ const parseSegment: SegmentParseFunc = function(segmentDoc: any, actionsMap: Res
arn = `arn:aws:s3:::${aws.bucket_name}/*`;
}
}
let actions = actionsMap.get(arn);
if (!actions) {
actions = new Set();
actionsMap.set(arn, actions);
}
actions.add(op);
actionsMap.addActionToResource(op, arn);
};

export default parseSegment;
70 changes: 70 additions & 0 deletions src/lib/aws-segments/sns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {SegmentParseFunc} from './index';
import { ResourceActionMap } from '../xray-trace-fetcher';
import {isEmpty} from 'lodash';
import {logger} from '../logger';
import { extractRegion } from './utils';

/**
* Map which is used to replace functions which are named differently to the IAM naming
* See also: https://docs.aws.amazon.com/IAM/latest/UserGuide/list_sns.html
*/
const OPS_REPLACERS: {[i: string]: string} = {
};

/**
* Global actions that work on root star
*/
const GLOBAL_ACTIONS = /^(ListTopics|ListPlatformApplications|ListSubscriptions|CreateTopic|CreatePlatformApplication)$/;

// tslint:disable:jsdoc-format
/**
* Parse a SNS segement. SNS segment will look like this:
*
{
"id": "4b1fd180bd9678f2",
"name": "SNS",
"start_time": 1520849681.61,
"end_time": 1520849681.714,
"http": {
"response": {
"status": 200
}
},
"aws": {
"operation": "ListSubscriptionsByTopic",
"region": "us-east-1",
"request_id": "0b537b2c-4c01-5f50-b619-280c195091e7",
"retries": 0,
"topic_arn": "arn:aws:sns:us-east-1:123456789012:test-topic"
},
"namespace": "aws"
}
*
* @param segmentDoc
* @param actionsMap
*/
const parseSegment: SegmentParseFunc = function(segmentDoc: any, actionsMap: ResourceActionMap, functionArn: string) {
const aws: any = segmentDoc.aws;
let arn;
if(isEmpty(aws)) {
logger.warn("Couldn't extract SNS info for segment document: ", segmentDoc);
return;
}
const region = aws.region || extractRegion(functionArn);
const op = OPS_REPLACERS[aws.operation] || aws.operation;
if(GLOBAL_ACTIONS.test(op)) {
arn = `arn:aws:sns:${region}:*:*`;
}
else {
if(!aws.topic_arn) {
logger.warn("Empty topic_arn for SNS op: [%s] on segment (skipping): ", op, segmentDoc);
return;
}
//construct the arn
arn = `arn:aws:sns:${region}:*:${aws.topic_arn}`;
}
actionsMap.addActionToResource(op, arn);
};
// tslint:enable:jsdoc-format

export default parseSegment;
7 changes: 1 addition & 6 deletions src/lib/aws-segments/sqs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,7 @@ const parseSegment: SegmentParseFunc = function(segmentDoc: any, actionsMap: Res
const queueName = regexMatch[1];
//construct the arn
const arn = `arn:aws:sqs:${region}:*:${queueName}`;
let actions = actionsMap.get(arn);
if(!actions) {
actions = new Set();
actionsMap.set(arn, actions);
}
actions.add(op);
actionsMap.addActionToResource(op, arn);
};
// tslint:enable:jsdoc-format

Expand Down
19 changes: 16 additions & 3 deletions src/lib/xray-trace-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,22 @@ export async function getXrayTraces(conf?: ScanXrayTracesConf) {
}

/**
* Maps that maps a resource to set of actions
* Map that maps a resource to set of actions
*/
export type ResourceActionMap = Map<string, Set<string>>;
export class ResourceActionMap extends Map<string, Set<string>> {
constructor() {
super();
}

addActionToResource(actionOp: string, resourceArn: string) {
let actions = super.get(resourceArn);
if(!actions) {
actions = new Set();
super.set(resourceArn, actions);
}
actions.add(actionOp);
}
}

/**
* Map which maps a function to the set of ResourceActionMap
Expand All @@ -122,7 +135,7 @@ function parseSegmentDoc(doc: any, functionMap: FunctionToActionsMap, parentActi
logger.debug('Found arn: %s for Segment: %s', arn, doc.id);
actionsMap = functionMap.get(arn);
if (!actionsMap) {
actionsMap = new Map();
actionsMap = new ResourceActionMap();
functionMap.set(arn, actionsMap);
}
}
Expand Down
Loading

0 comments on commit ee16055

Please sign in to comment.