diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb98db5..e164b510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## 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. +### Fixed +- 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/bin/aws-firewall-factory.ts b/bin/aws-firewall-factory.ts index 50e8cd37..8d6a68ef 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", 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(config, 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 1db4bfd7..d8857994 100644 --- a/lib/tools/helpers/aws-firewall-factory.ts +++ b/lib/tools/helpers/aws-firewall-factory.ts @@ -51,6 +51,18 @@ export const outputInfoBanner = (config?:Config) => { */ export function initRuntimeProperties() : RuntimeProperties { return { + GuidanceSummary: [], + Guidance: { + rateBasedStatementCount: 0, + nestedRateStatementCount: 0, + nestedRateStatementInfo: [], + overrideActionManagedRuleGroupCount: 0, + overrideActionManagedRuleGroupInfo: [], + byteMatchStatementPositionalConstraintCount: 0, + byteMatchStatementPositionalConstraintInfo: [], + noRuleLabelsCount: 0, + noRuleLabelsInfo: [] + }, ManagedRuleCapacity: 0, PostProcess: { Capacity: 0, @@ -62,6 +74,7 @@ export function initRuntimeProperties() : RuntimeProperties { ManagedRuleBotControlCount: 0, ManagedRuleATPCount: 0, CustomRuleCount: 0, + IpReputationListCount: 0, CustomRuleGroupCount: 0, CustomCaptchaRuleCount: 0 }, @@ -74,6 +87,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 new file mode 100644 index 00000000..25820ef1 --- /dev/null +++ b/lib/tools/helpers/guidance.ts @@ -0,0 +1,88 @@ +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. +@param source - The source of the guidance. For example, ManagedRuleGroup. + */ +export function getGuidance(context: string, runtimeProperties: RuntimeProperties, source?: string) { + switch(context){ + case "nestedRateStatement": + runtimeProperties.Guidance.nestedRateStatementCount++; + source ? runtimeProperties.Guidance.nestedRateStatementInfo.push(source) : undefined; + break; + case "overrideActionManagedRuleGroup": + runtimeProperties.Guidance.overrideActionManagedRuleGroupCount++; + 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"); + break; + case "deploymentHash": + 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"); + 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"); + break; + default: + break; + } +} + + +/** +This function will print out the collected guidance for your Firewall. +@param runtimeProperties - The runtimeProperties object. + */ +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 => { + 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.\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"); + 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"); + 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"); + 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.\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"); +} 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..6d682198 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"; @@ -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,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", runtimeProperties) : null; const managedcapacitieslog = []; managedcapacitieslog.push(["āž• RuleName", "Capacity", "šŸ· Specified Version", "šŸ”„ EnforceUpdate"]); for (const managedrule of managedrules) { @@ -239,6 +240,10 @@ async function calculateManagedRuleGroupCapacities(type: "Pre" | "Post",deployme processProperties.ManagedRuleATPCount += 1; break; } + case "AWSManagedRulesAmazonIpReputationList": { + processProperties.IpReputationListCount += 1; + break; + } } } console.log(table(managedcapacitieslog)); @@ -283,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"]); @@ -318,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) { @@ -373,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); } } @@ -406,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]]); } @@ -439,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, @@ -553,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", 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 643a8c99..ab1f6be8 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"; @@ -21,12 +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 - 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", runtimeProps, managedRuleGroup.name); + } + if(managedRuleGroup.name === "AWSManagedRulesBotControlRuleSet"){ + 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."); @@ -42,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, }); @@ -172,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", runtimeProps, rulename); cfnRuleProperties = cfnRulePropertii as wafv2.CfnWebACL.RuleProperty; } rules.push(cfnRuleProperties); @@ -412,6 +417,7 @@ export function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post" .ruleLabels ) { cfnRuleProperti = cfnRuleProperty; + 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 35db4792..b73333ba 100644 --- a/lib/tools/transformer.ts +++ b/lib/tools/transformer.ts @@ -6,15 +6,16 @@ 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"; + 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"; /** * 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) { @@ -32,9 +33,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", runtimeProperties, "CONTSTRAINT: " + bmst.positionalConstraint +"; SearchString: "+ bmst.searchString+"; FieldtoMatch: "+ JSON.stringify(FieldToMatch)); + } ByteMatchStatement = { PositionalConstraint: bmst.positionalConstraint, - SearchString: bmst.searchString ? convertStringToUint8Array(bmst.searchString) : undefined, + SearchString: bmst.searchString ? wafHelper.convertStringToUint8Array(bmst.searchString) : undefined, TextTransformations, FieldToMatch }; @@ -258,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)){ @@ -279,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": @@ -311,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": @@ -319,15 +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": - 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: @@ -370,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) { @@ -387,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": @@ -423,7 +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": - 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: @@ -439,61 +445,69 @@ 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) { - const Statement: 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); - 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 Limit: number | undefined = undefined; + let Statement: Statement | undefined = undefined; + let AggregateKeyType: string | undefined = undefined; + let CustomKeys: RateBasedStatementCustomKey[] | undefined = undefined; + let Header: RateLimitHeader | undefined = undefined; + let ForwardedIPConfig = undefined; + if(rbst){ + runtimeProperties.Guidance.rateBasedStatementCount++; + 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; + } } - let ForwardedIPConfig = undefined; if (rbst.forwardedIpConfig) { const fic = rbst.forwardedIpConfig as wafv2.CfnWebACL.ForwardedIPConfigurationProperty; ForwardedIPConfig ={ @@ -501,22 +515,157 @@ export function tranformRateBasedStatement(statement: wafv2.CfnWebACL.RateBasedS HeaderName: fic.headerName }; } - RateBasedStatement = { - ForwardedIPConfig, - ScopeDownStatement: Statement, - Limit: rbst.limit, - }; + 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); + } + 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){ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + 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); + } + 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 = { + ForwardedIPConfig, + ScopeDownStatement: Statement, + Limit, + 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 * @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) { @@ -728,7 +877,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); @@ -749,22 +898,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 5307008a..f6d031bf 100644 --- a/lib/types/runtimeprops.ts +++ b/lib/types/runtimeprops.ts @@ -1,9 +1,23 @@ export interface RuntimeProperties { + GuidanceSummary: string[], + Guidance: Guidance, PreProcess: ProcessProperties, PostProcess: ProcessProperties, ManagedRuleCapacity: number, Pricing: ResourcePrices, } + +export interface Guidance { + rateBasedStatementCount: number, + nestedRateStatementCount: number, + nestedRateStatementInfo: string[], + overrideActionManagedRuleGroupCount: number, + overrideActionManagedRuleGroupInfo: string[], + byteMatchStatementPositionalConstraintCount: number, + byteMatchStatementPositionalConstraintInfo: string[], + noRuleLabelsCount: number, + noRuleLabelsInfo: string[], +} export interface ResourcePrices { Policy: number, Rule: number, @@ -26,6 +40,7 @@ export interface ProcessProperties { ManagedRuleGroupCount: number, ManagedRuleBotControlCount: number, ManagedRuleATPCount: number, + IpReputationListCount: number, CustomRuleCount: number, CustomRuleGroupCount: number, CustomCaptchaRuleCount: 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" },