From 45fe62d8a76f1f91633475ff42f2d68d1fcd6d2c Mon Sep 17 00:00:00 2001 From: PChol22 Date: Fri, 27 May 2022 13:58:46 +0200 Subject: [PATCH] Implement no-fail-destination rule --- src/guardian/index.js | 2 + .../fail-destination/fail-destination.MD | 15 ++++ .../best_practices/fail-destination/index.js | 87 +++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 src/guardian/rules/best_practices/fail-destination/fail-destination.MD create mode 100644 src/guardian/rules/best_practices/fail-destination/index.js diff --git a/src/guardian/index.js b/src/guardian/index.js index 944e659c..cec59b51 100644 --- a/src/guardian/index.js +++ b/src/guardian/index.js @@ -7,6 +7,7 @@ import NoMaximumTimeout from "./rules/best_practices/no-max-timeout"; import NoMaximumMemory from "./rules/best_practices/no-max-memory"; import NoIdenticalCode from "./rules/best_practices/no-identical-code"; import NoSharedRoles from "./rules/best_practices/no-shared-roles"; +import FailDestination from "./rules/best_practices/fail-destination"; import { getAWSCredentials, getStackResources, @@ -43,6 +44,7 @@ class GuardianCI { NoMaximumMemory, NoIdenticalCode, NoSharedRoles, + FailDestination, ]; this.failingChecks = []; diff --git a/src/guardian/rules/best_practices/fail-destination/fail-destination.MD b/src/guardian/rules/best_practices/fail-destination/fail-destination.MD new file mode 100644 index 00000000..e46e05d6 --- /dev/null +++ b/src/guardian/rules/best_practices/fail-destination/fail-destination.MD @@ -0,0 +1,15 @@ +# No Functions Have Memory Configuration Left as Default (no-default-memory) + +Lambda Functions memory is configurable and should be configured for the use-case. +This can impact the speed and running cost of the Lambda Function. +> **Note:** Any increase in memory size triggers an equivalent increase in CPU available to your function + +--- + +## Suggested Actions: + +- Look into your CloudWatch Logs for the Lambda function to find `Max Memory Used` [more information](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html) +``` +REPORT RequestId: 3604209a-e9a3-11e6-939a-754dd98c7be3 Duration: 12.34 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 18 MB +``` +- Power-tune using [aws-lambda-power-tuning](https://github.com/alexcasalboni/aws-lambda-power-tuning) \ No newline at end of file diff --git a/src/guardian/rules/best_practices/fail-destination/index.js b/src/guardian/rules/best_practices/fail-destination/index.js new file mode 100644 index 00000000..b7cdfda1 --- /dev/null +++ b/src/guardian/rules/best_practices/fail-destination/index.js @@ -0,0 +1,87 @@ +const ASYNC_AWS_SERVICES = ["events", "s3", "sqs", "sns"]; + +const getAwsServiceFromArn = (arn) => arn.split(":")[2]; + +const isLambdaPolicyAsync = (policy) => { + const sourceArns = policy.Statement.map( + (statement) => statement.Condition.ArnLike["AWS:SourceArn"] + ); + return sourceArns.some((sourceArn) => + ASYNC_AWS_SERVICES.includes(getAwsServiceFromArn(sourceArn)) + ); +}; + +class FailDestination { + constructor(AWS, stackName, stackFunctions, SLS) { + this.name = "no-default-timeout"; + this.AWS = AWS; + this.stackName = stackName; + this.stackFunctions = stackFunctions; + this.result = false; + this.defaultTimeoutAWS = 3; + this.defaultTimeoutServerlessFramework = 6; + this.failingResources = []; + this.SLS = SLS; + this.failureMessage = + "The following asynchroneous functions have no destination configured on-failure."; + this.rulePage = "***RulePage***"; + this.lambda = new this.AWS.Lambda(); + } + + async run() { + try { + this.result = false; + + const stackLambdaPolicies = await Promise.all( + this.stackFunctions.map(({ FunctionName }) => + this.lambda + .getPolicy({ + FunctionName, + }) + .promise() + .then((policy) => ({ ...policy, FunctionName })) + .catch(() => {}) + ) + ); + + const asyncFunctionsNames = stackLambdaPolicies + .filter( + (policy) => + policy !== undefined && + isLambdaPolicyAsync(JSON.parse(policy.Policy)) + ) + .map((policy) => policy.FunctionName); + + const invokeEventConfigs = await Promise.all( + asyncFunctionsNames.map((FunctionName) => + this.lambda + .getFunctionEventInvokeConfig({ + FunctionName, + }) + .promise() + .then((invokeEventConfig) => ({ + ...invokeEventConfig, + FunctionName, + })) + .catch(() => ({ FunctionName })) + ) + ); + + this.failingResources = invokeEventConfigs + .filter( + (invokeEventConfig) => + invokeEventConfig.DestinationConfig === undefined || + invokeEventConfig.DestinationConfig.OnFailure.destination === null + ) + .map(({ FunctionName }) => ({ + FunctionName, + })); + this.result = this.failingResources.length === 0; + } catch (e) { + console.error(e); + } + return this.result; + } +} + +export default FailDestination;