From c57fdf6a8868b72084f22621cf98f5cc8698c15d Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 12:09:24 +0100 Subject: [PATCH 01/24] guidance Helper v1 --- CHANGELOG.md | 3 +++ bin/aws-firewall-factory.ts | 4 +-- lib/tools/helpers/guidance.ts | 26 +++++++++++++++++++ lib/tools/helpers/index.ts | 3 ++- .../quotas-and-capacity.ts | 3 ++- .../web-application-firewall/rulegroups.ts | 3 ++- lib/tools/transformer.ts | 11 +++++--- 7 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 lib/tools/helpers/guidance.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3756d049..fd346566 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Change Log ## Released +## 4.2.1 +### Added +- Guidance Helper - this Helper will help you to get guidance on implementing Best Practices for AWS Firewalls. ## 4.2.0 ### Fixed diff --git a/bin/aws-firewall-factory.ts b/bin/aws-firewall-factory.ts index 50e8cd37..a845eb70 100644 --- a/bin/aws-firewall-factory.ts +++ b/bin/aws-firewall-factory.ts @@ -3,7 +3,7 @@ import { WafStack } from "../lib/_web-application-firewall-stack"; import { PrerequisitesStack } from "../lib/_prerequisites-stack"; import * as cdk from "aws-cdk-lib"; import { Config, Prerequisites, PriceRegions, RegionString } from "../lib/types/config"; -import { wafHelper, afwfHelper, pricingHelper, cloudformationHelper } from "../lib/tools/helpers"; +import { wafHelper, afwfHelper, pricingHelper, cloudformationHelper, guidanceHelper } from "../lib/tools/helpers"; import * as values from "../values"; /** @@ -58,7 +58,7 @@ void (async () => { await cloudformationHelper.setOutputsFromStack(deploymentRegion, runtimeProperties, config); if(config.General.DeployHash){ console.log("#️⃣ Deployment Hash for this WAF: "+ config.General.DeployHash); - console.log(" ⚠️ Legacy functionality ⌛️\n\n"); + guidanceHelper.getGuidance("deploymentHash"); } console.log(`🔥 Deploy FMS Policy: ${config.General.Prefix.toUpperCase()}-WAF-${config.WebAcl.Name.toUpperCase()}-${config.General.Stage.toUpperCase()}${config.General.DeployHash ? "-"+config.General.DeployHash.toUpperCase() : ""}\n ⦂ Type: diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts new file mode 100644 index 00000000..de818d26 --- /dev/null +++ b/lib/tools/helpers/guidance.ts @@ -0,0 +1,26 @@ +/** +This function will help you to get guidance on implementing Best Practices for AWS Firewalls. +@param context - The context of the guidance. For example, nestedRateStatement. +@param source - The source of the guidance. For example, ManagedRuleGroup. + */ +export function getGuidance(context: string, source?: string) { + switch(context){ + case "nestedRateStatement": + console.log("\x1b[31m",`\n🚨 Found Nested RateBasedStatement in ${source} - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. You can define a RateBasedStatement inside a web ACL and inside a rule group.\n\n`,"\x1b[0m"); + break; + case "overrideActionManagedRuleGroup": + console.log("\x1b[31m",`\n🚨 OverrideAction of ManagedRuleGroup ${source} is set to COUNT, which simply tallies all rules within the group.\n However, this practice may create a vulnerability in your firewall and is not recommended.\n\n`,"\x1b[0m"); + break; + case "noManageRuleGroups": + console.log("\x1b[31m","\n🚨 No ManagedRuleGroups are used in your Firewall.\n More about ManagedRuleGroups: https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups.html.\n\n","\x1b[0m"); + break; + case "deploymentHash": + console.log("\x1b[31m"," ⚠️ Legacy functionality ⌛️ \n This functionality will be removed soon. \n\n","\x1b[0m"); + break; + case "byteMatchStatementPositionalConstraint": + console.log("\x1b[31m",`\n🚨 Found PositionalConstraint "${source}" in ByteMatchStatement - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.\n\n`,"\x1b[0m"); + break; + default: + break; + } +} \ No newline at end of file diff --git a/lib/tools/helpers/index.ts b/lib/tools/helpers/index.ts index 0136317f..1bdbc7a8 100644 --- a/lib/tools/helpers/index.ts +++ b/lib/tools/helpers/index.ts @@ -2,4 +2,5 @@ export * as cloudformationHelper from "./cloudformation"; export * as wafHelper from "./web-application-firewall"; export * as afwfHelper from "./aws-firewall-factory"; -export * as pricingHelper from "./pricing"; \ No newline at end of file +export * as pricingHelper from "./pricing"; +export * as guidanceHelper from "./guidance"; \ No newline at end of file diff --git a/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts b/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts index fe8b4c4c..80ea60ac 100644 --- a/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts +++ b/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts @@ -4,7 +4,7 @@ import { Scope, WAFV2Client, CheckCapacityCommand, CheckCapacityCommandInput, De import { FMSClient, ListPoliciesCommand, ListPoliciesCommandInput } from "@aws-sdk/client-fms"; import { RuntimeProperties, ProcessProperties } from "../../../types/runtimeprops"; import { Config } from "../../../types/config"; -import { cloudformationHelper } from "../../helpers"; +import { cloudformationHelper, guidanceHelper } from "../../helpers"; import * as lodash from "lodash"; import {transformCdkRuletoSdkRule} from "../../transformer"; import { Rule as FmsRule, ManagedRuleGroup } from "../../../types/fms"; @@ -208,6 +208,7 @@ async function calculateManagedRuleGroupCapacities(type: "Pre" | "Post",deployme processProperties = runtimeProperties.PostProcess; break; } + config.WebAcl.PreProcess.ManagedRuleGroups !== undefined && config.WebAcl.PostProcess.ManagedRuleGroups !== undefined ? guidanceHelper.getGuidance("noManageRuleGroups") : null; const managedcapacitieslog = []; managedcapacitieslog.push(["➕ RuleName", "Capacity", "🏷 Specified Version", "🔄 EnforceUpdate"]); for (const managedrule of managedrules) { diff --git a/lib/tools/helpers/web-application-firewall/rulegroups.ts b/lib/tools/helpers/web-application-firewall/rulegroups.ts index 643a8c99..88bf11bb 100644 --- a/lib/tools/helpers/web-application-firewall/rulegroups.ts +++ b/lib/tools/helpers/web-application-firewall/rulegroups.ts @@ -6,6 +6,7 @@ import { Scope, WAFV2Client, ListAvailableManagedRuleGroupVersionsCommand, ListA import { RuntimeProperties, ProcessProperties } from "../../../types/runtimeprops"; import { transformWafRuleStatements } from "./statements"; import { Construct } from "constructs"; +import { guidanceHelper } from "../../helpers"; import * as cr from "aws-cdk-lib/custom-resources"; @@ -26,7 +27,7 @@ export function buildServiceDataManagedRgs(scope: Construct, managedRuleGroups: for (const managedRuleGroup of managedRuleGroups) { if(managedRuleGroup.overrideAction?.type === "COUNT"){ // eslint-disable-next-line quotes - console.log("\x1b[31m",`\n🚨 OverrideAction of ManagedRuleGroup ${managedRuleGroup.name} is set to COUNT, which simply tallies all rules within the group.\n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); + guidanceHelper.getGuidance("overrideActionManagedRuleGroup", managedRuleGroup.name); } if(NONEVERSIONEDMANAGEDRULEGRPOUP.find((rulegroup) => rulegroup === managedRuleGroup.name)){ console.log("\nℹ️ ManagedRuleGroup " + managedRuleGroup.name + " is not versioned. Skip Custom Resource for Versioning."); diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index 35db4792..aaf1f6b9 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -7,7 +7,7 @@ import { aws_wafv2 as wafv2 } from "aws-cdk-lib"; import { NotStatement, LabelMatchStatement, OrStatement, AndStatement, XssMatchStatement, SqliMatchStatement, RegexPatternSetReferenceStatement, Statement, IPSetReferenceStatement, SizeConstraintStatement, Rule, RegexMatchStatement, RateBasedStatement, ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body } from "@aws-sdk/client-wafv2"; -import {convertStringToUint8Array} from "./helpers/web-application-firewall"; +import { wafHelper, guidanceHelper} from "./helpers"; /** * The function will map a CDK ByteMatchStatement Property to a SDK ByteMatchStatement Property @@ -32,9 +32,12 @@ export function transformByteMatchStatement(statement: wafv2.CfnWebACL.ByteMatch }); }); } + if(bmst.positionalConstraint === "CONTAINS" || bmst.positionalConstraint === "CONTAINS_WORD" || bmst.positionalConstraint === "STARTS_WITH" || bmst.positionalConstraint === "ENDS_WITH"){ + guidanceHelper.getGuidance("byteMatchStatementPositionalConstraint", bmst.positionalConstraint); + } ByteMatchStatement = { PositionalConstraint: bmst.positionalConstraint, - SearchString: bmst.searchString ? convertStringToUint8Array(bmst.searchString) : undefined, + SearchString: bmst.searchString ? wafHelper.convertStringToUint8Array(bmst.searchString) : undefined, TextTransformations, FieldToMatch }; @@ -319,6 +322,7 @@ export function transformConcatenatedStatement(statement: wafv2.CfnWebACL.AndSta Statement.RegexMatchStatement = RegexMatchStatement as RegexMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "rateBasedStatement": + guidanceHelper.getGuidance("nestedRateStatement", "And/OrStatement"); RateBasedStatement = tranformRateBasedStatement(currentstatement.rateBasedStatement as wafv2.CfnWebACL.RateBasedStatementProperty); Statement.RateBasedStatement = RateBasedStatement as RateBasedStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; @@ -423,6 +427,7 @@ export function tranformNotStatement(statement: wafv2.CfnWebACL.NotStatementProp Statement.RegexMatchStatement = RegexMatchStatement as RegexMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "rateBasedStatement": + guidanceHelper.getGuidance("nestedRateStatement", "NotStatement"); RateBasedStatement = tranformRateBasedStatement((nst.statement as wafv2.CfnWebACL.StatementProperty).rateBasedStatement as wafv2.CfnWebACL.RateBasedStatementProperty); Statement.RateBasedStatement = RateBasedStatement as RateBasedStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; @@ -465,7 +470,7 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS case "ipSetReferenceStatement": IPSetReferenceStatement = transformIPSetReferenceStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).ipSetReferenceStatement as wafv2.CfnWebACL.IPSetReferenceStatementProperty); Statement.IPSetReferenceStatement = IPSetReferenceStatement as IPSetReferenceStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; + break; case "regexPatternSetReferenceStatement": RegexPatternSetReferenceStatement = transformRegexPatternSetReferenceStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).regexPatternSetReferenceStatement as wafv2.CfnWebACL.RegexPatternSetReferenceStatementProperty); Statement.RegexPatternSetReferenceStatement = RegexPatternSetReferenceStatement as RegexPatternSetReferenceStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. From b245d6d92ecfbc2c9dcd90b00be66352a5be9e36 Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 12:11:21 +0100 Subject: [PATCH 02/24] improve changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd346566..cd110099 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,7 @@ ## Released ## 4.2.1 ### Added -- Guidance Helper - this Helper will help you to get guidance on implementing Best Practices for AWS Firewalls. - +- Guidance Helper v1: This Helper is designed to provide comprehensive assistance in implementing Best Practices for AWS Firewalls. Additionally, it addresses [Issue279](https://github.com/globaldatanet/aws-firewall-factory/issues/279), ensuring a more robust and effective implementation. ## 4.2.0 ### Fixed - Output of the correct ManagedRuleGroup version if the stack has already been deployed, no version has been specifically set or Enforce Update has been set From f3d6a3edc9aea4eeb555aff01b6ec7d22f616942 Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 12:25:48 +0100 Subject: [PATCH 03/24] add another guidance --- lib/tools/helpers/guidance.ts | 3 +++ lib/tools/helpers/web-application-firewall/rulegroups.ts | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index de818d26..b805a94f 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -20,6 +20,9 @@ export function getGuidance(context: string, source?: string) { case "byteMatchStatementPositionalConstraint": console.log("\x1b[31m",`\n🚨 Found PositionalConstraint "${source}" in ByteMatchStatement - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.\n\n`,"\x1b[0m"); break; + case "noBotControlRuleSetProperty": + console.log("\x1b[31m","\n🚨 No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet - This setting will help you to specify the inspection level.\n More about BotControlRuleSetProperty: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.\n\n","\x1b[0m"); + break; default: break; } diff --git a/lib/tools/helpers/web-application-firewall/rulegroups.ts b/lib/tools/helpers/web-application-firewall/rulegroups.ts index 88bf11bb..9a7e3db7 100644 --- a/lib/tools/helpers/web-application-firewall/rulegroups.ts +++ b/lib/tools/helpers/web-application-firewall/rulegroups.ts @@ -29,6 +29,9 @@ export function buildServiceDataManagedRgs(scope: Construct, managedRuleGroups: // eslint-disable-next-line quotes guidanceHelper.getGuidance("overrideActionManagedRuleGroup", managedRuleGroup.name); } + if(managedRuleGroup.name === "AWSManagedRulesBotControlRuleSet"){ + managedRuleGroup.awsManagedRulesBotControlRuleSetProperty ? guidanceHelper.getGuidance("noBotControlRuleSetProperty") : undefined; + } if(NONEVERSIONEDMANAGEDRULEGRPOUP.find((rulegroup) => rulegroup === managedRuleGroup.name)){ console.log("\nℹ️ ManagedRuleGroup " + managedRuleGroup.name + " is not versioned. Skip Custom Resource for Versioning."); cfnManagedRuleGroup.push({ @@ -43,7 +46,7 @@ export function buildServiceDataManagedRgs(scope: Construct, managedRuleGroups: excludeRules: managedRuleGroup.excludeRules ? managedRuleGroup.excludeRules : [], ruleGroupType: "ManagedRuleGroup", ruleActionOverrides: managedRuleGroup.ruleActionOverrides ?? undefined, - awsManagedRulesBotControlRuleSetProperty: managedRuleGroup.awsManagedRulesBotControlRuleSetProperty ? managedRuleGroup.awsManagedRulesBotControlRuleSetProperty : undefined, + awsManagedRulesBotControlRuleSetProperty: managedRuleGroup.awsManagedRulesBotControlRuleSetProperty ?? undefined, awsManagedRulesACFPRuleSetProperty: managedRuleGroup.awsManagedRulesACFPRuleSetProperty ?? undefined, awsManagedRulesATPRuleSetProperty: managedRuleGroup.awsManagedRulesATPRuleSetProperty ?? undefined, }); From 159dea61f422bb296592dd915a64e9273dc4b407 Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 12:40:38 +0100 Subject: [PATCH 04/24] add no rulelabels Guidance --- lib/tools/helpers/guidance.ts | 3 +++ lib/tools/helpers/web-application-firewall/rulegroups.ts | 2 ++ 2 files changed, 5 insertions(+) diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index b805a94f..c3b238b1 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -23,6 +23,9 @@ export function getGuidance(context: string, source?: string) { case "noBotControlRuleSetProperty": console.log("\x1b[31m","\n🚨 No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet - This setting will help you to specify the inspection level.\n More about BotControlRuleSetProperty: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.\n\n","\x1b[0m"); break; + case "noRuleLabels": + console.log("\x1b[31m",`\n🚨 No RuleLabels are used in CustomRule ${source} - Rule Labels help you to mitigate False/Positives.\n More about RuleLabels: https://docs.aws.amazon.com/waf/latest/developerguide/waf-labels.html.\n\n`,"\x1b[0m"); + break; default: break; } diff --git a/lib/tools/helpers/web-application-firewall/rulegroups.ts b/lib/tools/helpers/web-application-firewall/rulegroups.ts index 9a7e3db7..99eb7054 100644 --- a/lib/tools/helpers/web-application-firewall/rulegroups.ts +++ b/lib/tools/helpers/web-application-firewall/rulegroups.ts @@ -176,6 +176,7 @@ export function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post" } else { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-unsafe-assignment const { ruleLabels, ...cfnRulePropertii } = cfnRuleProperty; + guidanceHelper.getGuidance("noRuleLabels", rulename); cfnRuleProperties = cfnRulePropertii as wafv2.CfnWebACL.RuleProperty; } rules.push(cfnRuleProperties); @@ -416,6 +417,7 @@ export function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post" .ruleLabels ) { cfnRuleProperti = cfnRuleProperty; + guidanceHelper.getGuidance("noRuleLabels", rulename); } else { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { ruleLabels, ...cfnRulePropertii } = cfnRuleProperty; From de6f82e3fc403bca2801067e8865a5ec12799f98 Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 18:36:13 +0100 Subject: [PATCH 05/24] add new guidance: noAWSManagedIPDDoSList --- lib/tools/helpers/aws-firewall-factory.ts | 2 ++ lib/tools/helpers/guidance.ts | 3 +++ .../web-application-firewall/quotas-and-capacity.ts | 7 +++++++ lib/types/runtimeprops.ts | 1 + 4 files changed, 13 insertions(+) diff --git a/lib/tools/helpers/aws-firewall-factory.ts b/lib/tools/helpers/aws-firewall-factory.ts index 1db4bfd7..47ff15d8 100644 --- a/lib/tools/helpers/aws-firewall-factory.ts +++ b/lib/tools/helpers/aws-firewall-factory.ts @@ -62,6 +62,7 @@ export function initRuntimeProperties() : RuntimeProperties { ManagedRuleBotControlCount: 0, ManagedRuleATPCount: 0, CustomRuleCount: 0, + IpReputationListCount: 0, CustomRuleGroupCount: 0, CustomCaptchaRuleCount: 0 }, @@ -74,6 +75,7 @@ export function initRuntimeProperties() : RuntimeProperties { ManagedRuleGroupCount: 0, ManagedRuleBotControlCount: 0, ManagedRuleATPCount: 0, + IpReputationListCount: 0, CustomRuleCount: 0, CustomRuleGroupCount: 0, CustomCaptchaRuleCount: 0 diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index c3b238b1..505ed277 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -26,6 +26,9 @@ export function getGuidance(context: string, source?: string) { case "noRuleLabels": console.log("\x1b[31m",`\n🚨 No RuleLabels are used in CustomRule ${source} - Rule Labels help you to mitigate False/Positives.\n More about RuleLabels: https://docs.aws.amazon.com/waf/latest/developerguide/waf-labels.html.\n\n`,"\x1b[0m"); break; + case "noAWSManagedIPDDoSList": + console.log("\x1b[31m","\n🚨 No AWSManagedRulesAmazonIpReputationList is used in your Firewall - These Rules identify and block IPs acting as bots, conducting reconnaissance on AWS resources, or involved in DDoS activities. AWSManagedIPDDoSList rule has effectively blocked over 90% of malicious request floods.\n\n","\x1b[0m"); + break; default: break; } diff --git a/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts b/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts index 80ea60ac..8c79d805 100644 --- a/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts +++ b/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts @@ -240,6 +240,10 @@ async function calculateManagedRuleGroupCapacities(type: "Pre" | "Post",deployme processProperties.ManagedRuleATPCount += 1; break; } + case "AWSManagedRulesAmazonIpReputationList": { + processProperties.ManagedRuleATPCount += 1; + break; + } } } console.log(table(managedcapacitieslog)); @@ -554,5 +558,8 @@ export async function isWcuQuotaReached(deploymentRegion: string, runtimeProps: console.log(" 💡 Account WAF-WCU Quota: " +Number(quoteWcu).toString()); console.log(" 🧮 Calculated Custom Rule Capacity is: [" + customCapacity + "] (🥇[" + runtimeProps.PreProcess.Capacity + "] + 🥈[" + runtimeProps.PostProcess.Capacity + "]) \n ➕ ManagedRulesCapacity: ["+ runtimeProps.ManagedRuleCapacity +"] \n = Total Waf Capacity: " + totalWcu.toString() + "\n"); } + if(runtimeProps.PostProcess.IpReputationListCount === 0 && runtimeProps.PreProcess.IpReputationListCount === 0){ + guidanceHelper.getGuidance("noIpReputationList"); + } return wcuLimitReached; } \ No newline at end of file diff --git a/lib/types/runtimeprops.ts b/lib/types/runtimeprops.ts index 5307008a..363ebcaa 100644 --- a/lib/types/runtimeprops.ts +++ b/lib/types/runtimeprops.ts @@ -26,6 +26,7 @@ export interface ProcessProperties { ManagedRuleGroupCount: number, ManagedRuleBotControlCount: number, ManagedRuleATPCount: number, + AWSManagedRulesAmazonIpReputationListCount: number, CustomRuleCount: number, CustomRuleGroupCount: number, CustomCaptchaRuleCount: number From 0b51ce564b6ca503998ae3af86b5fb73b9f929d0 Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 18:42:38 +0100 Subject: [PATCH 06/24] Customise guidance severities --- CHANGELOG.md | 2 +- lib/tools/helpers/guidance.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd110099..ac1b3485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Released ## 4.2.1 ### Added -- Guidance Helper v1: This Helper is designed to provide comprehensive assistance in implementing Best Practices for AWS Firewalls. Additionally, it addresses [Issue279](https://github.com/globaldatanet/aws-firewall-factory/issues/279), ensuring a more robust and effective implementation. +- Guidance Helper v1: This Helper is designed to provide comprehensive assistance in implementing Best Practices for AWS Firewalls. Additionally, it addresses [Issue279](https://github.com/globaldatanet/aws-firewall-factory/issues/279), ensuring a more robust and effective implementation. Guidances have severities: ℹ️ - can be adapted, ⚠️ should be adapted, 🚨 must be adapted - exceptions of course confirm the rules. ## 4.2.0 ### Fixed - Output of the correct ManagedRuleGroup version if the stack has already been deployed, no version has been specifically set or Enforce Update has been set diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index 505ed277..946ec603 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -18,16 +18,16 @@ export function getGuidance(context: string, source?: string) { console.log("\x1b[31m"," ⚠️ Legacy functionality ⌛️ \n This functionality will be removed soon. \n\n","\x1b[0m"); break; case "byteMatchStatementPositionalConstraint": - console.log("\x1b[31m",`\n🚨 Found PositionalConstraint "${source}" in ByteMatchStatement - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.\n\n`,"\x1b[0m"); + console.log("\x1b[31m",`\nℹ️ Found PositionalConstraint "${source}" in ByteMatchStatement - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.\n\n`,"\x1b[0m"); break; case "noBotControlRuleSetProperty": - console.log("\x1b[31m","\n🚨 No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet - This setting will help you to specify the inspection level.\n More about BotControlRuleSetProperty: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.\n\n","\x1b[0m"); + console.log("\x1b[31m","\n⚠️ No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet - This setting will help you to specify the inspection level.\n More about BotControlRuleSetProperty: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.\n\n","\x1b[0m"); break; case "noRuleLabels": - console.log("\x1b[31m",`\n🚨 No RuleLabels are used in CustomRule ${source} - Rule Labels help you to mitigate False/Positives.\n More about RuleLabels: https://docs.aws.amazon.com/waf/latest/developerguide/waf-labels.html.\n\n`,"\x1b[0m"); + console.log("\x1b[31m",`\nℹ️ No RuleLabels are used in CustomRule ${source} - Rule Labels help you to mitigate False/Positives.\n More about RuleLabels: https://docs.aws.amazon.com/waf/latest/developerguide/waf-labels.html.\n\n`,"\x1b[0m"); break; case "noAWSManagedIPDDoSList": - console.log("\x1b[31m","\n🚨 No AWSManagedRulesAmazonIpReputationList is used in your Firewall - These Rules identify and block IPs acting as bots, conducting reconnaissance on AWS resources, or involved in DDoS activities. AWSManagedIPDDoSList rule has effectively blocked over 90% of malicious request floods.\n\n","\x1b[0m"); + console.log("\x1b[31m","\n⚠️ No AWSManagedRulesAmazonIpReputationList is used in your Firewall - These Rules identify and block IPs acting as bots, conducting reconnaissance on AWS resources, or involved in DDoS activities. AWSManagedIPDDoSList rule has effectively blocked over 90% of malicious request floods.\n\n","\x1b[0m"); break; default: break; From 224bc0c2ed8a7773000a0d12575e5e77a0c3f4e4 Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 18:49:41 +0100 Subject: [PATCH 07/24] fix runtime props init --- lib/types/runtimeprops.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/runtimeprops.ts b/lib/types/runtimeprops.ts index 363ebcaa..a0ac3724 100644 --- a/lib/types/runtimeprops.ts +++ b/lib/types/runtimeprops.ts @@ -26,7 +26,7 @@ export interface ProcessProperties { ManagedRuleGroupCount: number, ManagedRuleBotControlCount: number, ManagedRuleATPCount: number, - AWSManagedRulesAmazonIpReputationListCount: number, + IpReputationListCount: number, CustomRuleCount: number, CustomRuleGroupCount: number, CustomCaptchaRuleCount: number From e3f897eb87224aa7f280072fa54f184b8d58a787 Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 19:52:13 +0100 Subject: [PATCH 08/24] add guidance as own sections --- bin/aws-firewall-factory.ts | 3 +- lib/_web-application-firewall-stack.ts | 4 +- lib/tools/helpers/aws-firewall-factory.ts | 1 + lib/tools/helpers/guidance.ts | 33 ++++++++++---- .../quotas-and-capacity.ts | 22 +++++----- .../web-application-firewall/rulegroups.ts | 10 ++--- lib/tools/transformer.ts | 43 ++++++++++--------- lib/types/runtimeprops.ts | 1 + 8 files changed, 68 insertions(+), 49 deletions(-) diff --git a/bin/aws-firewall-factory.ts b/bin/aws-firewall-factory.ts index a845eb70..70f5e90b 100644 --- a/bin/aws-firewall-factory.ts +++ b/bin/aws-firewall-factory.ts @@ -58,7 +58,7 @@ void (async () => { await cloudformationHelper.setOutputsFromStack(deploymentRegion, runtimeProperties, config); if(config.General.DeployHash){ console.log("#️⃣ Deployment Hash for this WAF: "+ config.General.DeployHash); - guidanceHelper.getGuidance("deploymentHash"); + guidanceHelper.getGuidance("deploymentHash", runtimeProperties); } console.log(`🔥 Deploy FMS Policy: ${config.General.Prefix.toUpperCase()}-WAF-${config.WebAcl.Name.toUpperCase()}-${config.General.Stage.toUpperCase()}${config.General.DeployHash ? "-"+config.General.DeployHash.toUpperCase() : ""}\n ⦂ Type: @@ -116,5 +116,6 @@ void (async () => { }); await pricingHelper.isWafPriceCalculated(PriceRegions[deploymentRegion as RegionString], runtimeProperties, config,deploymentRegion); + await guidanceHelper.outputGuidance(runtimeProperties); } })(); \ No newline at end of file diff --git a/lib/_web-application-firewall-stack.ts b/lib/_web-application-firewall-stack.ts index 2a781751..e7ffb9d4 100644 --- a/lib/_web-application-firewall-stack.ts +++ b/lib/_web-application-firewall-stack.ts @@ -191,7 +191,7 @@ export class WafStack extends cdk.Stack { const MANAGEDRULEGROUPSINFO: string[]= [""]; let subVariables : SubVariables = {}; if (props.config.WebAcl.PreProcess.ManagedRuleGroups) { - const preProcessmanagedRgs = wafHelper.buildServiceDataManagedRgs(this, props.config.WebAcl.PreProcess.ManagedRuleGroups, managedRuleGroupVersionProvider, props.config.WebAcl.Scope); + const preProcessmanagedRgs = wafHelper.buildServiceDataManagedRgs(this, props.config.WebAcl.PreProcess.ManagedRuleGroups, managedRuleGroupVersionProvider, props.config.WebAcl.Scope, props.runtimeProperties); preProcessRuleGroups.push(...preProcessmanagedRgs.ServiceData); MANAGEDRULEGROUPSINFO.push(...preProcessmanagedRgs.ManagedRuleGroupInfo); subVariables = {...preProcessmanagedRgs.SubVariables}; @@ -199,7 +199,7 @@ export class WafStack extends cdk.Stack { console.log("\nℹ️ No ManagedRuleGroups defined in PreProcess."); } if (props.config.WebAcl.PostProcess.ManagedRuleGroups) { - const postProcessmanagedRgs = wafHelper.buildServiceDataManagedRgs(this, props.config.WebAcl.PostProcess.ManagedRuleGroups, managedRuleGroupVersionProvider, props.config.WebAcl.Scope); + const postProcessmanagedRgs = wafHelper.buildServiceDataManagedRgs(this, props.config.WebAcl.PostProcess.ManagedRuleGroups, managedRuleGroupVersionProvider, props.config.WebAcl.Scope, props.runtimeProperties); postProcessRuleGroups.push(...postProcessmanagedRgs.ServiceData); MANAGEDRULEGROUPSINFO.push(...postProcessmanagedRgs.ManagedRuleGroupInfo); subVariables = {...postProcessmanagedRgs.SubVariables}; diff --git a/lib/tools/helpers/aws-firewall-factory.ts b/lib/tools/helpers/aws-firewall-factory.ts index 47ff15d8..12086a95 100644 --- a/lib/tools/helpers/aws-firewall-factory.ts +++ b/lib/tools/helpers/aws-firewall-factory.ts @@ -51,6 +51,7 @@ export const outputInfoBanner = (config?:Config) => { */ export function initRuntimeProperties() : RuntimeProperties { return { + Guidance: [], ManagedRuleCapacity: 0, PostProcess: { Capacity: 0, diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index 946ec603..b1e05f1b 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -1,35 +1,50 @@ +import { RuntimeProperties } from "../../types/runtimeprops"; /** This function will help you to get guidance on implementing Best Practices for AWS Firewalls. @param context - The context of the guidance. For example, nestedRateStatement. @param source - The source of the guidance. For example, ManagedRuleGroup. */ -export function getGuidance(context: string, source?: string) { +export function getGuidance(context: string, runtimeProperties: RuntimeProperties, source?: string) { switch(context){ case "nestedRateStatement": - console.log("\x1b[31m",`\n🚨 Found Nested RateBasedStatement in ${source} - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. You can define a RateBasedStatement inside a web ACL and inside a rule group.\n\n`,"\x1b[0m"); + runtimeProperties.Guidance.push("\x1b[31m",`\n 🚨 Found Nested RateBasedStatement in ${source} - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); break; case "overrideActionManagedRuleGroup": - console.log("\x1b[31m",`\n🚨 OverrideAction of ManagedRuleGroup ${source} is set to COUNT, which simply tallies all rules within the group.\n However, this practice may create a vulnerability in your firewall and is not recommended.\n\n`,"\x1b[0m"); + runtimeProperties.Guidance.push("\x1b[31m",`\n 🚨 OverrideAction of ManagedRuleGroup ${source} is set to COUNT, which simply tallies all rules within the group.\n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); break; case "noManageRuleGroups": - console.log("\x1b[31m","\n🚨 No ManagedRuleGroups are used in your Firewall.\n More about ManagedRuleGroups: https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups.html.\n\n","\x1b[0m"); + runtimeProperties.Guidance.push("\x1b[31m","\n 🚨 No ManagedRuleGroups are used in your Firewall.\n https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups.html.","\x1b[0m"); break; case "deploymentHash": - console.log("\x1b[31m"," ⚠️ Legacy functionality ⌛️ \n This functionality will be removed soon. \n\n","\x1b[0m"); + runtimeProperties.Guidance.push("\x1b[33m"," ⚠️ Legacy functionality ⌛️ \n This functionality will be removed soon. \n","\x1b[0m"); break; case "byteMatchStatementPositionalConstraint": - console.log("\x1b[31m",`\nℹ️ Found PositionalConstraint "${source}" in ByteMatchStatement - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.\n\n`,"\x1b[0m"); + runtimeProperties.Guidance.push("\x1b[0m",`\n ℹ️ Found PositionalConstraint "${source}" in ByteMatchStatement - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.`,"\x1b[0m"); break; case "noBotControlRuleSetProperty": - console.log("\x1b[31m","\n⚠️ No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet - This setting will help you to specify the inspection level.\n More about BotControlRuleSetProperty: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.\n\n","\x1b[0m"); + runtimeProperties.Guidance.push("\x1b[33m","\n ⚠️ No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet.\n https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.","\x1b[0m"); break; case "noRuleLabels": - console.log("\x1b[31m",`\nℹ️ No RuleLabels are used in CustomRule ${source} - Rule Labels help you to mitigate False/Positives.\n More about RuleLabels: https://docs.aws.amazon.com/waf/latest/developerguide/waf-labels.html.\n\n`,"\x1b[0m"); + runtimeProperties.Guidance.push("\x1b[0m",`\n ℹ️ No RuleLabels are used in CustomRule ${source} - Rule Labels help you to mitigate False/Positives.\n`,"\x1b[0m"); break; case "noAWSManagedIPDDoSList": - console.log("\x1b[31m","\n⚠️ No AWSManagedRulesAmazonIpReputationList is used in your Firewall - These Rules identify and block IPs acting as bots, conducting reconnaissance on AWS resources, or involved in DDoS activities. AWSManagedIPDDoSList rule has effectively blocked over 90% of malicious request floods.\n\n","\x1b[0m"); + runtimeProperties.Guidance.push("\x1b[33m","\n ⚠️ No AWSManagedRulesAmazonIpReputationList is used in your Firewall - These Rules identify and block IPs acting as bots, conducting reconnaissance on AWS resources, or involved in DDoS activities. AWSManagedIPDDoSList rule has effectively blocked over 90% of malicious request floods.","\x1b[0m"); break; default: break; } +} + + +/** +This function will print out the collected guidance for your Firewall. +@param runtimeProperties - The runtimeProperties object. + */ +export function outputGuidance(runtimeProperties: RuntimeProperties) { + if(runtimeProperties.Guidance.length !== 0){ + console.log("\x1b[0m","\n📢 Guidance:","\x1b[0m"); + runtimeProperties.Guidance.forEach(element => { + console.log(element); + }); + } } \ No newline at end of file diff --git a/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts b/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts index 8c79d805..afb8248f 100644 --- a/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts +++ b/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts @@ -161,7 +161,7 @@ async function calculateCapacities( console.log(" 🥇 PreProcess: "); runtimeProperties.PreProcess.CustomRuleCount = config.WebAcl.PreProcess.CustomRules.length; runtimeProperties.PreProcess.CustomCaptchaRuleCount = config.WebAcl.PreProcess.CustomRules.filter(rule => rule.action.captcha).length; - runtimeProperties.PreProcess.Capacity = (await calculateCustomRulesCapacities(config.WebAcl.PreProcess.CustomRules, deploymentRegion, config.WebAcl.Scope)).reduce((a,b) => a+b, 0); + runtimeProperties.PreProcess.Capacity = (await calculateCustomRulesCapacities(config.WebAcl.PreProcess.CustomRules, deploymentRegion, config.WebAcl.Scope, runtimeProperties)).reduce((a,b) => a+b, 0); } if (!config.WebAcl.PostProcess.CustomRules) { console.log( @@ -171,7 +171,7 @@ async function calculateCapacities( console.log("\n 🥈 PostProcess: "); runtimeProperties.PostProcess.CustomRuleCount = config.WebAcl.PostProcess.CustomRules.length; runtimeProperties.PostProcess.CustomCaptchaRuleCount = config.WebAcl.PostProcess.CustomRules.filter(rule => rule.action.captcha).length; - runtimeProperties.PostProcess.Capacity = (await calculateCustomRulesCapacities(config.WebAcl.PostProcess.CustomRules, deploymentRegion, config.WebAcl.Scope)).reduce((a,b) => a+b, 0); + runtimeProperties.PostProcess.Capacity = (await calculateCustomRulesCapacities(config.WebAcl.PostProcess.CustomRules, deploymentRegion, config.WebAcl.Scope, runtimeProperties)).reduce((a,b) => a+b, 0); } console.log("\n👀 Get ManagedRule Capacity:\n"); if (!config.WebAcl.PreProcess.ManagedRuleGroups || config.WebAcl.PreProcess.ManagedRuleGroups?.length === 0) { @@ -208,7 +208,7 @@ async function calculateManagedRuleGroupCapacities(type: "Pre" | "Post",deployme processProperties = runtimeProperties.PostProcess; break; } - config.WebAcl.PreProcess.ManagedRuleGroups !== undefined && config.WebAcl.PostProcess.ManagedRuleGroups !== undefined ? guidanceHelper.getGuidance("noManageRuleGroups") : null; + config.WebAcl.PreProcess.ManagedRuleGroups !== undefined && config.WebAcl.PostProcess.ManagedRuleGroups !== undefined ? guidanceHelper.getGuidance("noManageRuleGroups", runtimeProperties) : null; const managedcapacitieslog = []; managedcapacitieslog.push(["➕ RuleName", "Capacity", "🏷 Specified Version", "🔄 EnforceUpdate"]); for (const managedrule of managedrules) { @@ -288,7 +288,7 @@ function filterStatements(statement: wafv2.CfnWebACL.StatementProperty){ * @param scope the scope of the WebACL, e.g. REGIONAL or CLOUDFRONT * @returns an array with the capacities of the supplied custom rules */ -async function calculateCustomRulesCapacities(customRules: FmsRule[], deploymentRegion: string, scope: "REGIONAL" | "CLOUDFRONT") { +async function calculateCustomRulesCapacities(customRules: FmsRule[], deploymentRegion: string, scope: "REGIONAL" | "CLOUDFRONT", runtimeProperties: RuntimeProperties) { const capacities = []; const capacitieslog = []; capacitieslog.push(["🔺 Priority", "➕ RuleName", "Capacity"]); @@ -323,7 +323,7 @@ async function calculateCustomRulesCapacities(customRules: FmsRule[], deployment capacities.push(regexPatternSetsStatementsCapacity(notregexPatternSetReferenceStatement)); } else{ - capacities.push(await calculateCustomRuleStatementsCapacity(customRule, deploymentRegion, scope)); + capacities.push(await calculateCustomRuleStatementsCapacity(customRule, deploymentRegion, scope, runtimeProperties)); } } else if(andStatement && andStatement.statements) { @@ -378,7 +378,7 @@ async function calculateCustomRulesCapacities(customRules: FmsRule[], deployment filterStatements(statement))}; if (filteredAndStatements && filteredAndStatements.statements && filteredAndStatements.statements.length > 0) { const calcRule = buildCustomRuleWithoutReferenceStatements(customRule, filteredAndStatements, false); - const capacity = await calculateCustomRuleStatementsCapacity(calcRule, deploymentRegion, scope); + const capacity = await calculateCustomRuleStatementsCapacity(calcRule, deploymentRegion, scope, runtimeProperties); capacities.push(capacity); } } @@ -411,12 +411,12 @@ async function calculateCustomRulesCapacities(customRules: FmsRule[], deployment }; if (filteredOrStatements && filteredOrStatements.statements && filteredOrStatements.statements.length > 0) { const calcRule = buildCustomRuleWithoutReferenceStatements(customRule, filteredOrStatements, true); - const capacity = await calculateCustomRuleStatementsCapacity(calcRule, deploymentRegion, scope); + const capacity = await calculateCustomRuleStatementsCapacity(calcRule, deploymentRegion, scope, runtimeProperties); capacities.push(capacity); } } else { - capacities.push(await calculateCustomRuleStatementsCapacity(customRule, deploymentRegion, scope)); + capacities.push(await calculateCustomRuleStatementsCapacity(customRule, deploymentRegion, scope, runtimeProperties)); } capacitieslog.push([customRule.priority, customRule.name,capacities[capacities.length-1]]); } @@ -444,9 +444,9 @@ function calculateIpsSetStatementCapacity(ipSetReferenceStatement: wafv2.CfnWebA * @param scope "REGIONAL" | "CLOUDFRONT" * @returns */ -async function calculateCustomRuleStatementsCapacity(customRule: FmsRule, deploymentRegion: string, scope: "REGIONAL" | "CLOUDFRONT") { +async function calculateCustomRuleStatementsCapacity(customRule: FmsRule, deploymentRegion: string, scope: "REGIONAL" | "CLOUDFRONT", runtimeProperties: RuntimeProperties) { const ruleCalculatedCapacityJson = []; - const rule = transformCdkRuletoSdkRule(customRule); + const rule = transformCdkRuletoSdkRule(customRule, runtimeProperties); ruleCalculatedCapacityJson.push(rule); const capacity = await getTotalCapacityOfRules( deploymentRegion, @@ -559,7 +559,7 @@ export async function isWcuQuotaReached(deploymentRegion: string, runtimeProps: console.log(" 🧮 Calculated Custom Rule Capacity is: [" + customCapacity + "] (🥇[" + runtimeProps.PreProcess.Capacity + "] + 🥈[" + runtimeProps.PostProcess.Capacity + "]) \n ➕ ManagedRulesCapacity: ["+ runtimeProps.ManagedRuleCapacity +"] \n = Total Waf Capacity: " + totalWcu.toString() + "\n"); } if(runtimeProps.PostProcess.IpReputationListCount === 0 && runtimeProps.PreProcess.IpReputationListCount === 0){ - guidanceHelper.getGuidance("noIpReputationList"); + guidanceHelper.getGuidance("noIpReputationList", runtimeProps); } return wcuLimitReached; } \ No newline at end of file diff --git a/lib/tools/helpers/web-application-firewall/rulegroups.ts b/lib/tools/helpers/web-application-firewall/rulegroups.ts index 99eb7054..ab1f6be8 100644 --- a/lib/tools/helpers/web-application-firewall/rulegroups.ts +++ b/lib/tools/helpers/web-application-firewall/rulegroups.ts @@ -22,15 +22,15 @@ const subVariables : SubVariables = {}; * @param regexPatternSets cdk.aws_wafv2.CfnRegexPatternSet[] * @returns adjustedRule */ -export function buildServiceDataManagedRgs(scope: Construct, managedRuleGroups: ManagedRuleGroup[], managedRuleGroupVersionProvider: cr.Provider, wafScope: string): { ServiceData: ServiceDataManagedRuleGroup[], ManagedRuleGroupInfo: string[], SubVariables: SubVariables } { +export function buildServiceDataManagedRgs(scope: Construct, managedRuleGroups: ManagedRuleGroup[], managedRuleGroupVersionProvider: cr.Provider, wafScope: string, runtimeProps: RuntimeProperties): { ServiceData: ServiceDataManagedRuleGroup[], ManagedRuleGroupInfo: string[], SubVariables: SubVariables } { const cfnManagedRuleGroup : ServiceDataManagedRuleGroup[] = []; for (const managedRuleGroup of managedRuleGroups) { if(managedRuleGroup.overrideAction?.type === "COUNT"){ // eslint-disable-next-line quotes - guidanceHelper.getGuidance("overrideActionManagedRuleGroup", managedRuleGroup.name); + guidanceHelper.getGuidance("overrideActionManagedRuleGroup", runtimeProps, managedRuleGroup.name); } if(managedRuleGroup.name === "AWSManagedRulesBotControlRuleSet"){ - managedRuleGroup.awsManagedRulesBotControlRuleSetProperty ? guidanceHelper.getGuidance("noBotControlRuleSetProperty") : undefined; + managedRuleGroup.awsManagedRulesBotControlRuleSetProperty ? undefined : guidanceHelper.getGuidance("noBotControlRuleSetProperty",runtimeProps); } if(NONEVERSIONEDMANAGEDRULEGRPOUP.find((rulegroup) => rulegroup === managedRuleGroup.name)){ console.log("\nℹ️ ManagedRuleGroup " + managedRuleGroup.name + " is not versioned. Skip Custom Resource for Versioning."); @@ -176,7 +176,7 @@ export function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post" } else { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-unsafe-assignment const { ruleLabels, ...cfnRulePropertii } = cfnRuleProperty; - guidanceHelper.getGuidance("noRuleLabels", rulename); + guidanceHelper.getGuidance("noRuleLabels", runtimeProps, rulename); cfnRuleProperties = cfnRulePropertii as wafv2.CfnWebACL.RuleProperty; } rules.push(cfnRuleProperties); @@ -417,7 +417,7 @@ export function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post" .ruleLabels ) { cfnRuleProperti = cfnRuleProperty; - guidanceHelper.getGuidance("noRuleLabels", rulename); + guidanceHelper.getGuidance("noRuleLabels", runtimeProps, rulename); } else { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { ruleLabels, ...cfnRulePropertii } = cfnRuleProperty; diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index aaf1f6b9..29c92701 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -8,13 +8,14 @@ import { NotStatement, LabelMatchStatement, OrStatement, AndStatement, XssMatchS IPSetReferenceStatement, SizeConstraintStatement, Rule, RegexMatchStatement, RateBasedStatement, ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body } from "@aws-sdk/client-wafv2"; import { wafHelper, guidanceHelper} from "./helpers"; +import { RuntimeProperties } from "../types/runtimeprops"; /** * The function will map a CDK ByteMatchStatement Property to a SDK ByteMatchStatement Property * @param statement object of a CDK ByteMatchStatement Property * @return configuration object of a SDK ByteMatchStatement Property */ -export function transformByteMatchStatement(statement: wafv2.CfnWebACL.ByteMatchStatementProperty): ByteMatchStatement { +export function transformByteMatchStatement(statement: wafv2.CfnWebACL.ByteMatchStatementProperty, runtimeProperties: RuntimeProperties): ByteMatchStatement { const bmst = statement as wafv2.CfnWebACL.ByteMatchStatementProperty | undefined; let ByteMatchStatement = undefined; if (bmst) { @@ -33,7 +34,7 @@ export function transformByteMatchStatement(statement: wafv2.CfnWebACL.ByteMatch }); } if(bmst.positionalConstraint === "CONTAINS" || bmst.positionalConstraint === "CONTAINS_WORD" || bmst.positionalConstraint === "STARTS_WITH" || bmst.positionalConstraint === "ENDS_WITH"){ - guidanceHelper.getGuidance("byteMatchStatementPositionalConstraint", bmst.positionalConstraint); + guidanceHelper.getGuidance("byteMatchStatementPositionalConstraint", runtimeProperties, bmst.positionalConstraint); } ByteMatchStatement = { PositionalConstraint: bmst.positionalConstraint, @@ -261,7 +262,7 @@ export function transformXssMatchStatement(statement: wafv2.CfnWebACL.XssMatchSt * @param statement object of a CDK And/OrStatement Property Property * @return configuration object of a SDK And/OrStatement Property Property */ -export function transformConcatenatedStatement(statement: wafv2.CfnWebACL.AndStatementProperty | wafv2.CfnWebACL.OrStatementProperty, isandStatement:boolean): AndStatement | OrStatement | undefined { +export function transformConcatenatedStatement(statement: wafv2.CfnWebACL.AndStatementProperty | wafv2.CfnWebACL.OrStatementProperty, isandStatement:boolean, runtimeProperties: RuntimeProperties): AndStatement | OrStatement | undefined { const Statements = []; let ConcatenatedStatement = undefined; if(statement.statements && Array.isArray(statement.statements)){ @@ -282,7 +283,7 @@ export function transformConcatenatedStatement(statement: wafv2.CfnWebACL.AndSta let AndStatement = undefined; switch(Object.keys(currentstatement)[0]){ case "byteMatchStatement": - ByteMatchStatement = transformByteMatchStatement(currentstatement.byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty); + ByteMatchStatement = transformByteMatchStatement(currentstatement.byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty, runtimeProperties); Statement.ByteMatchStatement = ByteMatchStatement as ByteMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "geoMatchStatement": @@ -314,7 +315,7 @@ export function transformConcatenatedStatement(statement: wafv2.CfnWebACL.AndSta Statement.LabelMatchStatement = LabelMatchStatement as LabelMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "notStatement": - NotStatement = tranformNotStatement(currentstatement.notStatement as wafv2.CfnWebACL.NotStatementProperty); + NotStatement = tranformNotStatement(currentstatement.notStatement as wafv2.CfnWebACL.NotStatementProperty, runtimeProperties); Statement.NotStatement = NotStatement as NotStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "regexMatchStatement": @@ -322,16 +323,16 @@ export function transformConcatenatedStatement(statement: wafv2.CfnWebACL.AndSta Statement.RegexMatchStatement = RegexMatchStatement as RegexMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "rateBasedStatement": - guidanceHelper.getGuidance("nestedRateStatement", "And/OrStatement"); - RateBasedStatement = tranformRateBasedStatement(currentstatement.rateBasedStatement as wafv2.CfnWebACL.RateBasedStatementProperty); + guidanceHelper.getGuidance("nestedRateStatement", runtimeProperties, "And/OrStatement"); + RateBasedStatement = tranformRateBasedStatement(currentstatement.rateBasedStatement as wafv2.CfnWebACL.RateBasedStatementProperty, runtimeProperties); Statement.RateBasedStatement = RateBasedStatement as RateBasedStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "orStatement": - OrStatement = transformConcatenatedStatement(currentstatement.orStatement as wafv2.CfnWebACL.OrStatementProperty, false); + OrStatement = transformConcatenatedStatement(currentstatement.orStatement as wafv2.CfnWebACL.OrStatementProperty, false, runtimeProperties); Statement.OrStatement = OrStatement as OrStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "andStatement": - AndStatement = transformConcatenatedStatement(currentstatement.andStatement as wafv2.CfnWebACL.AndStatementProperty, true); + AndStatement = transformConcatenatedStatement(currentstatement.andStatement as wafv2.CfnWebACL.AndStatementProperty, true, runtimeProperties); Statement.AndStatement = AndStatement as AndStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; default: @@ -374,7 +375,7 @@ export function transformLabelMatchStatement(statement: wafv2.CfnWebACL.LabelMat * @param statement object of a CDK NotStatement Property * @return configuration object of a SDK NotStatement Property */ -export function tranformNotStatement(statement: wafv2.CfnWebACL.NotStatementProperty): NotStatement { +export function tranformNotStatement(statement: wafv2.CfnWebACL.NotStatementProperty, runtimeProperties: RuntimeProperties): NotStatement { const nst = statement as wafv2.CfnWebACL.NotStatementProperty | undefined; let NotStatement = undefined; if (nst && nst.statement) { @@ -391,7 +392,7 @@ export function tranformNotStatement(statement: wafv2.CfnWebACL.NotStatementProp let RateBasedStatement = undefined; switch(Object.keys(nst.statement)[0]){ case "byteMatchStatement": - ByteMatchStatement = transformByteMatchStatement((nst.statement as wafv2.CfnWebACL.StatementProperty).byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty); + ByteMatchStatement = transformByteMatchStatement((nst.statement as wafv2.CfnWebACL.StatementProperty).byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty, runtimeProperties); Statement.ByteMatchStatement = ByteMatchStatement as ByteMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "geoMatchStatement": @@ -427,8 +428,8 @@ export function tranformNotStatement(statement: wafv2.CfnWebACL.NotStatementProp Statement.RegexMatchStatement = RegexMatchStatement as RegexMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "rateBasedStatement": - guidanceHelper.getGuidance("nestedRateStatement", "NotStatement"); - RateBasedStatement = tranformRateBasedStatement((nst.statement as wafv2.CfnWebACL.StatementProperty).rateBasedStatement as wafv2.CfnWebACL.RateBasedStatementProperty); + guidanceHelper.getGuidance("nestedRateStatement", runtimeProperties, "NotStatement"); + RateBasedStatement = tranformRateBasedStatement((nst.statement as wafv2.CfnWebACL.StatementProperty).rateBasedStatement as wafv2.CfnWebACL.RateBasedStatementProperty, runtimeProperties); Statement.RateBasedStatement = RateBasedStatement as RateBasedStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; default: @@ -444,7 +445,7 @@ export function tranformNotStatement(statement: wafv2.CfnWebACL.NotStatementProp * @param statement object of a CDK RateBasedStatement Property * @return configuration object of a SDK RateBasedStatement Property */ -export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedStatementProperty): RateBasedStatement { +export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedStatementProperty, runtimeProperties: RuntimeProperties): RateBasedStatement { const rbst = statement as wafv2.CfnWebACL.RateBasedStatementProperty | undefined; let RateBasedStatement = undefined; if (rbst && rbst.scopeDownStatement) { @@ -460,7 +461,7 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS let RegexMatchStatement = undefined; switch(Object.keys(rbst.scopeDownStatement)[0]){ case "byteMatchStatement": - ByteMatchStatement = transformByteMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty); + ByteMatchStatement = transformByteMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty, runtimeProperties); Statement.ByteMatchStatement = ByteMatchStatement as ByteMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. break; case "geoMatchStatement": @@ -521,7 +522,7 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS * @param cdkRule configuration object of a CDK Rule Property * @return configuration object of a SDK Rule Property */ -export function transformCdkRuletoSdkRule(cdkRule: wafv2.CfnWebACL.RuleProperty): Rule { +export function transformCdkRuletoSdkRule(cdkRule: wafv2.CfnWebACL.RuleProperty, runtimeProperties: RuntimeProperties): Rule { const action = (cdkRule.action as wafv2.CfnWebACL.RuleActionProperty) as wafv2.CfnWebACL.RuleActionProperty | undefined; let Action = undefined; if (action) { @@ -733,7 +734,7 @@ export function transformCdkRuletoSdkRule(cdkRule: wafv2.CfnWebACL.RuleProperty) switch(Object.keys(cdkRule.statement)[0]){ case "byteMatchStatement": - ByteMatchStatement = transformByteMatchStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty); + ByteMatchStatement = transformByteMatchStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty, runtimeProperties); break; case "geoMatchStatement": GeoMatchStatement = transformGeoMatchStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).geoMatchStatement as wafv2.CfnWebACL.GeoMatchStatementProperty); @@ -754,22 +755,22 @@ export function transformCdkRuletoSdkRule(cdkRule: wafv2.CfnWebACL.RuleProperty) XssMatchStatement = transformXssMatchStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).xssMatchStatement as wafv2.CfnWebACL.XssMatchStatementProperty); break; case "andStatement": - AndStatement = transformConcatenatedStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).andStatement as wafv2.CfnWebACL.AndStatementProperty, true); + AndStatement = transformConcatenatedStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).andStatement as wafv2.CfnWebACL.AndStatementProperty, true, runtimeProperties); break; case "orStatement": - OrStatement = transformConcatenatedStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).orStatement as wafv2.CfnWebACL.OrStatementProperty, false); + OrStatement = transformConcatenatedStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).orStatement as wafv2.CfnWebACL.OrStatementProperty, false, runtimeProperties); break; case "labelMatchStatement": LabelMatchStatement = transformLabelMatchStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).labelMatchStatement as wafv2.CfnWebACL.LabelMatchStatementProperty); break; case "notStatement": - NotStatement = tranformNotStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).notStatement as wafv2.CfnWebACL.NotStatementProperty); + NotStatement = tranformNotStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).notStatement as wafv2.CfnWebACL.NotStatementProperty, runtimeProperties); break; case "regexMatchStatement": RegexMatchStatement = transformRegexMatchStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).regexMatchStatement as wafv2.CfnWebACL.RegexMatchStatementProperty); break; case "rateBasedStatement": - RateBasedStatement = tranformRateBasedStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).rateBasedStatement as wafv2.CfnWebACL.RateBasedStatementProperty); + RateBasedStatement = tranformRateBasedStatement((cdkRule.statement as wafv2.CfnWebACL.StatementProperty).rateBasedStatement as wafv2.CfnWebACL.RateBasedStatementProperty, runtimeProperties); break; default: break; diff --git a/lib/types/runtimeprops.ts b/lib/types/runtimeprops.ts index a0ac3724..33b29b3b 100644 --- a/lib/types/runtimeprops.ts +++ b/lib/types/runtimeprops.ts @@ -1,4 +1,5 @@ export interface RuntimeProperties { + Guidance: string[], PreProcess: ProcessProperties, PostProcess: ProcessProperties, ManagedRuleCapacity: number, From 9f98b7187964dcf27d338dc0ee5ed86f510c12e9 Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 22:10:16 +0100 Subject: [PATCH 09/24] improve guidance helper --- lib/tools/helpers/aws-firewall-factory.ts | 12 ++++- lib/tools/helpers/guidance.ts | 55 ++++++++++++++++++----- lib/tools/transformer.ts | 2 +- lib/types/runtimeprops.ts | 14 +++++- 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/lib/tools/helpers/aws-firewall-factory.ts b/lib/tools/helpers/aws-firewall-factory.ts index 12086a95..30c64d39 100644 --- a/lib/tools/helpers/aws-firewall-factory.ts +++ b/lib/tools/helpers/aws-firewall-factory.ts @@ -51,7 +51,17 @@ export const outputInfoBanner = (config?:Config) => { */ export function initRuntimeProperties() : RuntimeProperties { return { - Guidance: [], + GuidanceSummary: [], + Guidance: { + nestedRateStatementCount: 0, + nestedRateStatementInfo: [], + overrideActionManagedRuleGroupCount: 0, + overrideActionManagedRuleGroupInfo: [], + byteMatchStatementPositionalConstraintCount: 0, + byteMatchStatementPositionalConstraintInfo: [], + noRuleLabelsCount: 0, + noRuleLabelsInfo: [] + }, ManagedRuleCapacity: 0, PostProcess: { Capacity: 0, diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index b1e05f1b..81717595 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -7,28 +7,32 @@ This function will help you to get guidance on implementing Best Practices for A export function getGuidance(context: string, runtimeProperties: RuntimeProperties, source?: string) { switch(context){ case "nestedRateStatement": - runtimeProperties.Guidance.push("\x1b[31m",`\n 🚨 Found Nested RateBasedStatement in ${source} - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); + runtimeProperties.Guidance.nestedRateStatementCount++; + source ? runtimeProperties.Guidance.nestedRateStatementInfo.push(source) : undefined; break; case "overrideActionManagedRuleGroup": - runtimeProperties.Guidance.push("\x1b[31m",`\n 🚨 OverrideAction of ManagedRuleGroup ${source} is set to COUNT, which simply tallies all rules within the group.\n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); + runtimeProperties.Guidance.overrideActionManagedRuleGroupCount++; + source ? runtimeProperties.Guidance.overrideActionManagedRuleGroupInfo.push(source) : undefined; break; case "noManageRuleGroups": - runtimeProperties.Guidance.push("\x1b[31m","\n 🚨 No ManagedRuleGroups are used in your Firewall.\n https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups.html.","\x1b[0m"); + runtimeProperties.GuidanceSummary.push("\x1b[31m","\n 🚨 No ManagedRuleGroups are used in your Firewall.\n https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups.html.","\x1b[0m"); break; case "deploymentHash": - runtimeProperties.Guidance.push("\x1b[33m"," ⚠️ Legacy functionality ⌛️ \n This functionality will be removed soon. \n","\x1b[0m"); + runtimeProperties.GuidanceSummary.push("\x1b[33m"," ⚠️ Legacy functionality ⌛️ \n This functionality will be removed soon. \n","\x1b[0m"); break; case "byteMatchStatementPositionalConstraint": - runtimeProperties.Guidance.push("\x1b[0m",`\n ℹ️ Found PositionalConstraint "${source}" in ByteMatchStatement - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.`,"\x1b[0m"); + runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount++; + source ? runtimeProperties.Guidance.byteMatchStatementPositionalConstraintInfo.push(source) : undefined; break; case "noBotControlRuleSetProperty": - runtimeProperties.Guidance.push("\x1b[33m","\n ⚠️ No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet.\n https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.","\x1b[0m"); + runtimeProperties.GuidanceSummary.push("\x1b[33m","\n ⚠️ No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet.\n https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.","\x1b[0m"); break; case "noRuleLabels": - runtimeProperties.Guidance.push("\x1b[0m",`\n ℹ️ No RuleLabels are used in CustomRule ${source} - Rule Labels help you to mitigate False/Positives.\n`,"\x1b[0m"); + runtimeProperties.Guidance.noRuleLabelsCount++; + source ? runtimeProperties.Guidance.noRuleLabelsInfo.push(source) : undefined; break; case "noAWSManagedIPDDoSList": - runtimeProperties.Guidance.push("\x1b[33m","\n ⚠️ No AWSManagedRulesAmazonIpReputationList is used in your Firewall - These Rules identify and block IPs acting as bots, conducting reconnaissance on AWS resources, or involved in DDoS activities. AWSManagedIPDDoSList rule has effectively blocked over 90% of malicious request floods.","\x1b[0m"); + runtimeProperties.GuidanceSummary.push("\x1b[33m","\n ⚠️ No AWSManagedRulesAmazonIpReputationList is used in your Firewall - These Rules identify and block IPs acting as bots, conducting reconnaissance on AWS resources, or involved in DDoS activities. AWSManagedIPDDoSList rule has effectively blocked over 90% of malicious request floods.","\x1b[0m"); break; default: break; @@ -41,10 +45,39 @@ This function will print out the collected guidance for your Firewall. @param runtimeProperties - The runtimeProperties object. */ export function outputGuidance(runtimeProperties: RuntimeProperties) { - if(runtimeProperties.Guidance.length !== 0){ + if(runtimeProperties.GuidanceSummary.length !== 0){ console.log("\x1b[0m","\n📢 Guidance:","\x1b[0m"); - runtimeProperties.Guidance.forEach(element => { + runtimeProperties.GuidanceSummary.forEach(element => { console.log(element); }); } -} \ No newline at end of file + if(runtimeProperties.Guidance.nestedRateStatementCount !== 0){ + console.log("\x1b[31m",`\n 🚨 Found Nested RateBasedStatement in ${runtimeProperties.Guidance.nestedRateStatementCount} - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); + console.log(" Statement Names:"); + runtimeProperties.Guidance.nestedRateStatementInfo.forEach(element => { + console.log(" "+element); + }); + } + if(runtimeProperties.Guidance.overrideActionManagedRuleGroupCount !== 0){ + console.log("\x1b[31m",`\n 🚨 Found OverrideAction of ManagedRuleGroup ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} - OverrideAction of ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group. However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); + console.log(" ManagedRuleGroup Names:"); + runtimeProperties.Guidance.overrideActionManagedRuleGroupInfo.forEach(element => { + console.log(" "+element); + }); + } + if(runtimeProperties.Guidance.noRuleLabelsCount !== 0){ + console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.noRuleLabelsCount} CustomRules without RuleLabels - Rule Labels help you to mitigate False/Positives.`,"\x1b[0m"); + console.log(" Custom Rule Names:"); + runtimeProperties.Guidance.noRuleLabelsInfo.forEach(element => { + console.log(" "+element); + }); + } + if(runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount !== 0){ + console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount} ByteMatchStatements with PositionalConstraint - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.`,"\x1b[0m"); + console.log(" CONTSTRAINT Info:"); + runtimeProperties.Guidance.byteMatchStatementPositionalConstraintInfo.forEach(element => { + console.log(" "+element); + }); + } + console.log("\n\n"); +} diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index 29c92701..153b1d3e 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -34,7 +34,7 @@ export function transformByteMatchStatement(statement: wafv2.CfnWebACL.ByteMatch }); } if(bmst.positionalConstraint === "CONTAINS" || bmst.positionalConstraint === "CONTAINS_WORD" || bmst.positionalConstraint === "STARTS_WITH" || bmst.positionalConstraint === "ENDS_WITH"){ - guidanceHelper.getGuidance("byteMatchStatementPositionalConstraint", runtimeProperties, bmst.positionalConstraint); + guidanceHelper.getGuidance("byteMatchStatementPositionalConstraint", runtimeProperties, bmst.positionalConstraint +" - SearchString: "+ bmst.searchString); } ByteMatchStatement = { PositionalConstraint: bmst.positionalConstraint, diff --git a/lib/types/runtimeprops.ts b/lib/types/runtimeprops.ts index 33b29b3b..bc4468a3 100644 --- a/lib/types/runtimeprops.ts +++ b/lib/types/runtimeprops.ts @@ -1,10 +1,22 @@ export interface RuntimeProperties { - Guidance: string[], + GuidanceSummary: string[], + Guidance: Guidance, PreProcess: ProcessProperties, PostProcess: ProcessProperties, ManagedRuleCapacity: number, Pricing: ResourcePrices, } + +export interface Guidance { + nestedRateStatementCount: number, + nestedRateStatementInfo: string[], + overrideActionManagedRuleGroupCount: number, + overrideActionManagedRuleGroupInfo: string[], + byteMatchStatementPositionalConstraintCount: number, + byteMatchStatementPositionalConstraintInfo: string[], + noRuleLabelsCount: number, + noRuleLabelsInfo: string[], +} export interface ResourcePrices { Policy: number, Rule: number, From 064691a3a087d3d1256a5852b11619bf43ee57a3 Mon Sep 17 00:00:00 2001 From: daknhh Date: Sun, 21 Jan 2024 22:13:23 +0100 Subject: [PATCH 10/24] improveguidance helper --- lib/tools/helpers/guidance.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index 81717595..706a2f44 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -52,14 +52,14 @@ export function outputGuidance(runtimeProperties: RuntimeProperties) { }); } if(runtimeProperties.Guidance.nestedRateStatementCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found Nested RateBasedStatement in ${runtimeProperties.Guidance.nestedRateStatementCount} - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); + console.log("\x1b[31m",`\n 🚨 Found ${runtimeProperties.Guidance.nestedRateStatementCount} Nested RateBasedStatement - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); console.log(" Statement Names:"); runtimeProperties.Guidance.nestedRateStatementInfo.forEach(element => { console.log(" "+element); }); } if(runtimeProperties.Guidance.overrideActionManagedRuleGroupCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found OverrideAction of ManagedRuleGroup ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} - OverrideAction of ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group. However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); + console.log("\x1b[31m",`\n 🚨 Found OverrideAction in ManagedRuleGroup - OverrideAction of ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group. However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); console.log(" ManagedRuleGroup Names:"); runtimeProperties.Guidance.overrideActionManagedRuleGroupInfo.forEach(element => { console.log(" "+element); From 7d257433b24ace4ee080eedcd5d82a8758d4984e Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 22 Jan 2024 08:36:13 +0100 Subject: [PATCH 11/24] Improve Guidance Helper output --- lib/tools/helpers/guidance.ts | 30 +++++++++++++++--------------- lib/tools/transformer.ts | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index 706a2f44..6d04a86b 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -15,24 +15,24 @@ export function getGuidance(context: string, runtimeProperties: RuntimePropertie source ? runtimeProperties.Guidance.overrideActionManagedRuleGroupInfo.push(source) : undefined; break; case "noManageRuleGroups": - runtimeProperties.GuidanceSummary.push("\x1b[31m","\n 🚨 No ManagedRuleGroups are used in your Firewall.\n https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups.html.","\x1b[0m"); + runtimeProperties.GuidanceSummary.push("\x1b[31m","\n 🚨 No ManagedRuleGroups are used in your Firewall.\n https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups.html.","\x1b[0m"); break; case "deploymentHash": - runtimeProperties.GuidanceSummary.push("\x1b[33m"," ⚠️ Legacy functionality ⌛️ \n This functionality will be removed soon. \n","\x1b[0m"); + runtimeProperties.GuidanceSummary.push("\x1b[33m"," ⚠️ Legacy functionality ⌛️ \n This functionality will be removed soon. \n","\x1b[0m"); break; case "byteMatchStatementPositionalConstraint": runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount++; source ? runtimeProperties.Guidance.byteMatchStatementPositionalConstraintInfo.push(source) : undefined; break; case "noBotControlRuleSetProperty": - runtimeProperties.GuidanceSummary.push("\x1b[33m","\n ⚠️ No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet.\n https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.","\x1b[0m"); + runtimeProperties.GuidanceSummary.push("\x1b[33m","\n ⚠️ No BotControlRuleSetProperty is used in your ManagedRulesBotControlRuleSet.\n https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesbotcontrolruleset.html.","\x1b[0m"); break; case "noRuleLabels": runtimeProperties.Guidance.noRuleLabelsCount++; source ? runtimeProperties.Guidance.noRuleLabelsInfo.push(source) : undefined; break; case "noAWSManagedIPDDoSList": - runtimeProperties.GuidanceSummary.push("\x1b[33m","\n ⚠️ No AWSManagedRulesAmazonIpReputationList is used in your Firewall - These Rules identify and block IPs acting as bots, conducting reconnaissance on AWS resources, or involved in DDoS activities. AWSManagedIPDDoSList rule has effectively blocked over 90% of malicious request floods.","\x1b[0m"); + runtimeProperties.GuidanceSummary.push("\x1b[33m","\n ⚠️ No AWSManagedRulesAmazonIpReputationList is used in your Firewall - These Rules identify and block IPs acting as bots, conducting reconnaissance on AWS resources, or involved in DDoS activities. AWSManagedIPDDoSList rule has effectively blocked over 90% of malicious request floods.","\x1b[0m"); break; default: break; @@ -46,37 +46,37 @@ This function will print out the collected guidance for your Firewall. */ export function outputGuidance(runtimeProperties: RuntimeProperties) { if(runtimeProperties.GuidanceSummary.length !== 0){ - console.log("\x1b[0m","\n📢 Guidance:","\x1b[0m"); + console.log("\x1b[0m","\n🛟 Guidance:","\x1b[0m"); runtimeProperties.GuidanceSummary.forEach(element => { console.log(element); }); } if(runtimeProperties.Guidance.nestedRateStatementCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found ${runtimeProperties.Guidance.nestedRateStatementCount} Nested RateBasedStatement - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); - console.log(" Statement Names:"); + console.log("\x1b[31m",`\n 🚨 Found ${runtimeProperties.Guidance.nestedRateStatementCount} Nested RateBasedStatement - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. \n You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); + console.log("\x1b[1m"," Affected Statements:\n","\x1b[0m"); runtimeProperties.Guidance.nestedRateStatementInfo.forEach(element => { - console.log(" "+element); + console.log(" − "+element); }); } if(runtimeProperties.Guidance.overrideActionManagedRuleGroupCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found OverrideAction in ManagedRuleGroup - OverrideAction of ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group. However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); - console.log(" ManagedRuleGroup Names:"); + console.log("\x1b[31m",`\n 🚨 Found OverrideAction in ManagedRuleGroup - OverrideAction of ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group. \n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); + console.log("\x1b[1m"," Affected ManagedRuleGroups:\n","\x1b[0m"); runtimeProperties.Guidance.overrideActionManagedRuleGroupInfo.forEach(element => { - console.log(" "+element); + console.log(" − "+element); }); } if(runtimeProperties.Guidance.noRuleLabelsCount !== 0){ console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.noRuleLabelsCount} CustomRules without RuleLabels - Rule Labels help you to mitigate False/Positives.`,"\x1b[0m"); - console.log(" Custom Rule Names:"); + console.log("\x1b[1m"," Affected CustomRules:\n","\x1b[0m"); runtimeProperties.Guidance.noRuleLabelsInfo.forEach(element => { - console.log(" "+element); + console.log(" − "+element); }); } if(runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount !== 0){ console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount} ByteMatchStatements with PositionalConstraint - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.`,"\x1b[0m"); - console.log(" CONTSTRAINT Info:"); + console.log("\x1b[1m"," Affected CONTSTRAINT Information:\n","\x1b[0m"); runtimeProperties.Guidance.byteMatchStatementPositionalConstraintInfo.forEach(element => { - console.log(" "+element); + console.log(" − "+element); }); } console.log("\n\n"); diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index 153b1d3e..7342a204 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -34,7 +34,7 @@ export function transformByteMatchStatement(statement: wafv2.CfnWebACL.ByteMatch }); } if(bmst.positionalConstraint === "CONTAINS" || bmst.positionalConstraint === "CONTAINS_WORD" || bmst.positionalConstraint === "STARTS_WITH" || bmst.positionalConstraint === "ENDS_WITH"){ - guidanceHelper.getGuidance("byteMatchStatementPositionalConstraint", runtimeProperties, bmst.positionalConstraint +" - SearchString: "+ bmst.searchString); + guidanceHelper.getGuidance("byteMatchStatementPositionalConstraint", runtimeProperties, "CONTSTRAINT: " + bmst.positionalConstraint +"; SearchString: "+ bmst.searchString+"; FieldtoMatch: "+ JSON.stringify(FieldToMatch)); } ByteMatchStatement = { PositionalConstraint: bmst.positionalConstraint, From 783bf99ffa55ffdf06f44275e964481cd2e0719a Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 22 Jan 2024 20:59:27 +0100 Subject: [PATCH 12/24] adjust changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb98db5..5ceefafc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Released +## 4.2.2 +### Added +- Guidance Helper v1: This Helper is designed to provide comprehensive assistance in implementing Best Practices for AWS Firewalls. Additionally, it addresses [Issue279](https://github.com/globaldatanet/aws-firewall-factory/issues/279), ensuring a more robust and effective implementation. Guidances have severities: ℹ️ - can be adapted, ⚠️ should be adapted, 🚨 must be adapted - exceptions of course confirm the rules. + ## 4.2.1 ### Fixed - [Issue285](https://github.com/globaldatanet/aws-firewall-factory/issues/285) - Resolved an issue where the redeployment of changed capacity was not functioning correctly due to inconsistencies in the writing of ProcessProperties for DeployedRuleGroups. From 95931a778f08f00ab7ce31b315a2ec0c787d75b7 Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 22 Jan 2024 21:23:50 +0100 Subject: [PATCH 13/24] WIP ratebasedStatement transformer --- lib/tools/helpers/aws-firewall-factory.ts | 1 + lib/tools/transformer.ts | 39 +++++++++++++++-------- lib/types/runtimeprops.ts | 1 + package.json | 2 +- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/lib/tools/helpers/aws-firewall-factory.ts b/lib/tools/helpers/aws-firewall-factory.ts index 30c64d39..d8857994 100644 --- a/lib/tools/helpers/aws-firewall-factory.ts +++ b/lib/tools/helpers/aws-firewall-factory.ts @@ -53,6 +53,7 @@ export function initRuntimeProperties() : RuntimeProperties { return { GuidanceSummary: [], Guidance: { + rateBasedStatementCount: 0, nestedRateStatementCount: 0, nestedRateStatementInfo: [], overrideActionManagedRuleGroupCount: 0, diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index 7342a204..77fcce28 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -6,7 +6,7 @@ import { aws_wafv2 as wafv2 } from "aws-cdk-lib"; import { NotStatement, LabelMatchStatement, OrStatement, AndStatement, XssMatchStatement, SqliMatchStatement, RegexPatternSetReferenceStatement, Statement, IPSetReferenceStatement, SizeConstraintStatement, Rule, RegexMatchStatement, RateBasedStatement, - ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body } from "@aws-sdk/client-wafv2"; + ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body, RateBasedStatementCustomKey } from "@aws-sdk/client-wafv2"; import { wafHelper, guidanceHelper} from "./helpers"; import { RuntimeProperties } from "../types/runtimeprops"; @@ -448,8 +448,11 @@ export function tranformNotStatement(statement: wafv2.CfnWebACL.NotStatementProp export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedStatementProperty, runtimeProperties: RuntimeProperties): RateBasedStatement { const rbst = statement as wafv2.CfnWebACL.RateBasedStatementProperty | undefined; let RateBasedStatement = undefined; + let Limit: number | undefined = undefined; + let Statement: Statement | undefined = undefined; + let AggregateKeyType: string | undefined = undefined; if (rbst && rbst.scopeDownStatement) { - const Statement: Statement ={}; + Statement = {}; let ByteMatchStatement = undefined; let GeoMatchStatement = undefined; let IPSetReferenceStatement = undefined; @@ -499,20 +502,28 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS default: break; } - let ForwardedIPConfig = undefined; - if (rbst.forwardedIpConfig) { - const fic = rbst.forwardedIpConfig as wafv2.CfnWebACL.ForwardedIPConfigurationProperty; - ForwardedIPConfig ={ - FallbackBehavior: fic.fallbackBehavior, - HeaderName: fic.headerName - }; - } - RateBasedStatement = { - ForwardedIPConfig, - ScopeDownStatement: Statement, - Limit: rbst.limit, + } + let ForwardedIPConfig = undefined; + if (rbst && rbst.forwardedIpConfig) { + const fic = rbst.forwardedIpConfig as wafv2.CfnWebACL.ForwardedIPConfigurationProperty; + ForwardedIPConfig ={ + FallbackBehavior: fic.fallbackBehavior, + HeaderName: fic.headerName }; } + if(rbst && rbst.limit){ + Limit = rbst.limit; + } + if(rbst && rbst.aggregateKeyType){ + AggregateKeyType = rbst.aggregateKeyType; + } + RateBasedStatement = { + ForwardedIPConfig, + ScopeDownStatement: Statement, + Limit, + AggregateKeyType + }; + return RateBasedStatement as RateBasedStatement; } diff --git a/lib/types/runtimeprops.ts b/lib/types/runtimeprops.ts index bc4468a3..f6d031bf 100644 --- a/lib/types/runtimeprops.ts +++ b/lib/types/runtimeprops.ts @@ -8,6 +8,7 @@ export interface RuntimeProperties { } export interface Guidance { + rateBasedStatementCount: number, nestedRateStatementCount: number, nestedRateStatementInfo: string[], overrideActionManagedRuleGroupCount: number, diff --git a/package.json b/package.json index fcd55dc9..b0ed16bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aws-firewall-factory", - "version": "4.2.1", + "version": "4.2.2", "bin": { "firewallfactory": "bin/aws-firewall-factory.js" }, From 26a1faf4d7770eb72601e31ce52ef7cd5b82ec9e Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 23 Jan 2024 08:41:42 +0100 Subject: [PATCH 14/24] WIP ratebasedStatement transformer --- lib/tools/transformer.ts | 162 +++++++++++++++++++++++---------------- 1 file changed, 96 insertions(+), 66 deletions(-) diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index 77fcce28..20910847 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -6,7 +6,7 @@ import { aws_wafv2 as wafv2 } from "aws-cdk-lib"; import { NotStatement, LabelMatchStatement, OrStatement, AndStatement, XssMatchStatement, SqliMatchStatement, RegexPatternSetReferenceStatement, Statement, IPSetReferenceStatement, SizeConstraintStatement, Rule, RegexMatchStatement, RateBasedStatement, - ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body, RateBasedStatementCustomKey } from "@aws-sdk/client-wafv2"; + ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body, RateBasedStatementCustomKey, RateLimitHeader } from "@aws-sdk/client-wafv2"; import { wafHelper, guidanceHelper} from "./helpers"; import { RuntimeProperties } from "../types/runtimeprops"; @@ -451,83 +451,113 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS let Limit: number | undefined = undefined; let Statement: Statement | undefined = undefined; let AggregateKeyType: string | undefined = undefined; - if (rbst && rbst.scopeDownStatement) { - Statement = {}; - let ByteMatchStatement = undefined; - let GeoMatchStatement = undefined; - let IPSetReferenceStatement = undefined; - let RegexPatternSetReferenceStatement = undefined; - let SizeConstraintStatement = undefined; - let SqliMatchStatement = undefined; - let XssMatchStatement = undefined; - let LabelMatchStatement = undefined; - let RegexMatchStatement = undefined; - switch(Object.keys(rbst.scopeDownStatement)[0]){ - case "byteMatchStatement": - ByteMatchStatement = transformByteMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty, runtimeProperties); - Statement.ByteMatchStatement = ByteMatchStatement as ByteMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; - case "geoMatchStatement": - GeoMatchStatement = transformGeoMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).geoMatchStatement as wafv2.CfnWebACL.GeoMatchStatementProperty); - Statement.GeoMatchStatement = GeoMatchStatement as GeoMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; - case "ipSetReferenceStatement": - IPSetReferenceStatement = transformIPSetReferenceStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).ipSetReferenceStatement as wafv2.CfnWebACL.IPSetReferenceStatementProperty); - Statement.IPSetReferenceStatement = IPSetReferenceStatement as IPSetReferenceStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; - case "regexPatternSetReferenceStatement": - RegexPatternSetReferenceStatement = transformRegexPatternSetReferenceStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).regexPatternSetReferenceStatement as wafv2.CfnWebACL.RegexPatternSetReferenceStatementProperty); - Statement.RegexPatternSetReferenceStatement = RegexPatternSetReferenceStatement as RegexPatternSetReferenceStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; - case "sizeConstraintStatement": - SizeConstraintStatement = transformSizeConstraintStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).sizeConstraintStatement as wafv2.CfnWebACL.SizeConstraintStatementProperty); - Statement.SizeConstraintStatement = SizeConstraintStatement as SizeConstraintStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; - case "sqliMatchStatement": - SqliMatchStatement = transformSqliMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).sqliMatchStatement as wafv2.CfnWebACL.SqliMatchStatementProperty); - Statement.SqliMatchStatement = SqliMatchStatement as SqliMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; - case "xssMatchStatement": - XssMatchStatement = transformXssMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).xssMatchStatement as wafv2.CfnWebACL.XssMatchStatementProperty); - Statement.XssMatchStatement = XssMatchStatement as XssMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; - case "labelMatchStatement": - LabelMatchStatement = transformLabelMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).labelMatchStatement as wafv2.CfnWebACL.LabelMatchStatementProperty); - Statement.LabelMatchStatement = LabelMatchStatement as LabelMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; - case "regexMatchStatement": - RegexMatchStatement = transformRegexMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).regexMatchStatement as wafv2.CfnWebACL.RegexMatchStatementProperty); - Statement.RegexMatchStatement = RegexMatchStatement as RegexMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. - break; - default: - break; - } - } + let CustomKeys: RateBasedStatementCustomKey[] | undefined = undefined; + let Header: RateLimitHeader | undefined = undefined; let ForwardedIPConfig = undefined; - if (rbst && rbst.forwardedIpConfig) { - const fic = rbst.forwardedIpConfig as wafv2.CfnWebACL.ForwardedIPConfigurationProperty; - ForwardedIPConfig ={ - FallbackBehavior: fic.fallbackBehavior, - HeaderName: fic.headerName - }; - } - if(rbst && rbst.limit){ - Limit = rbst.limit; - } - if(rbst && rbst.aggregateKeyType){ - AggregateKeyType = rbst.aggregateKeyType; + if(rbst){ + if (rbst.scopeDownStatement) { + Statement = {}; + let ByteMatchStatement = undefined; + let GeoMatchStatement = undefined; + let IPSetReferenceStatement = undefined; + let RegexPatternSetReferenceStatement = undefined; + let SizeConstraintStatement = undefined; + let SqliMatchStatement = undefined; + let XssMatchStatement = undefined; + let LabelMatchStatement = undefined; + let RegexMatchStatement = undefined; + switch(Object.keys(rbst.scopeDownStatement)[0]){ + case "byteMatchStatement": + ByteMatchStatement = transformByteMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).byteMatchStatement as wafv2.CfnWebACL.ByteMatchStatementProperty, runtimeProperties); + Statement.ByteMatchStatement = ByteMatchStatement as ByteMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. + break; + case "geoMatchStatement": + GeoMatchStatement = transformGeoMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).geoMatchStatement as wafv2.CfnWebACL.GeoMatchStatementProperty); + Statement.GeoMatchStatement = GeoMatchStatement as GeoMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. + break; + case "ipSetReferenceStatement": + IPSetReferenceStatement = transformIPSetReferenceStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).ipSetReferenceStatement as wafv2.CfnWebACL.IPSetReferenceStatementProperty); + Statement.IPSetReferenceStatement = IPSetReferenceStatement as IPSetReferenceStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. + break; + case "regexPatternSetReferenceStatement": + RegexPatternSetReferenceStatement = transformRegexPatternSetReferenceStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).regexPatternSetReferenceStatement as wafv2.CfnWebACL.RegexPatternSetReferenceStatementProperty); + Statement.RegexPatternSetReferenceStatement = RegexPatternSetReferenceStatement as RegexPatternSetReferenceStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. + break; + case "sizeConstraintStatement": + SizeConstraintStatement = transformSizeConstraintStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).sizeConstraintStatement as wafv2.CfnWebACL.SizeConstraintStatementProperty); + Statement.SizeConstraintStatement = SizeConstraintStatement as SizeConstraintStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. + break; + case "sqliMatchStatement": + SqliMatchStatement = transformSqliMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).sqliMatchStatement as wafv2.CfnWebACL.SqliMatchStatementProperty); + Statement.SqliMatchStatement = SqliMatchStatement as SqliMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. + break; + case "xssMatchStatement": + XssMatchStatement = transformXssMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).xssMatchStatement as wafv2.CfnWebACL.XssMatchStatementProperty); + Statement.XssMatchStatement = XssMatchStatement as XssMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. + break; + case "labelMatchStatement": + LabelMatchStatement = transformLabelMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).labelMatchStatement as wafv2.CfnWebACL.LabelMatchStatementProperty); + Statement.LabelMatchStatement = LabelMatchStatement as LabelMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. + break; + case "regexMatchStatement": + RegexMatchStatement = transformRegexMatchStatement((rbst.scopeDownStatement as wafv2.CfnWebACL.StatementProperty).regexMatchStatement as wafv2.CfnWebACL.RegexMatchStatementProperty); + Statement.RegexMatchStatement = RegexMatchStatement as RegexMatchStatement; // NOSONAR -> SonarQube is identitfying this line as a Major Issue, but it is not. Sonarqube identify the following Error: This assertion is unnecessary since it does not change the type of the expression. + break; + default: + break; + } + } + if (rbst.forwardedIpConfig) { + const fic = rbst.forwardedIpConfig as wafv2.CfnWebACL.ForwardedIPConfigurationProperty; + ForwardedIPConfig ={ + FallbackBehavior: fic.fallbackBehavior, + HeaderName: fic.headerName + }; + } + if(rbst.limit){ + Limit = rbst.limit; + } + if(rbst.aggregateKeyType){ + AggregateKeyType = rbst.aggregateKeyType; + } + if(rbst.customKeys){ + const customkeys = rbst.customKeys as wafv2.CfnWebACL.RateBasedStatementCustomKeyProperty; + CustomKeys = []; + if(customkeys.header){ + const header = customkeys.header as wafv2.CfnWebACL.RateLimitHeaderProperty; + let TextTransformations = undefined; + if (header.textTransformations) { + TextTransformations = []; + (header.textTransformations as wafv2.CfnWebACL.TextTransformationProperty[]).forEach((tt) => { + TextTransformations?.push({ + Priority: tt.priority, + Type: tt.type + }); + }); + } + Header = { + Name: header.name, + TextTransformations: TextTransformations, + }; + CustomKeys.push(Header as RateBasedStatementCustomKey); + } + } } RateBasedStatement = { ForwardedIPConfig, ScopeDownStatement: Statement, Limit, - AggregateKeyType + AggregateKeyType, + CustomKeys }; return RateBasedStatement as RateBasedStatement; } + + + /** * The function will map a CDK Rule Property to a SDK Rule Property * @param cdkRule configuration object of a CDK Rule Property From 7fe512d792a6a8b604aa41bdc4c5e535aadb2046 Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 23 Jan 2024 09:00:36 +0100 Subject: [PATCH 15/24] WIP ratebasedStatement transformer --- lib/tools/transformer.ts | 69 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index 20910847..c3016661 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -6,7 +6,7 @@ import { aws_wafv2 as wafv2 } from "aws-cdk-lib"; import { NotStatement, LabelMatchStatement, OrStatement, AndStatement, XssMatchStatement, SqliMatchStatement, RegexPatternSetReferenceStatement, Statement, IPSetReferenceStatement, SizeConstraintStatement, Rule, RegexMatchStatement, RateBasedStatement, - ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body, RateBasedStatementCustomKey, RateLimitHeader } from "@aws-sdk/client-wafv2"; + ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body, RateBasedStatementCustomKey, RateLimitHeader, RateLimitQueryString, RateLimitUriPath, RateLimitQueryArgument } from "@aws-sdk/client-wafv2"; import { wafHelper, guidanceHelper} from "./helpers"; import { RuntimeProperties } from "../types/runtimeprops"; @@ -541,6 +541,73 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS }; CustomKeys.push(Header as RateBasedStatementCustomKey); } + if(customkeys.cookie){ + const cookie = customkeys.cookie as wafv2.CfnWebACL.RateLimitCookieProperty; + let TextTransformations = undefined; + if (cookie.textTransformations) { + TextTransformations = []; + (cookie.textTransformations as wafv2.CfnWebACL.TextTransformationProperty[]).forEach((tt) => { + TextTransformations?.push({ + Priority: tt.priority, + Type: tt.type + }); + }); + } + const Cookie = { + Name: cookie.name, + TextTransformations: TextTransformations, + }; + CustomKeys.push(Cookie as RateBasedStatementCustomKey); + } + if(customkeys.ip){ + const ip = customkeys.ip as any; + const IP = { + Address: ip.address, + }; + CustomKeys.push(IP as RateBasedStatementCustomKey); + } + if(customkeys.labelNamespace){ + const labelNamespace = customkeys.labelNamespace as wafv2.CfnWebACL.RateLimitLabelNamespaceProperty; + const LabelNamespace = { + Namespace: labelNamespace.namespace, + }; + CustomKeys.push(LabelNamespace as RateBasedStatementCustomKey); + } + if(customkeys.queryArgument){ + const queryArgument = customkeys.queryArgument as wafv2.CfnWebACL.RateLimitQueryArgumentProperty; + let TextTransformations = undefined; + if (queryArgument.textTransformations) { + TextTransformations = []; + (queryArgument.textTransformations as wafv2.CfnWebACL.TextTransformationProperty[]).forEach((tt) => { + TextTransformations?.push({ + Priority: tt.priority, + Type: tt.type + }); + }); + } + const QueryArgument = { + Name: queryArgument.name, + TextTransformations: TextTransformations, + }; + CustomKeys.push(QueryArgument as RateBasedStatementCustomKey); + } + if(customkeys.queryString){ + const queryString = customkeys.queryString as wafv2.CfnWebACL.RateLimitQueryStringProperty; + let TextTransformations = undefined; + if (queryString.textTransformations) { + TextTransformations = []; + (queryString.textTransformations as wafv2.CfnWebACL.TextTransformationProperty[]).forEach((tt) => { + TextTransformations?.push({ + Priority: tt.priority, + Type: tt.type + }); + }); + } + const QueryString: RateLimitQueryString = { + TextTransformations: TextTransformations, + }; + CustomKeys.push(QueryString as RateBasedStatementCustomKey); + } } } RateBasedStatement = { From e459ae05ec8842b542e310e7366801b1dcc812b6 Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 23 Jan 2024 09:04:47 +0100 Subject: [PATCH 16/24] ratebasedStatement transformer --- CHANGELOG.md | 3 ++- lib/tools/transformer.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ceefafc..bb5d5dc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ ## 4.2.2 ### Added - Guidance Helper v1: This Helper is designed to provide comprehensive assistance in implementing Best Practices for AWS Firewalls. Additionally, it addresses [Issue279](https://github.com/globaldatanet/aws-firewall-factory/issues/279), ensuring a more robust and effective implementation. Guidances have severities: ℹ️ - can be adapted, ⚠️ should be adapted, 🚨 must be adapted - exceptions of course confirm the rules. - +### Fixed +- RateBasedStatement Transformation of rules from CDK to SDK was not working properly. This is required for WCU Calculation. This is fixed now. ## 4.2.1 ### Fixed - [Issue285](https://github.com/globaldatanet/aws-firewall-factory/issues/285) - Resolved an issue where the redeployment of changed capacity was not functioning correctly due to inconsistencies in the writing of ProcessProperties for DeployedRuleGroups. diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index c3016661..13ef492b 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -560,6 +560,7 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS CustomKeys.push(Cookie as RateBasedStatementCustomKey); } if(customkeys.ip){ + // eslint-disable-next-line @typescript-eslint/no-explicit-any const ip = customkeys.ip as any; const IP = { Address: ip.address, From 1a9a64cfe66f82766723606296e03e15b92ce9db Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 23 Jan 2024 09:10:32 +0100 Subject: [PATCH 17/24] ratebasedStatement transformer --- CHANGELOG.md | 3 ++- lib/tools/transformer.ts | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb5d5dc4..e164b510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ ### Added - Guidance Helper v1: This Helper is designed to provide comprehensive assistance in implementing Best Practices for AWS Firewalls. Additionally, it addresses [Issue279](https://github.com/globaldatanet/aws-firewall-factory/issues/279), ensuring a more robust and effective implementation. Guidances have severities: ℹ️ - can be adapted, ⚠️ should be adapted, 🚨 must be adapted - exceptions of course confirm the rules. ### Fixed -- RateBasedStatement Transformation of rules from CDK to SDK was not working properly. This is required for WCU Calculation. This is fixed now. +- The conversion of rules from CDK to SDK for RateBasedStatement was experiencing issues, impacting the proper functioning essential for WCU Calculation. I'm pleased to inform you that this issue has been successfully addressed and resolved. + ## 4.2.1 ### Fixed - [Issue285](https://github.com/globaldatanet/aws-firewall-factory/issues/285) - Resolved an issue where the redeployment of changed capacity was not functioning correctly due to inconsistencies in the writing of ProcessProperties for DeployedRuleGroups. diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index 13ef492b..2f8efa0a 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -6,7 +6,7 @@ import { aws_wafv2 as wafv2 } from "aws-cdk-lib"; import { NotStatement, LabelMatchStatement, OrStatement, AndStatement, XssMatchStatement, SqliMatchStatement, RegexPatternSetReferenceStatement, Statement, IPSetReferenceStatement, SizeConstraintStatement, Rule, RegexMatchStatement, RateBasedStatement, - ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body, RateBasedStatementCustomKey, RateLimitHeader, RateLimitQueryString, RateLimitUriPath, RateLimitQueryArgument } from "@aws-sdk/client-wafv2"; + ByteMatchStatement, GeoMatchStatement, FieldToMatch, JsonMatchScope, Headers, MapMatchScope, OversizeHandling, Cookies, JsonBody, Body, RateBasedStatementCustomKey, RateLimitHeader, RateLimitQueryString, RateLimitUriPath, RateLimitIP, RateLimitHTTPMethod } from "@aws-sdk/client-wafv2"; import { wafHelper, guidanceHelper} from "./helpers"; import { RuntimeProperties } from "../types/runtimeprops"; @@ -609,6 +609,39 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS }; CustomKeys.push(QueryString as RateBasedStatementCustomKey); } + if(customkeys.uriPath){ + const uriPath = customkeys.uriPath as wafv2.CfnWebACL.RateLimitUriPathProperty; + let TextTransformations = undefined; + if (uriPath.textTransformations) { + TextTransformations = []; + (uriPath.textTransformations as wafv2.CfnWebACL.TextTransformationProperty[]).forEach((tt) => { + TextTransformations?.push({ + Priority: tt.priority, + Type: tt.type + }); + }); + } + const UriPath: RateLimitUriPath = { + TextTransformations: TextTransformations, + }; + CustomKeys.push(UriPath as RateBasedStatementCustomKey); + } + if(customkeys.forwardedIp){ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const forwardedIp = customkeys.forwardedIp as any; + const ForwardedIp: RateLimitIP = { + HeaderName: forwardedIp.headerName, + }; + CustomKeys.push(ForwardedIp as RateBasedStatementCustomKey); + } + if(customkeys.httpMethod){ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const httpMethod = customkeys.httpMethod as any; + const HttpMethod: RateLimitHTTPMethod = { + Name: httpMethod.name, + }; + CustomKeys.push(HttpMethod as RateBasedStatementCustomKey); + } } } RateBasedStatement = { From 57a74b646486d573cc4a96509772be3158cf50b0 Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 23 Jan 2024 16:21:38 +0100 Subject: [PATCH 18/24] fix guidance output --- lib/tools/helpers/guidance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index 6d04a86b..8d6c8a0a 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -45,7 +45,7 @@ This function will print out the collected guidance for your Firewall. @param runtimeProperties - The runtimeProperties object. */ export function outputGuidance(runtimeProperties: RuntimeProperties) { - if(runtimeProperties.GuidanceSummary.length !== 0){ + if(runtimeProperties.GuidanceSummary.length !== 0 || runtimeProperties.Guidance.nestedRateStatementCount !== 0 || runtimeProperties.Guidance.overrideActionManagedRuleGroupCount !== 0 || runtimeProperties.Guidance.noRuleLabelsCount !== 0 || runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount !== 0){ console.log("\x1b[0m","\n🛟 Guidance:","\x1b[0m"); runtimeProperties.GuidanceSummary.forEach(element => { console.log(element); From cd45502ffe464e8e7df4ad180372dce0bc0ca215 Mon Sep 17 00:00:00 2001 From: daknhh Date: Thu, 25 Jan 2024 15:37:35 +0100 Subject: [PATCH 19/24] add rateBased guidance usage --- bin/aws-firewall-factory.ts | 2 +- lib/tools/helpers/guidance.ts | 7 ++++++- lib/tools/transformer.ts | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bin/aws-firewall-factory.ts b/bin/aws-firewall-factory.ts index 70f5e90b..8d6a68ef 100644 --- a/bin/aws-firewall-factory.ts +++ b/bin/aws-firewall-factory.ts @@ -116,6 +116,6 @@ void (async () => { }); await pricingHelper.isWafPriceCalculated(PriceRegions[deploymentRegion as RegionString], runtimeProperties, config,deploymentRegion); - await guidanceHelper.outputGuidance(runtimeProperties); + await guidanceHelper.outputGuidance(config, runtimeProperties); } })(); \ No newline at end of file diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index 8d6c8a0a..74e5740b 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -1,4 +1,6 @@ import { RuntimeProperties } from "../../types/runtimeprops"; +import { Config } from "../../types/config"; + /** This function will help you to get guidance on implementing Best Practices for AWS Firewalls. @param context - The context of the guidance. For example, nestedRateStatement. @@ -44,7 +46,7 @@ export function getGuidance(context: string, runtimeProperties: RuntimePropertie This function will print out the collected guidance for your Firewall. @param runtimeProperties - The runtimeProperties object. */ -export function outputGuidance(runtimeProperties: RuntimeProperties) { +export function outputGuidance(config: Config, runtimeProperties: RuntimeProperties) { if(runtimeProperties.GuidanceSummary.length !== 0 || runtimeProperties.Guidance.nestedRateStatementCount !== 0 || runtimeProperties.Guidance.overrideActionManagedRuleGroupCount !== 0 || runtimeProperties.Guidance.noRuleLabelsCount !== 0 || runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount !== 0){ console.log("\x1b[0m","\n🛟 Guidance:","\x1b[0m"); runtimeProperties.GuidanceSummary.forEach(element => { @@ -79,5 +81,8 @@ export function outputGuidance(runtimeProperties: RuntimeProperties) { console.log(" − "+element); }); } + if(runtimeProperties.Guidance.rateBasedStatementCount === 0 && config.WebAcl.Type === "AWS::ElasticLoadBalancingV2::LoadBalancer"){ + console.log("\x1b[0m","\n ℹ️ You are securing a LoadBalancer with your Firewall with usage of a RateBasedStatement - Rate-Based Statements empower you to automatically block requests originating from problematic source IPs until their request rate diminishes below a predetermined threshold.","\x1b[0m"); + } console.log("\n\n"); } diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index 2f8efa0a..5184578a 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -456,6 +456,7 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS let ForwardedIPConfig = undefined; if(rbst){ if (rbst.scopeDownStatement) { + runtimeProperties.Guidance.rateBasedStatementCount++; Statement = {}; let ByteMatchStatement = undefined; let GeoMatchStatement = undefined; From f965475ac2daaf0d9a4703c2f09f96a0fdc62b1b Mon Sep 17 00:00:00 2001 From: daknhh Date: Thu, 25 Jan 2024 15:42:26 +0100 Subject: [PATCH 20/24] Improve Guidance Output --- lib/tools/helpers/guidance.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index 74e5740b..1b165565 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -54,35 +54,35 @@ export function outputGuidance(config: Config, runtimeProperties: RuntimePropert }); } if(runtimeProperties.Guidance.nestedRateStatementCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found ${runtimeProperties.Guidance.nestedRateStatementCount} Nested RateBasedStatement - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. \n You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); + console.log("\x1b[31m",`\n 🚨 Found ${runtimeProperties.Guidance.nestedRateStatementCount} Nested RateBasedStatement - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. \n You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); console.log("\x1b[1m"," Affected Statements:\n","\x1b[0m"); runtimeProperties.Guidance.nestedRateStatementInfo.forEach(element => { console.log(" − "+element); }); } if(runtimeProperties.Guidance.overrideActionManagedRuleGroupCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found OverrideAction in ManagedRuleGroup - OverrideAction of ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group. \n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); + console.log("\x1b[31m",`\n 🚨 Found OverrideAction in ManagedRuleGroup - OverrideAction of ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group. \n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); console.log("\x1b[1m"," Affected ManagedRuleGroups:\n","\x1b[0m"); runtimeProperties.Guidance.overrideActionManagedRuleGroupInfo.forEach(element => { console.log(" − "+element); }); } if(runtimeProperties.Guidance.noRuleLabelsCount !== 0){ - console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.noRuleLabelsCount} CustomRules without RuleLabels - Rule Labels help you to mitigate False/Positives.`,"\x1b[0m"); + console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.noRuleLabelsCount} CustomRules without RuleLabels - Rule Labels help you to mitigate False/Positives.`,"\x1b[0m"); console.log("\x1b[1m"," Affected CustomRules:\n","\x1b[0m"); runtimeProperties.Guidance.noRuleLabelsInfo.forEach(element => { console.log(" − "+element); }); } if(runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount !== 0){ - console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount} ByteMatchStatements with PositionalConstraint - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.`,"\x1b[0m"); + console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount} ByteMatchStatements with PositionalConstraint - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.`,"\x1b[0m"); console.log("\x1b[1m"," Affected CONTSTRAINT Information:\n","\x1b[0m"); runtimeProperties.Guidance.byteMatchStatementPositionalConstraintInfo.forEach(element => { console.log(" − "+element); }); } if(runtimeProperties.Guidance.rateBasedStatementCount === 0 && config.WebAcl.Type === "AWS::ElasticLoadBalancingV2::LoadBalancer"){ - console.log("\x1b[0m","\n ℹ️ You are securing a LoadBalancer with your Firewall with usage of a RateBasedStatement - Rate-Based Statements empower you to automatically block requests originating from problematic source IPs until their request rate diminishes below a predetermined threshold.","\x1b[0m"); + console.log("\x1b[0m","\n ℹ️ You are securing a LoadBalancer with your Firewall with usage of a RateBasedStatement.\n RateBasedStatements empower you to automatically block requests originating from problematic source IPs until their request rate diminishes below a predetermined threshold.","\x1b[0m"); } console.log("\n\n"); } From db5e45a40ceee0d52ada70ac3b7a48512c5fbdde Mon Sep 17 00:00:00 2001 From: daknhh Date: Thu, 25 Jan 2024 15:46:38 +0100 Subject: [PATCH 21/24] Improve Guidance Output --- lib/tools/helpers/guidance.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index 1b165565..0f29e571 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -54,29 +54,29 @@ export function outputGuidance(config: Config, runtimeProperties: RuntimePropert }); } if(runtimeProperties.Guidance.nestedRateStatementCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found ${runtimeProperties.Guidance.nestedRateStatementCount} Nested RateBasedStatement - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement. \n You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); - console.log("\x1b[1m"," Affected Statements:\n","\x1b[0m"); + console.log("\x1b[31m",`\n 🚨 Found ${runtimeProperties.Guidance.nestedRateStatementCount} Nested RateBasedStatement - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement.\n You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); + console.log("\x1b[1m"," Affected Statements:\n","\x1b[0m"); runtimeProperties.Guidance.nestedRateStatementInfo.forEach(element => { console.log(" − "+element); }); } if(runtimeProperties.Guidance.overrideActionManagedRuleGroupCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found OverrideAction in ManagedRuleGroup - OverrideAction of ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group. \n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); - console.log("\x1b[1m"," Affected ManagedRuleGroups:\n","\x1b[0m"); + console.log("\x1b[31m",`\n 🚨 Found OverrideAction in ManagedRuleGroup - OverrideAction of ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group.\n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); + console.log("\x1b[1m"," Affected ManagedRuleGroups:\n","\x1b[0m"); runtimeProperties.Guidance.overrideActionManagedRuleGroupInfo.forEach(element => { console.log(" − "+element); }); } if(runtimeProperties.Guidance.noRuleLabelsCount !== 0){ console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.noRuleLabelsCount} CustomRules without RuleLabels - Rule Labels help you to mitigate False/Positives.`,"\x1b[0m"); - console.log("\x1b[1m"," Affected CustomRules:\n","\x1b[0m"); + console.log("\x1b[1m"," Affected CustomRules:\n","\x1b[0m"); runtimeProperties.Guidance.noRuleLabelsInfo.forEach(element => { console.log(" − "+element); }); } if(runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount !== 0){ console.log("\x1b[0m",`\n ℹ️ Found ${runtimeProperties.Guidance.byteMatchStatementPositionalConstraintCount} ByteMatchStatements with PositionalConstraint - It is cheaper from WCU perspektive to use a RegexMatchStatement in this Case.`,"\x1b[0m"); - console.log("\x1b[1m"," Affected CONTSTRAINT Information:\n","\x1b[0m"); + console.log("\x1b[1m"," Affected CONTSTRAINT Information:\n","\x1b[0m"); runtimeProperties.Guidance.byteMatchStatementPositionalConstraintInfo.forEach(element => { console.log(" − "+element); }); From 5d8f64106b8f649868d36bea9a0d907fdd877566 Mon Sep 17 00:00:00 2001 From: daknhh Date: Thu, 25 Jan 2024 15:47:37 +0100 Subject: [PATCH 22/24] Improve Guidance Output --- lib/tools/helpers/guidance.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tools/helpers/guidance.ts b/lib/tools/helpers/guidance.ts index 0f29e571..25820ef1 100644 --- a/lib/tools/helpers/guidance.ts +++ b/lib/tools/helpers/guidance.ts @@ -54,14 +54,14 @@ export function outputGuidance(config: Config, runtimeProperties: RuntimePropert }); } if(runtimeProperties.Guidance.nestedRateStatementCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found ${runtimeProperties.Guidance.nestedRateStatementCount} Nested RateBasedStatement - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement.\n You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); + console.log("\x1b[31m",`\n 🚨 Found ${runtimeProperties.Guidance.nestedRateStatementCount} Nested RateBasedStatement - You cannot nest a RateBasedStatement inside another statement, for example inside a NotStatement or OrStatement.\n You can define a RateBasedStatement inside a web ACL and inside a rule group.`,"\x1b[0m"); console.log("\x1b[1m"," Affected Statements:\n","\x1b[0m"); runtimeProperties.Guidance.nestedRateStatementInfo.forEach(element => { console.log(" − "+element); }); } if(runtimeProperties.Guidance.overrideActionManagedRuleGroupCount !== 0){ - console.log("\x1b[31m",`\n 🚨 Found OverrideAction in ManagedRuleGroup - OverrideAction of ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group.\n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); + console.log("\x1b[31m",`\n 🚨 Found OverrideAction in ManagedRuleGroup - OverrideAction of ${runtimeProperties.Guidance.overrideActionManagedRuleGroupCount} ManagedRuleGroup is set to COUNT, which simply tallies all rules within the group.\n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m"); console.log("\x1b[1m"," Affected ManagedRuleGroups:\n","\x1b[0m"); runtimeProperties.Guidance.overrideActionManagedRuleGroupInfo.forEach(element => { console.log(" − "+element); From 359c8e48db65adaf887484126048bee862d2ba70 Mon Sep 17 00:00:00 2001 From: daknhh Date: Thu, 25 Jan 2024 16:19:55 +0100 Subject: [PATCH 23/24] fix processprop naming --- .../helpers/web-application-firewall/quotas-and-capacity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts b/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts index afb8248f..6d682198 100644 --- a/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts +++ b/lib/tools/helpers/web-application-firewall/quotas-and-capacity.ts @@ -241,7 +241,7 @@ async function calculateManagedRuleGroupCapacities(type: "Pre" | "Post",deployme break; } case "AWSManagedRulesAmazonIpReputationList": { - processProperties.ManagedRuleATPCount += 1; + processProperties.IpReputationListCount += 1; break; } } From 0ee8abf03ebf9c5a155a44ae0aa4b96714c9a553 Mon Sep 17 00:00:00 2001 From: daknhh Date: Thu, 25 Jan 2024 16:23:07 +0100 Subject: [PATCH 24/24] fix counter --- lib/tools/transformer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tools/transformer.ts b/lib/tools/transformer.ts index 5184578a..b73333ba 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -455,8 +455,8 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS let Header: RateLimitHeader | undefined = undefined; let ForwardedIPConfig = undefined; if(rbst){ + runtimeProperties.Guidance.rateBasedStatementCount++; if (rbst.scopeDownStatement) { - runtimeProperties.Guidance.rateBasedStatementCount++; Statement = {}; let ByteMatchStatement = undefined; let GeoMatchStatement = undefined;