From dd838ac9aca6872b9272a90012ccbde073fc3e27 Mon Sep 17 00:00:00 2001 From: daknhh Date: Thu, 10 Feb 2022 16:05:10 +0100 Subject: [PATCH 01/24] Update for preprocess and postprocess rules --- bin/plattform-wafv2-cdk-automation.ts | 135 ++- lib/plattform-wafv2-cdk-automation-stack.ts | 907 +++++++++++++------- lib/types/config.ts | 19 +- lib/types/runtimeprops.ts | 15 +- package.json | 4 +- 5 files changed, 749 insertions(+), 331 deletions(-) diff --git a/bin/plattform-wafv2-cdk-automation.ts b/bin/plattform-wafv2-cdk-automation.ts index 0ab1d45b..37ec4a51 100644 --- a/bin/plattform-wafv2-cdk-automation.ts +++ b/bin/plattform-wafv2-cdk-automation.ts @@ -14,7 +14,10 @@ import { validate } from "../lib/tools/config-validator"; import {Config} from "../lib/types/config"; import { Runtimeprops } from "../lib/types/runtimeprops"; -const runtimeprops: Runtimeprops = {Capacity: 0, DeployedRuleGroupCapacities: [], RuleCapacities: [], DeployedRuleGroupNames: [], DeployedRuleGroupIdentifier: []} +const runtimeprops: Runtimeprops = {PreProcessCapacity: 0, PostProcessCapacity: 0, + PreProcessDeployedRuleGroupCapacities: [], PreProcessRuleCapacities: [], PreProcessDeployedRuleGroupNames: [], PreProcessDeployedRuleGroupIdentifier: [], + PostProcessDeployedRuleGroupCapacities: [], PostProcessRuleCapacities: [], PostProcessDeployedRuleGroupNames: [], PostProcessDeployedRuleGroupIdentifier: [] +} function str2ab(str: string): Uint8Array { var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char @@ -153,17 +156,42 @@ async function GetOutputsFromStack(StackName:string,config: Config): Promise { - if(!(indexes.find((e)=> e === i+1))){ - if(v+tracker <= threshold){ - tracker += v - ruleset.push(i) - indexes.push(i+1) + console.log("\n\nℹ️ Custom Rules PreProcess: \n") + if (props.runtimeprops.PreProcessCapacity < 100){ + const rules = []; + let count = 1 + + for(const statement of props.config.WebAcl.PreProcess.CustomRules){ + let rulename = "" + if(statement.Name !== undefined){ + rulename = statement.Name + "-" + props.config.General.DeployHash + } + else{ + rulename = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + } + let CfnRuleProperty: wafv2.CfnRuleGroup.RuleProperty + if("Captcha" in statement.Action){ + CfnRuleProperty = { + name: rulename, + priority: count, + action: toCamel(statement.Action), + statement: toCamel(statement.Statement), + visibilityConfig: { + sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + captchaConfig: toCamel(statement.CaptchaConfig), + } + } + else{ + CfnRuleProperty = { + name: rulename, + priority: count, + action: toCamel(statement.Action), + statement: toCamel(statement.Statement), + visibilityConfig: { + sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + };} + rules.push(CfnRuleProperty) + count +=1 + } + + let name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash + let rulegroupidentifier = "RuleGroup" + if(typeof props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] !== "undefined"){ + if(props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] != props.runtimeprops.PreProcessCapacity){ + console.log("⭕️ Deploy new RuleGroup because the Capacity has changed!") + console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] + "]\n 🟩 New Capacity: [" + props.runtimeprops.PreProcessCapacity+"]") + if(props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[0] == "RuleGroup"){ + rulegroupidentifier ="RG" + } + + if(props.runtimeprops.PreProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash){ + name = props.config.General.Prefix.toUpperCase() + "-G" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash + } + console.log(" 💬 New Name: "+ name) + console.log(" 📇 New Identifier: "+ rulegroupidentifier) } } - }) - rulesets.push(ruleset) - rulegroupcapacities.push(tracker) + const rulegroup = new wafv2.CfnRuleGroup(this,rulegroupidentifier, { + capacity: props.runtimeprops.PreProcessCapacity, + scope: props.config.WebAcl.Scope, + rules: rules, + name: name, + visibilityConfig: { + sampledRequestsEnabled: false, + cloudWatchMetricsEnabled: false, + metricName: props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash, + } + }); + preProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); + console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + props.runtimeprops.PreProcessCapacity +"]") + props.runtimeprops.PreProcessDeployedRuleGroupCapacities.splice(0) + props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.splice(0) + props.runtimeprops.PreProcessDeployedRuleGroupNames.splice(0) + + props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[0] = rulegroupidentifier + props.runtimeprops.PreProcessDeployedRuleGroupNames[0] = name + props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] = props.runtimeprops.PreProcessCapacity + + + new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupNames", { + value: props.runtimeprops.PreProcessDeployedRuleGroupNames.toString(), + description: "PreProcessDeployedRuleGroupNames", + exportName: "PreProcessDeployedRuleGroupNames"+props.config.General.DeployHash, + }); + + new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupCapacities", { + value: props.runtimeprops.PreProcessDeployedRuleGroupCapacities.toString(), + description: "PreProcessDeployedRuleGroupCapacities", + exportName: "PreProcessDeployedRuleGroupCapacities"+props.config.General.DeployHash, + }); + + new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupIdentifier", { + value: props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.toString(), + description: "PreProcessDeployedRuleGroupIdentifier", + exportName: "PreProcessDeployedRuleGroupIdentifier"+props.config.General.DeployHash, + }); + } + else{ + const threshold = 100 + const rulesets: any[] = [] + const indexes: number[] = [] + const rulegroupcapacities = [] + while(indexes.length { + if(!(indexes.find((e)=> e === i+1))){ + if(v+tracker <= threshold){ + tracker += v + ruleset.push(i) + indexes.push(i+1) + } + } + }) + rulesets.push(ruleset) + rulegroupcapacities.push(tracker) + } - console.log(`🖖 Split Rules into ${rulesets.length.toString()} RuleGroups \n ℹ️ AWS Limitation 100 Capacity per RuleGroup\n`); - let count = 0 - const preProcessRuleGroups = [] - let rulegroupidentifier = "" - let name ="" - while (count < rulesets.length){ - if(typeof props.runtimeprops.DeployedRuleGroupCapacities[count] !== "undefined"){ - if(rulegroupcapacities[count] == props.runtimeprops.DeployedRuleGroupCapacities[count]){ - rulegroupidentifier = "R"+count.toString() - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash - } - else{ - console.log("\n⭕️ Deploy new RuleGroup because the Capacity has changed for " +props.runtimeprops.DeployedRuleGroupIdentifier[count] + " !") - console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.DeployedRuleGroupCapacities[count] + "]\n 🟩 New Capacity: [" + rulegroupcapacities[count] +"]") - if(typeof props.runtimeprops.DeployedRuleGroupNames[count] !== "undefined"){ - if(props.runtimeprops.DeployedRuleGroupNames[count] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString()+ "-" +props.config.General.DeployHash){ - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-R" + count.toString() + "-" +props.config.General.DeployHash + console.log(`🖖 Split Rules into ${rulesets.length.toString()} RuleGroups \n ℹ️ AWS Limitation 100 Capacity per RuleGroup\n`); + let count = 0 + const preProcessRuleGroups = [] + let rulegroupidentifier = "" + let name ="" + while (count < rulesets.length){ + if(typeof props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count] !== "undefined"){ + if(rulegroupcapacities[count] == props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count]){ + rulegroupidentifier = "R"+count.toString() + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + } + else{ + console.log("\n⭕️ Deploy new RuleGroup because the Capacity has changed for " +props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[count] + " !") + console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count] + "]\n 🟩 New Capacity: [" + rulegroupcapacities[count] +"]") + if(typeof props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count] !== "undefined"){ + if(props.runtimeprops.PreProcessDeployedRuleGroupNames[count] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString()+ "-" +props.config.General.DeployHash){ + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-R" + count.toString() + "-" +props.config.General.DeployHash + } + else{ + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + } + console.log(" 💬 New Name: "+ name) } - else{ - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + if(typeof props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[count] !== undefined){ + if(props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[count] == "R"+count.toString()){ + rulegroupidentifier = "preG"+count.toString() + } + else{ + rulegroupidentifier = "preR"+count.toString() + } + console.log(" 📇 New Identifier: "+ rulegroupidentifier + "\n") } - console.log(" 💬 New Name: "+ name) } - if(typeof props.runtimeprops.DeployedRuleGroupIdentifier[count] !== undefined){ - if(props.runtimeprops.DeployedRuleGroupIdentifier[count] == "R"+count.toString()){ - rulegroupidentifier = "G"+count.toString() + }else{ + rulegroupidentifier = "preR"+count.toString() + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + } + const CfnRuleProperties = [] + let rulegroupcounter = 0 + while( rulegroupcounter < rulesets[count].length){ + const statementindex = rulesets[count][rulegroupcounter] + let rulename = "" + if(props.config.WebAcl.PreProcess.CustomRules[statementindex].Name !== undefined){ + const Temp_Hash = Date.now().toString(36) + rulename = props.config.WebAcl.PreProcess.CustomRules[statementindex].Name + "-" + Temp_Hash + } + else{ + rulename = rulegroupcounter.toString() + } + let CfnRuleProperty: wafv2.CfnRuleGroup.RuleProperty + if("Captcha" in props.config.WebAcl.PreProcess.CustomRules[statementindex].Action){ + CfnRuleProperty = { + name: rulename, + priority: rulegroupcounter, + action: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].Action), + statement: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].Statement), + visibilityConfig: { + sampledRequestsEnabled: props.config.WebAcl.PreProcess.CustomRules[statementindex].VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: props.config.WebAcl.PreProcess.CustomRules[statementindex].VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + captchaConfig: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].CaptchaConfig), } - else{ - rulegroupidentifier = "R"+count.toString() + } + else{ + CfnRuleProperty = { + name: rulename, + priority: rulegroupcounter, + action: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].Action), + statement: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].Statement), + visibilityConfig: { + sampledRequestsEnabled: props.config.WebAcl.PreProcess.CustomRules[statementindex].VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: props.config.WebAcl.PreProcess.CustomRules[statementindex].VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + } } - console.log(" 📇 New Identifier: "+ rulegroupidentifier + "\n") } + + CfnRuleProperties.push(CfnRuleProperty) + rulegroupcounter++ } - }else{ - rulegroupidentifier = "R"+count.toString() - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + const rulegroup = new wafv2.CfnRuleGroup(this,rulegroupidentifier, { + capacity: rulegroupcapacities[count], + scope: props.config.WebAcl.Scope, + rules: CfnRuleProperties, + name: name, + visibilityConfig: { + sampledRequestsEnabled: false, + cloudWatchMetricsEnabled: false, + metricName: props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash, + } + }); + + preProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); + console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + rulegroupcapacities[count].toString() +"]") + props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count] = rulegroupcapacities[count] + props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[count] = rulegroupidentifier + props.runtimeprops.PreProcessDeployedRuleGroupNames[count] = name + count++ } - const CfnRuleProperties = [] - let rulegroupcounter = 0 - while( rulegroupcounter < rulesets[count].length){ - const statementindex = rulesets[count][rulegroupcounter] + const lenght = rulesets.length + props.runtimeprops.PreProcessDeployedRuleGroupCapacities.splice(lenght) + props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.splice(lenght) + props.runtimeprops.PreProcessDeployedRuleGroupNames.splice(lenght) + const novalue = null + + new cdk.CfnOutput(this, "DeployedRuleGroupNames", { + value: props.runtimeprops.PreProcessDeployedRuleGroupCapacities.toString(), + description: "DeployedRuleGroupNames", + exportName: "DeployedRuleGroupNames"+props.config.General.DeployHash, + }); + + new cdk.CfnOutput(this, "DeployedRuleGroupCapacities", { + value: props.runtimeprops.PreProcessDeployedRuleGroupCapacities.toString(), + description: "DeployedRuleGroupCapacities", + exportName: "DeployedRuleGroupCapacities"+props.config.General.DeployHash, + }); + + new cdk.CfnOutput(this, "DeployedRuleGroupIdentifier", { + value: props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.toString(), + description: "DeployedRuleGroupIdentifier", + exportName: "DeployedRuleGroupIdentifier"+props.config.General.DeployHash, + }); + } + } + if(props.config.WebAcl.PostProcess.CustomRules === undefined){ + console.log("\n\nℹ️ No Custom Rules defined in PostProcess.") + } + else{ + console.log("\n\nℹ️ Custom Rules PostProcess: \n") + if (props.runtimeprops.PostProcessCapacity < 100){ + const rules = []; + let count = 1 + + for(const statement of props.config.WebAcl.PostProcess.CustomRules){ let rulename = "" - if(props.config.WebAcl.Rules[statementindex].Name !== undefined){ - const Temp_Hash = Date.now().toString(36) - rulename = props.config.WebAcl.Rules[statementindex].Name + "-" + Temp_Hash + if(statement.Name !== undefined){ + rulename = statement.Name + "-" + props.config.General.DeployHash } else{ - rulename = rulegroupcounter.toString() + rulename = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash } let CfnRuleProperty: wafv2.CfnRuleGroup.RuleProperty - if("Captcha" in props.config.WebAcl.Rules[statementindex].Action){ + if("Captcha" in statement.Action){ CfnRuleProperty = { name: rulename, - priority: rulegroupcounter, - action: toCamel(props.config.WebAcl.Rules[statementindex].Action), - statement: toCamel(props.config.WebAcl.Rules[statementindex].Statement), + priority: count, + action: toCamel(statement.Action), + statement: toCamel(statement.Statement), visibilityConfig: { - sampledRequestsEnabled: props.config.WebAcl.Rules[statementindex].VisibilityConfig.SampledRequestsEnabled, - cloudWatchMetricsEnabled: props.config.WebAcl.Rules[statementindex].VisibilityConfig.CloudWatchMetricsEnabled, + sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, metricName: rulename + "-metric", }, - captchaConfig: toCamel(props.config.WebAcl.Rules[statementindex].CaptchaConfig), + captchaConfig: toCamel(statement.CaptchaConfig), } } - else{ + else{ CfnRuleProperty = { - name: rulename, - priority: rulegroupcounter, - action: toCamel(props.config.WebAcl.Rules[statementindex].Action), - statement: toCamel(props.config.WebAcl.Rules[statementindex].Statement), - visibilityConfig: { - sampledRequestsEnabled: props.config.WebAcl.Rules[statementindex].VisibilityConfig.SampledRequestsEnabled, - cloudWatchMetricsEnabled: props.config.WebAcl.Rules[statementindex].VisibilityConfig.CloudWatchMetricsEnabled, - metricName: rulename + "-metric", - } + name: rulename, + priority: count, + action: toCamel(statement.Action), + statement: toCamel(statement.Statement), + visibilityConfig: { + sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + };} + rules.push(CfnRuleProperty) + count +=1 + } + + let name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash + let rulegroupidentifier = "RuleGroup" + if(typeof props.runtimeprops.PostProcessDeployedRuleGroupCapacities[0] !== "undefined"){ + if(props.runtimeprops.PostProcessDeployedRuleGroupCapacities[0] != props.runtimeprops.PostProcessCapacity){ + console.log("⭕️ Deploy new RuleGroup because the Capacity has changed!") + console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PostProcessDeployedRuleGroupCapacities[0] + "]\n 🟩 New Capacity: [" + props.runtimeprops.PostProcessCapacity+"]") + if(props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[0] == "RuleGroup"){ + rulegroupidentifier ="RG" } + + if(props.runtimeprops.PostProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash){ + name = props.config.General.Prefix.toUpperCase() + "-G" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash + } + console.log(" 💬 New Name: "+ name) + console.log(" 📇 New Identifier: "+ rulegroupidentifier) } - - CfnRuleProperties.push(CfnRuleProperty) - rulegroupcounter++ } const rulegroup = new wafv2.CfnRuleGroup(this,rulegroupidentifier, { - capacity: rulegroupcapacities[count], + capacity: props.runtimeprops.PostProcessCapacity, scope: props.config.WebAcl.Scope, - rules: CfnRuleProperties, + rules: rules, name: name, visibilityConfig: { sampledRequestsEnabled: false, cloudWatchMetricsEnabled: false, - metricName: props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash, + metricName: props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash, } }); - - preProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); - console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + rulegroupcapacities[count].toString() +"]") - props.runtimeprops.DeployedRuleGroupCapacities[count] = rulegroupcapacities[count] - props.runtimeprops.DeployedRuleGroupIdentifier[count] = rulegroupidentifier - props.runtimeprops.DeployedRuleGroupNames[count] = name - count++ + postProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); + console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + props.runtimeprops.PostProcessCapacity +"]") + props.runtimeprops.PostProcessDeployedRuleGroupCapacities.splice(0) + props.runtimeprops.PostProcessDeployedRuleGroupIdentifier.splice(0) + props.runtimeprops.PostProcessDeployedRuleGroupNames.splice(0) + + props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[0] = rulegroupidentifier + props.runtimeprops.PostProcessDeployedRuleGroupNames[0] = name + props.runtimeprops.PostProcessDeployedRuleGroupCapacities[0] = props.runtimeprops.PostProcessCapacity + + + new cdk.CfnOutput(this, "PostProcessDeployedRuleGroupNames", { + value: props.runtimeprops.PostProcessDeployedRuleGroupNames.toString(), + description: "PostProcessDeployedRuleGroupNames", + exportName: "PostProcessDeployedRuleGroupNames"+props.config.General.DeployHash, + }); + + new cdk.CfnOutput(this, "PostProcessDeployedRuleGroupCapacities", { + value: props.runtimeprops.PostProcessDeployedRuleGroupCapacities.toString(), + description: "PostProcessDeployedRuleGroupCapacities", + exportName: "PostProcessDeployedRuleGroupCapacities"+props.config.General.DeployHash, + }); + + new cdk.CfnOutput(this, "PostProcessDeployedRuleGroupIdentifier", { + value: props.runtimeprops.PostProcessDeployedRuleGroupIdentifier.toString(), + description: "PostProcessDeployedRuleGroupIdentifier", + exportName: "PostProcessDeployedRuleGroupIdentifier"+props.config.General.DeployHash, + }); + } - const lenght = rulesets.length - props.runtimeprops.DeployedRuleGroupCapacities.splice(lenght) - props.runtimeprops.DeployedRuleGroupIdentifier.splice(lenght) - props.runtimeprops.DeployedRuleGroupNames.splice(lenght) - const novalue = null - - new cdk.CfnOutput(this, "DeployedRuleGroupNames", { - value: props.runtimeprops.DeployedRuleGroupNames.toString(), - description: "DeployedRuleGroupNames", - exportName: "DeployedRuleGroupNames"+props.config.General.DeployHash, - }); - - new cdk.CfnOutput(this, "DeployedRuleGroupCapacities", { - value: props.runtimeprops.DeployedRuleGroupCapacities.toString(), - description: "DeployedRuleGroupCapacities", - exportName: "DeployedRuleGroupCapacities"+props.config.General.DeployHash, - }); - - new cdk.CfnOutput(this, "DeployedRuleGroupIdentifier", { - value: props.runtimeprops.DeployedRuleGroupIdentifier.toString(), - description: "DeployedRuleGroupIdentifier", - exportName: "DeployedRuleGroupIdentifier"+props.config.General.DeployHash, - }); - + else{ + const threshold = 100 + const rulesets: any[] = [] + const indexes: number[] = [] + const rulegroupcapacities = [] + while(indexes.length { + if(!(indexes.find((e)=> e === i+1))){ + if(v+tracker <= threshold){ + tracker += v + ruleset.push(i) + indexes.push(i+1) + } + } + }) + rulesets.push(ruleset) + rulegroupcapacities.push(tracker) + } + + console.log(`🖖 Split Rules into ${rulesets.length.toString()} RuleGroups \n ℹ️ AWS Limitation 100 Capacity per RuleGroup\n`); + let count = 0 + const postProcessRuleGroups = [] + let rulegroupidentifier = "" + let name ="" + while (count < rulesets.length){ + if(typeof props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] !== "undefined"){ + if(rulegroupcapacities[count] == props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count]){ + rulegroupidentifier = "R"+count.toString() + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + } + else{ + console.log("\n⭕️ Deploy new RuleGroup because the Capacity has changed for " +props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[count] + " !") + console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] + "]\n 🟩 New Capacity: [" + rulegroupcapacities[count] +"]") + if(typeof props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] !== "undefined"){ + if(props.runtimeprops.PostProcessDeployedRuleGroupNames[count] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString()+ "-" +props.config.General.DeployHash){ + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-R" + count.toString() + "-" +props.config.General.DeployHash + } + else{ + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + } + console.log(" 💬 New Name: "+ name) + } + if(typeof props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[count] !== undefined){ + if(props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[count] == "R"+count.toString()){ + rulegroupidentifier = "preG"+count.toString() + } + else{ + rulegroupidentifier = "preR"+count.toString() + } + console.log(" 📇 New Identifier: "+ rulegroupidentifier + "\n") + } + } + }else{ + rulegroupidentifier = "preR"+count.toString() + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + } + const CfnRuleProperties = [] + let rulegroupcounter = 0 + while( rulegroupcounter < rulesets[count].length){ + const statementindex = rulesets[count][rulegroupcounter] + let rulename = "" + if(props.config.WebAcl.PostProcess.CustomRules[statementindex].Name !== undefined){ + const Temp_Hash = Date.now().toString(36) + rulename = props.config.WebAcl.PostProcess.CustomRules[statementindex].Name + "-" + Temp_Hash + } + else{ + rulename = rulegroupcounter.toString() + } + let CfnRuleProperty: wafv2.CfnRuleGroup.RuleProperty + if("Captcha" in props.config.WebAcl.PostProcess.CustomRules[statementindex].Action){ + CfnRuleProperty = { + name: rulename, + priority: rulegroupcounter, + action: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].Action), + statement: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].Statement), + visibilityConfig: { + sampledRequestsEnabled: props.config.WebAcl.PostProcess.CustomRules[statementindex].VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: props.config.WebAcl.PostProcess.CustomRules[statementindex].VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + captchaConfig: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].CaptchaConfig), + } + } + else{ + CfnRuleProperty = { + name: rulename, + priority: rulegroupcounter, + action: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].Action), + statement: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].Statement), + visibilityConfig: { + sampledRequestsEnabled: props.config.WebAcl.PostProcess.CustomRules[statementindex].VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: props.config.WebAcl.PostProcess.CustomRules[statementindex].VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + } + } + } + + CfnRuleProperties.push(CfnRuleProperty) + rulegroupcounter++ + } + const rulegroup = new wafv2.CfnRuleGroup(this,rulegroupidentifier, { + capacity: rulegroupcapacities[count], + scope: props.config.WebAcl.Scope, + rules: CfnRuleProperties, + name: name, + visibilityConfig: { + sampledRequestsEnabled: false, + cloudWatchMetricsEnabled: false, + metricName: props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash, + } + }); + + postProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); + console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + rulegroupcapacities[count].toString() +"]") + props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] = rulegroupcapacities[count] + props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[count] = rulegroupidentifier + props.runtimeprops.PostProcessDeployedRuleGroupNames[count] = name + count++ + } + const lenght = rulesets.length + props.runtimeprops.PostProcessDeployedRuleGroupCapacities.splice(lenght) + props.runtimeprops.PostProcessDeployedRuleGroupIdentifier.splice(lenght) + props.runtimeprops.PostProcessDeployedRuleGroupNames.splice(lenght) + const novalue = null + + new cdk.CfnOutput(this, "DeployedRuleGroupNames", { + value: props.runtimeprops.PostProcessDeployedRuleGroupCapacities.toString(), + description: "DeployedRuleGroupNames", + exportName: "DeployedRuleGroupNames"+props.config.General.DeployHash, + }); + + new cdk.CfnOutput(this, "DeployedRuleGroupCapacities", { + value: props.runtimeprops.PostProcessDeployedRuleGroupCapacities.toString(), + description: "DeployedRuleGroupCapacities", + exportName: "DeployedRuleGroupCapacities"+props.config.General.DeployHash, + }); + + new cdk.CfnOutput(this, "DeployedRuleGroupIdentifier", { + value: props.runtimeprops.PostProcessDeployedRuleGroupIdentifier.toString(), + description: "DeployedRuleGroupIdentifier", + exportName: "DeployedRuleGroupIdentifier"+props.config.General.DeployHash, + }); + } + } + const novalue = null + if(props.config.WebAcl.PostProcess.ManagedRuleGroups === undefined){ + console.log("\nℹ️ No ManagedRuleGroups defined in PostProcess.") + } + else{ let mangedrule; let ExcludeRules; let OverrideAction; - for(mangedrule of props.config.WebAcl.ManagedRuleGroups){ - if(mangedrule.ExcludeRules){ - ExcludeRules = toCamel(mangedrule.ExcludeRules) - OverrideAction = mangedrule.OverrideAction - } - else{ - ExcludeRules = [] - OverrideAction = { "type": "NONE" } - } - if(mangedrule.Version == ""){ - preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, - "managedRuleGroupName":mangedrule.Name,"version": novalue},"overrideAction": OverrideAction, - "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} - else{ - preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, - "managedRuleGroupName":mangedrule.Name,"version": mangedrule.Version},"overrideAction": OverrideAction, - "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + for(mangedrule of props.config.WebAcl.PostProcess.ManagedRuleGroups){ + if(mangedrule.ExcludeRules){ + ExcludeRules = toCamel(mangedrule.ExcludeRules) + OverrideAction = mangedrule.OverrideAction } - + else{ + ExcludeRules = [] + OverrideAction = { "type": "NONE" } + } + if(mangedrule.Version == ""){ + postProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": novalue},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + else{ + postProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": mangedrule.Version},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + } + } + if(props.config.WebAcl.PreProcess.ManagedRuleGroups === undefined){ + console.log("ℹ️ No ManagedRuleGroups defined in PreProcess.") + } + else{ + let mangedrule; + let ExcludeRules; + let OverrideAction; + for(mangedrule of props.config.WebAcl.PreProcess.ManagedRuleGroups){ + if(mangedrule.ExcludeRules){ + ExcludeRules = toCamel(mangedrule.ExcludeRules) + OverrideAction = mangedrule.OverrideAction + } + else{ + ExcludeRules = [] + OverrideAction = { "type": "NONE" } + } + if(mangedrule.Version == ""){ + preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": novalue},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + else{ + preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": mangedrule.Version},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + } + } + if(postProcessRuleGroups == []){ const securityservicepolicydata = { "type":"WAFV2", "defaultAction":{ "type":"ALLOW" }, @@ -516,6 +795,46 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { securityServicePolicyData: {"Type": "WAFV2","ManagedServiceData": cdk.Fn.sub(JSON.stringify(securityservicepolicydata))} }); } + if(preProcessRuleGroups == []){ + const securityservicepolicydata = { + "type":"WAFV2", + "defaultAction":{ "type":"ALLOW" }, + "preProcessRuleGroups": [], + "postProcessRuleGroups": postProcessRuleGroups, + "overrideCustomerWebACLAssociation":true, + "loggingConfiguration": { + "logDestinationConfigs":["${S3DeliveryStream.Arn}"] + } + } + const fmsPolicy = new fms.CfnPolicy(this, "CfnPolicy", { + excludeResourceTags: false, + remediationEnabled: false, + resourceType: props.config.WebAcl.Type, + policyName: props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage+ "-" +props.config.General.DeployHash, + includeMap: {account: props.config.General.DeployTo }, + securityServicePolicyData: {"Type": "WAFV2","ManagedServiceData": cdk.Fn.sub(JSON.stringify(securityservicepolicydata))} + }); + } + if(preProcessRuleGroups != [] && postProcessRuleGroups != []){ + const securityservicepolicydata = { + "type":"WAFV2", + "defaultAction":{ "type":"ALLOW" }, + "preProcessRuleGroups": preProcessRuleGroups, + "postProcessRuleGroups": postProcessRuleGroups, + "overrideCustomerWebACLAssociation":true, + "loggingConfiguration": { + "logDestinationConfigs":["${S3DeliveryStream.Arn}"] + } + } + const fmsPolicy = new fms.CfnPolicy(this, "CfnPolicy", { + excludeResourceTags: false, + remediationEnabled: false, + resourceType: props.config.WebAcl.Type, + policyName: props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage+ "-" +props.config.General.DeployHash, + includeMap: {account: props.config.General.DeployTo }, + securityServicePolicyData: {"Type": "WAFV2","ManagedServiceData": cdk.Fn.sub(JSON.stringify(securityservicepolicydata))} + }); + } } const options = { flag : "w", force: true }; diff --git a/lib/types/config.ts b/lib/types/config.ts index 7f99815e..76d1e7f2 100644 --- a/lib/types/config.ts +++ b/lib/types/config.ts @@ -1,3 +1,11 @@ +interface RulesArray{ + Name?: string, + Statement: any, + Action: any, + VisibilityConfig: any, + CaptchaConfig?: any, +} + export interface Config { readonly General: { readonly Prefix: string, @@ -12,11 +20,18 @@ export interface Config { readonly Name: string, readonly Scope: string, readonly Type: string, - readonly Rules: Array, - readonly ManagedRuleGroups: any[], + readonly PreProcess: { + CustomRules?: Array | undefined, + ManagedRuleGroups?: any[] | undefined; + } + readonly PostProcess:{ + CustomRules?: Array | undefined, + ManagedRuleGroups?: any[] | undefined; + } }, } + interface RulesArray{ Name?: string, Statement: any, diff --git a/lib/types/runtimeprops.ts b/lib/types/runtimeprops.ts index e80771a4..7f08e2af 100644 --- a/lib/types/runtimeprops.ts +++ b/lib/types/runtimeprops.ts @@ -1,7 +1,12 @@ export interface Runtimeprops { - Capacity: number, - RuleCapacities: number[], - DeployedRuleGroupCapacities: number[], - DeployedRuleGroupNames: string[], - DeployedRuleGroupIdentifier: string[] + PreProcessCapacity: number, + PostProcessCapacity: number, + PreProcessRuleCapacities: number[], + PostProcessRuleCapacities: number[], + PreProcessDeployedRuleGroupCapacities: number[], + PreProcessDeployedRuleGroupNames: string[], + PreProcessDeployedRuleGroupIdentifier: string[], + PostProcessDeployedRuleGroupCapacities: number[], + PostProcessDeployedRuleGroupNames: string[], + PostProcessDeployedRuleGroupIdentifier: string[], } \ No newline at end of file diff --git a/package.json b/package.json index d3f2385e..d3b280af 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "typescript": "~3.9.7" }, "dependencies": { - "typescript-json-schema": "^0.53.0", "@aws-sdk/client-cloudformation": "^3.40.0", "@aws-sdk/client-fms": "^3.43.0", "@aws-sdk/client-service-quotas": "^3.38.0", @@ -38,6 +37,7 @@ "constructs": "^10.0.0", "lodash": "^4.17.21", "process": "^0.11.10", - "shapes": "^0.4.0" + "shapes": "^0.4.0", + "typescript-json-schema": "^0.53.0" } } From 8dd6bcd96c053dba075f273cf3805816be27737a Mon Sep 17 00:00:00 2001 From: daknhh Date: Fri, 11 Feb 2022 10:31:17 +0100 Subject: [PATCH 02/24] Add Rule Labels --- CHANGELOG.md | 23 ++ bin/plattform-wafv2-cdk-automation.ts | 27 +- lib/plattform-wafv2-cdk-automation-stack.ts | 12 +- values/calculatecapacity.json | 3 +- values/example-waf.json | 267 ++++++++++---------- 5 files changed, 177 insertions(+), 155 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b94e5839..2ddd48c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,29 @@ # Change Log ## Released + +## 1.1.0 + +### Added +1. preProcessRuleGroups and postProcessRuleGroups - you can decide now where the Custom or ManagedRules should be added to. + +2. RuleLabels - A label is a string made up of a prefix, optional namespaces, and a name. The components of a label are delimited with a colon. Labels have the following requirements and characteristics: + + - Labels are case-sensitive. + + - Each label namespace or label name can have up to 128 characters. + + - You can specify up to five namespaces in a label. + + - Components of a label are separated by colon (:). +### Changed + +1. Values Structure: + + - Removed (Rules and ManagedRuleGroups) + - Added PreProcess and PostProcess + +ℹ️ See [example json](./values/example-waf.json). ## 1.0.4 ### Added diff --git a/bin/plattform-wafv2-cdk-automation.ts b/bin/plattform-wafv2-cdk-automation.ts index 37ec4a51..aaa6dec6 100644 --- a/bin/plattform-wafv2-cdk-automation.ts +++ b/bin/plattform-wafv2-cdk-automation.ts @@ -291,26 +291,25 @@ if (configFile && fs.existsSync(configFile)) { } else{ while (count < config.WebAcl.PostProcess.CustomRules.length) { + const rule_calculated_capacity_json = []; + const temp_template = template; if("Captcha" in config.WebAcl.PostProcess.CustomRules[count].Action){ - const rule_calculated_capacity_json = []; - const temp_template = template; - temp_template.Statement = config.WebAcl.PostProcess.CustomRules[count].Statement; - temp_template.Action = config.WebAcl.PostProcess.CustomRules[count].Action; temp_template.CaptchaConfig = config.WebAcl.PostProcess.CustomRules[count].CaptchaConfig; - rule_calculated_capacity_json.push(temp_template); - const capacity = await CheckCapacity(config.WebAcl.Scope, rule_calculated_capacity_json); - runtimeprops.PostProcessRuleCapacities.push(capacity); } else{ - const rule_calculated_capacity_json = []; - const temp_template = template; - temp_template.Statement = config.WebAcl.PostProcess.CustomRules[count].Statement; - temp_template.Action = config.WebAcl.PostProcess.CustomRules[count].Action; delete temp_template.CaptchaConfig - rule_calculated_capacity_json.push(temp_template); - const capacity = await CheckCapacity(config.WebAcl.Scope, rule_calculated_capacity_json); - runtimeprops.PostProcessRuleCapacities.push(capacity); } + if(config.WebAcl.PostProcess.CustomRules[count].RuleLabels){ + temp_template.RuleLabels = config.WebAcl.PostProcess.CustomRules[count].RuleLabels; + } + else{ + delete temp_template.RuleLabels + } + temp_template.Statement = config.WebAcl.PostProcess.CustomRules[count].Statement; + temp_template.Action = config.WebAcl.PostProcess.CustomRules[count].Action; + rule_calculated_capacity_json.push(temp_template); + const capacity = await CheckCapacity(config.WebAcl.Scope, rule_calculated_capacity_json); + runtimeprops.PostProcessRuleCapacities.push(capacity); count++ } post_calculate_capacity_sum = runtimeprops.PostProcessRuleCapacities.reduce(function (a, b) { diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index f66437b1..a4b7b290 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -248,6 +248,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { metricName: rulename + "-metric", }, captchaConfig: toCamel(statement.CaptchaConfig), + ruleLabels: toCamel(statement.RuleLabels), } } else{ @@ -261,6 +262,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, metricName: rulename + "-metric", }, + ruleLabels: toCamel(statement.RuleLabels) };} rules.push(CfnRuleProperty) count +=1 @@ -407,6 +409,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { metricName: rulename + "-metric", }, captchaConfig: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].CaptchaConfig), + ruleLabels: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].RuleLabels) } } else{ @@ -419,7 +422,8 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { sampledRequestsEnabled: props.config.WebAcl.PreProcess.CustomRules[statementindex].VisibilityConfig.SampledRequestsEnabled, cloudWatchMetricsEnabled: props.config.WebAcl.PreProcess.CustomRules[statementindex].VisibilityConfig.CloudWatchMetricsEnabled, metricName: rulename + "-metric", - } + }, + ruleLabels: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].RuleLabels) } } @@ -500,6 +504,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { metricName: rulename + "-metric", }, captchaConfig: toCamel(statement.CaptchaConfig), + ruleLabels: toCamel(statement.RuleLabels) } } else{ @@ -513,6 +518,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, metricName: rulename + "-metric", }, + ruleLabels: toCamel(statement.RuleLabels) };} rules.push(CfnRuleProperty) count +=1 @@ -659,6 +665,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { metricName: rulename + "-metric", }, captchaConfig: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].CaptchaConfig), + ruleLabels: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels), } } else{ @@ -671,7 +678,8 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { sampledRequestsEnabled: props.config.WebAcl.PostProcess.CustomRules[statementindex].VisibilityConfig.SampledRequestsEnabled, cloudWatchMetricsEnabled: props.config.WebAcl.PostProcess.CustomRules[statementindex].VisibilityConfig.CloudWatchMetricsEnabled, metricName: rulename + "-metric", - } + }, + ruleLabels: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels), } } diff --git a/values/calculatecapacity.json b/values/calculatecapacity.json index 885c55b4..749981e2 100644 --- a/values/calculatecapacity.json +++ b/values/calculatecapacity.json @@ -11,5 +11,6 @@ "SampledRequestsEnabled": false, "CloudWatchMetricsEnabled": false, "MetricName": "TEST" - } +}, +"RuleLabels": [] } diff --git a/values/example-waf.json b/values/example-waf.json index e362d6cf..593fec36 100644 --- a/values/example-waf.json +++ b/values/example-waf.json @@ -1,140 +1,131 @@ { - "General": { - "Prefix": "gdn", - "Stage": "int", - "DeployTo": [ - "" - ], - "S3LoggingBucketName": "", - "FireHoseKeyArn": "", - "DeployHash": "", - "SecuredDomain":"" - }, - "WebAcl": { - "Name": "DAVID", - "Scope": "REGIONAL", - "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", - "Rules": [ - { - "Statement": { - "AndStatement": { - "Statements": [ - { - "ByteMatchStatement": { - "SearchString": "test.de", - "FieldToMatch": { - "SingleHeader": { - "Name": "host" - } - }, - "TextTransformations": [ - { - "Priority": 0, - "Type": "NONE" - } - ], - "PositionalConstraint": "CONTAINS" - } - }, - { - "ByteMatchStatement": { - "SearchString": "test.de", - "FieldToMatch": { - "SingleHeader": { - "Name": "host" - } - }, - "TextTransformations": [ - { - "Priority": 0, - "Type": "NONE" - } - ], - "PositionalConstraint": "CONTAINS" - } - } - ] - } - }, - "Action": { - "Allow": {} - }, - "VisibilityConfig": { - "SampledRequestsEnabled": true, - "CloudWatchMetricsEnabled": true - } - }, - { - "Statement": { - "OrStatement": { - "Statements": [ - { - "RegexMatchStatement": { - "RegexString": "^.*\\/clients-registrations\\/openid-connect", - "FieldToMatch": { - "UriPath": {} - }, - "TextTransformations": [ - { - "Priority": 0, - "Type": "LOWERCASE" - } - ] - } - }, - { - "RegexMatchStatement": { - "RegexString": "^.*\\/console", - "FieldToMatch": { - "UriPath": {} - }, - "TextTransformations": [ - { - "Priority": 0, - "Type": "LOWERCASE" - } - ] - } - } - ] - } - }, - "Action": { - "Block": {} - }, - "VisibilityConfig": { - "SampledRequestsEnabled": true, - "CloudWatchMetricsEnabled": true - } - }, - ], - "ManagedRuleGroups": [ - { - "Vendor": "AWS", - "Name": "AWSManagedRulesCommonRuleSet", - "ExcludeRules": [ - { - "Name": "CrossSiteScripting_BODY" - }, - { - "Name": "GenericRFI_QUERYARGUMENTS" - }, - { - "Name": "NoUserAgent_HEADER" - } - ], - "Version": "", - "OverrideAction": { - "type": "COUNT" - }, - "Capacity": 700 - }, - { - "Vendor": "AWS", - "Name": "AWSManagedRulesAmazonIpReputationList", - "Version": "", - "Capacity": 25 - } - ] - } + "General": { + "Prefix": "gdn", + "Stage": "int", + "DeployTo": [ + "" + ], + "S3LoggingBucketName": "", + "FireHoseKeyArn": "", + "DeployHash": "", + "SecuredDomain": "" + }, + "WebAcl": { + "Name": "DAVID", + "Scope": "REGIONAL", + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "PostProcess": { + "CustomRules": [{ + "Statement": { + "AndStatement": { + "Statements": [{ + "ByteMatchStatement": { + "SearchString": "test.de", + "FieldToMatch": { + "SingleHeader": { + "Name": "host" + } + }, + "TextTransformations": [{ + "Priority": 0, + "Type": "NONE" + }], + "PositionalConstraint": "CONTAINS" + } + }, + { + "ByteMatchStatement": { + "SearchString": "test.de", + "FieldToMatch": { + "SingleHeader": { + "Name": "host" + } + }, + "TextTransformations": [{ + "Priority": 0, + "Type": "NONE" + }], + "PositionalConstraint": "CONTAINS" + } + } + ] + } + }, + "Action": { + "Allow": {} + }, + "VisibilityConfig": { + "SampledRequestsEnabled": true, + "CloudWatchMetricsEnabled": true + } + }, + { + "Statement": { + "OrStatement": { + "Statements": [{ + "RegexMatchStatement": { + "RegexString": "^.*\\/clients-registrations\\/openid-connect", + "FieldToMatch": { + "UriPath": {} + }, + "TextTransformations": [{ + "Priority": 0, + "Type": "LOWERCASE" + }] + } + }, + { + "RegexMatchStatement": { + "RegexString": "^.*\\/console", + "FieldToMatch": { + "UriPath": {} + }, + "TextTransformations": [{ + "Priority": 0, + "Type": "LOWERCASE" + }] + } + } + ] + } + }, + "Action": { + "Block": {} + }, + "VisibilityConfig": { + "SampledRequestsEnabled": true, + "CloudWatchMetricsEnabled": true + } + } + ] + }, + "PreProcess": { + "ManagedRuleGroups": [{ + "Vendor": "AWS", + "Name": "AWSManagedRulesCommonRuleSet", + "ExcludeRules": [{ + "Name": "CrossSiteScripting_BODY" + }, + { + "Name": "GenericRFI_QUERYARGUMENTS" + }, + { + "Name": "NoUserAgent_HEADER" + } + ], + "Version": "", + "OverrideAction": { + "type": "COUNT" + }, + "Capacity": 700 + }, + { + "Vendor": "AWS", + "Name": "AWSManagedRulesAmazonIpReputationList", + "Version": "", + "Capacity": 25 + } + ] + } + } } \ No newline at end of file From f4497e4a24024d062e2fda3d921d106a373439b4 Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 09:30:41 +0100 Subject: [PATCH 03/24] Add RuleLabels + Bugfixing --- lib/plattform-wafv2-cdk-automation-stack.ts | 159 ++++++++++++-------- lib/types/config.ts | 1 + 2 files changed, 97 insertions(+), 63 deletions(-) diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index a4b7b290..e7d15a3a 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -226,16 +226,15 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { if (props.runtimeprops.PreProcessCapacity < 100){ const rules = []; let count = 1 - for(const statement of props.config.WebAcl.PreProcess.CustomRules){ let rulename = "" if(statement.Name !== undefined){ - rulename = statement.Name + "-" + props.config.General.DeployHash + rulename = statement.Name + "-pre-" + props.config.General.DeployHash } else{ - rulename = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + rulename = props.config.WebAcl.Name + "-pre-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash } - let CfnRuleProperty: wafv2.CfnRuleGroup.RuleProperty + let CfnRuleProperty if("Captcha" in statement.Action){ CfnRuleProperty = { name: rulename, @@ -248,7 +247,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { metricName: rulename + "-metric", }, captchaConfig: toCamel(statement.CaptchaConfig), - ruleLabels: toCamel(statement.RuleLabels), + ruleLabels: toCamel(statement.RuleLabels) } } else{ @@ -264,18 +263,26 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { }, ruleLabels: toCamel(statement.RuleLabels) };} - rules.push(CfnRuleProperty) + let CfnRuleProperties: wafv2.CfnRuleGroup.RuleProperty + if(CfnRuleProperty.ruleLabels = []){ + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty + CfnRuleProperties = CfnRulePropertii + } + else{ + CfnRuleProperties = CfnRuleProperty + } + rules.push(CfnRuleProperties) count +=1 } - let name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash - let rulegroupidentifier = "RuleGroup" + let name = props.config.WebAcl.Name + "-pre-" + props.config.General.Stage + "-" +props.config.General.DeployHash + let rulegroupidentifier = "PreRuleGroup" if(typeof props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] !== "undefined"){ if(props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] != props.runtimeprops.PreProcessCapacity){ console.log("⭕️ Deploy new RuleGroup because the Capacity has changed!") console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] + "]\n 🟩 New Capacity: [" + props.runtimeprops.PreProcessCapacity+"]") if(props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[0] == "RuleGroup"){ - rulegroupidentifier ="RG" + rulegroupidentifier ="preRG" } if(props.runtimeprops.PreProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash){ @@ -355,18 +362,18 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { while (count < rulesets.length){ if(typeof props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count] !== "undefined"){ if(rulegroupcapacities[count] == props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count]){ - rulegroupidentifier = "R"+count.toString() - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + rulegroupidentifier = "preR"+count.toString() + name = props.config.WebAcl.Name + "-pre-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash } else{ console.log("\n⭕️ Deploy new RuleGroup because the Capacity has changed for " +props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[count] + " !") console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count] + "]\n 🟩 New Capacity: [" + rulegroupcapacities[count] +"]") if(typeof props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count] !== "undefined"){ if(props.runtimeprops.PreProcessDeployedRuleGroupNames[count] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString()+ "-" +props.config.General.DeployHash){ - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-R" + count.toString() + "-" +props.config.General.DeployHash + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-preR-" + count.toString() + "-" +props.config.General.DeployHash } else{ - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-pre-" + count.toString() + "-" +props.config.General.DeployHash } console.log(" 💬 New Name: "+ name) } @@ -396,7 +403,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { else{ rulename = rulegroupcounter.toString() } - let CfnRuleProperty: wafv2.CfnRuleGroup.RuleProperty + let CfnRuleProperty if("Captcha" in props.config.WebAcl.PreProcess.CustomRules[statementindex].Action){ CfnRuleProperty = { name: rulename, @@ -426,8 +433,16 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { ruleLabels: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].RuleLabels) } } + let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - CfnRuleProperties.push(CfnRuleProperty) + if(CfnRuleProperty.ruleLabels = []){ + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty + CfnRuleProperti = CfnRulePropertii + } + else{ + CfnRuleProperti = CfnRuleProperty + } + CfnRuleProperties.push(CfnRuleProperti) rulegroupcounter++ } const rulegroup = new wafv2.CfnRuleGroup(this,rulegroupidentifier, { @@ -486,12 +501,12 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { for(const statement of props.config.WebAcl.PostProcess.CustomRules){ let rulename = "" if(statement.Name !== undefined){ - rulename = statement.Name + "-" + props.config.General.DeployHash + rulename = statement.Name + "-post-" + props.config.General.DeployHash } else{ - rulename = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + rulename = props.config.WebAcl.Name + "-post-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash } - let CfnRuleProperty: wafv2.CfnRuleGroup.RuleProperty + let CfnRuleProperty if("Captcha" in statement.Action){ CfnRuleProperty = { name: rulename, @@ -520,22 +535,32 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { }, ruleLabels: toCamel(statement.RuleLabels) };} - rules.push(CfnRuleProperty) + let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty + if(CfnRuleProperty.ruleLabels = []){ + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty + CfnRuleProperti = CfnRulePropertii + } + else{ + CfnRuleProperti = CfnRuleProperty + } + console.log(CfnRuleProperti) + rules.push(CfnRuleProperti) + console.log(rules) count +=1 } - let name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash - let rulegroupidentifier = "RuleGroup" + let name = props.config.WebAcl.Name + "-post-" + props.config.General.Stage + "-" +props.config.General.DeployHash + let rulegroupidentifier = "PostRuleGroup" if(typeof props.runtimeprops.PostProcessDeployedRuleGroupCapacities[0] !== "undefined"){ if(props.runtimeprops.PostProcessDeployedRuleGroupCapacities[0] != props.runtimeprops.PostProcessCapacity){ console.log("⭕️ Deploy new RuleGroup because the Capacity has changed!") console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PostProcessDeployedRuleGroupCapacities[0] + "]\n 🟩 New Capacity: [" + props.runtimeprops.PostProcessCapacity+"]") if(props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[0] == "RuleGroup"){ - rulegroupidentifier ="RG" + rulegroupidentifier ="postRG" } - if(props.runtimeprops.PostProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash){ - name = props.config.General.Prefix.toUpperCase() + "-G" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash + if(props.runtimeprops.PostProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-post-" + props.config.General.Stage + "-" +props.config.General.DeployHash){ + name = props.config.General.Prefix.toUpperCase() + "-postG" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash } console.log(" 💬 New Name: "+ name) console.log(" 📇 New Identifier: "+ rulegroupidentifier) @@ -611,33 +636,33 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { while (count < rulesets.length){ if(typeof props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] !== "undefined"){ if(rulegroupcapacities[count] == props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count]){ - rulegroupidentifier = "R"+count.toString() - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + rulegroupidentifier = "postR"+count.toString() + name = props.config.WebAcl.Name + "-post-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash } else{ console.log("\n⭕️ Deploy new RuleGroup because the Capacity has changed for " +props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[count] + " !") console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] + "]\n 🟩 New Capacity: [" + rulegroupcapacities[count] +"]") if(typeof props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] !== "undefined"){ if(props.runtimeprops.PostProcessDeployedRuleGroupNames[count] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString()+ "-" +props.config.General.DeployHash){ - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-R" + count.toString() + "-" +props.config.General.DeployHash + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-postR-" + count.toString() + "-" +props.config.General.DeployHash } else{ - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-post-" + count.toString() + "-" +props.config.General.DeployHash } console.log(" 💬 New Name: "+ name) } if(typeof props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[count] !== undefined){ if(props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[count] == "R"+count.toString()){ - rulegroupidentifier = "preG"+count.toString() + rulegroupidentifier = "postG"+count.toString() } else{ - rulegroupidentifier = "preR"+count.toString() + rulegroupidentifier = "postR"+count.toString() } console.log(" 📇 New Identifier: "+ rulegroupidentifier + "\n") } } }else{ - rulegroupidentifier = "preR"+count.toString() + rulegroupidentifier = "postR"+count.toString() name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash } const CfnRuleProperties = [] @@ -652,7 +677,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { else{ rulename = rulegroupcounter.toString() } - let CfnRuleProperty: wafv2.CfnRuleGroup.RuleProperty + let CfnRuleProperty if("Captcha" in props.config.WebAcl.PostProcess.CustomRules[statementindex].Action){ CfnRuleProperty = { name: rulename, @@ -665,7 +690,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { metricName: rulename + "-metric", }, captchaConfig: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].CaptchaConfig), - ruleLabels: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels), + ruleLabels: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels) } } else{ @@ -679,11 +704,19 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { cloudWatchMetricsEnabled: props.config.WebAcl.PostProcess.CustomRules[statementindex].VisibilityConfig.CloudWatchMetricsEnabled, metricName: rulename + "-metric", }, - ruleLabels: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels), + ruleLabels: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels) } } - - CfnRuleProperties.push(CfnRuleProperty) + let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty + if(CfnRuleProperty.ruleLabels = []){ + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty + CfnRuleProperti = CfnRulePropertii + } + else{ + const CfnRulePropertii = CfnRuleProperty + CfnRuleProperti = CfnRulePropertii + } + CfnRuleProperties.push(CfnRuleProperti) rulegroupcounter++ } const rulegroup = new wafv2.CfnRuleGroup(this,rulegroupidentifier, { @@ -736,9 +769,9 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } else{ let mangedrule; + for(mangedrule of props.config.WebAcl.PostProcess.ManagedRuleGroups){ let ExcludeRules; let OverrideAction; - for(mangedrule of props.config.WebAcl.PostProcess.ManagedRuleGroups){ if(mangedrule.ExcludeRules){ ExcludeRules = toCamel(mangedrule.ExcludeRules) OverrideAction = mangedrule.OverrideAction @@ -757,32 +790,32 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} } } - if(props.config.WebAcl.PreProcess.ManagedRuleGroups === undefined){ - console.log("ℹ️ No ManagedRuleGroups defined in PreProcess.") - } - else{ - let mangedrule; - let ExcludeRules; - let OverrideAction; - for(mangedrule of props.config.WebAcl.PreProcess.ManagedRuleGroups){ - if(mangedrule.ExcludeRules){ - ExcludeRules = toCamel(mangedrule.ExcludeRules) - OverrideAction = mangedrule.OverrideAction - } - else{ - ExcludeRules = [] - OverrideAction = { "type": "NONE" } - } - if(mangedrule.Version == ""){ - preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, - "managedRuleGroupName":mangedrule.Name,"version": novalue},"overrideAction": OverrideAction, - "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} - else{ - preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, - "managedRuleGroupName":mangedrule.Name,"version": mangedrule.Version},"overrideAction": OverrideAction, - "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} - } - } + if(props.config.WebAcl.PreProcess.ManagedRuleGroups === undefined){ + console.log("ℹ️ No ManagedRuleGroups defined in PreProcess.") + } + else{ + let mangedrule; + for(mangedrule of props.config.WebAcl.PreProcess.ManagedRuleGroups){ + let PreProcessExcludeRules = []; + let OverrideAction; + if(mangedrule.ExcludeRules){ + PreProcessExcludeRules = toCamel(mangedrule.ExcludeRules) + OverrideAction = mangedrule.OverrideAction + } + else{ + PreProcessExcludeRules = [] + OverrideAction = { "type": "NONE" } + } + if(mangedrule.Version == ""){ + preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": novalue},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": PreProcessExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + else{ + preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": mangedrule.Version},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": PreProcessExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + } + } if(postProcessRuleGroups == []){ const securityservicepolicydata = { "type":"WAFV2", diff --git a/lib/types/config.ts b/lib/types/config.ts index 76d1e7f2..4fc4eef8 100644 --- a/lib/types/config.ts +++ b/lib/types/config.ts @@ -38,4 +38,5 @@ interface RulesArray{ Action: any, VisibilityConfig: any, CaptchaConfig?: any, + RuleLabels?: any } \ No newline at end of file From 2ed24b7f3cf5d99fc3c8cdf492d4895f7aee484e Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 10:17:30 +0100 Subject: [PATCH 04/24] Astjustments --- CHANGELOG.md | 2 +- lib/plattform-wafv2-cdk-automation-stack.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ddd48c1..e397ad6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Released -## 1.1.0 +## 2.0.0 ### Added 1. preProcessRuleGroups and postProcessRuleGroups - you can decide now where the Custom or ManagedRules should be added to. diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index e7d15a3a..cbe0eab1 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -543,9 +543,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { else{ CfnRuleProperti = CfnRuleProperty } - console.log(CfnRuleProperti) rules.push(CfnRuleProperti) - console.log(rules) count +=1 } From 8c5f5b0d705e2e23ab65a6efa571575384eb4625 Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 10:24:52 +0100 Subject: [PATCH 05/24] Fix Outputnaming --- lib/plattform-wafv2-cdk-automation-stack.ts | 44 ++++++++++----------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index cbe0eab1..eed912fc 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -470,22 +470,22 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { props.runtimeprops.PreProcessDeployedRuleGroupNames.splice(lenght) const novalue = null - new cdk.CfnOutput(this, "DeployedRuleGroupNames", { - value: props.runtimeprops.PreProcessDeployedRuleGroupCapacities.toString(), - description: "DeployedRuleGroupNames", - exportName: "DeployedRuleGroupNames"+props.config.General.DeployHash, + new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupNames", { + value: props.runtimeprops.PreProcessDeployedRuleGroupNames.toString(), + description: "PreProcessDeployedRuleGroupNames", + exportName: "PreProcessDeployedRuleGroupNames"+props.config.General.DeployHash, }); - new cdk.CfnOutput(this, "DeployedRuleGroupCapacities", { + new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupCapacities", { value: props.runtimeprops.PreProcessDeployedRuleGroupCapacities.toString(), - description: "DeployedRuleGroupCapacities", - exportName: "DeployedRuleGroupCapacities"+props.config.General.DeployHash, + description: "PreProcessDeployedRuleGroupCapacities", + exportName: "PreProcessDeployedRuleGroupCapacities"+props.config.General.DeployHash, }); - new cdk.CfnOutput(this, "DeployedRuleGroupIdentifier", { + new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupIdentifier", { value: props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.toString(), - description: "DeployedRuleGroupIdentifier", - exportName: "DeployedRuleGroupIdentifier"+props.config.General.DeployHash, + description: "PreProcessDeployedRuleGroupIdentifier", + exportName: "PreProcessDeployedRuleGroupIdentifier"+props.config.General.DeployHash, }); } } @@ -742,22 +742,22 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { props.runtimeprops.PostProcessDeployedRuleGroupNames.splice(lenght) const novalue = null - new cdk.CfnOutput(this, "DeployedRuleGroupNames", { - value: props.runtimeprops.PostProcessDeployedRuleGroupCapacities.toString(), - description: "DeployedRuleGroupNames", - exportName: "DeployedRuleGroupNames"+props.config.General.DeployHash, + new cdk.CfnOutput(this, "PostProcessDeployedRuleGroupNames", { + value: props.runtimeprops.PostProcessDeployedRuleGroupNames.toString(), + description: "PostProcessDeployedRuleGroupNames", + exportName: "PostProcessDeployedRuleGroupNames"+props.config.General.DeployHash, }); - new cdk.CfnOutput(this, "DeployedRuleGroupCapacities", { - value: props.runtimeprops.PostProcessDeployedRuleGroupCapacities.toString(), - description: "DeployedRuleGroupCapacities", - exportName: "DeployedRuleGroupCapacities"+props.config.General.DeployHash, + new cdk.CfnOutput(this, "PostProcessDeployedRuleGroupIdentifier", { + value: props.runtimeprops.PostProcessDeployedRuleGroupIdentifier.toString(), + description: "PostProcessDeployedRuleGroupIdentifier", + exportName: "PostProcessDeployedRuleGroupIdentifier"+props.config.General.DeployHash, }); - new cdk.CfnOutput(this, "DeployedRuleGroupIdentifier", { - value: props.runtimeprops.PostProcessDeployedRuleGroupIdentifier.toString(), - description: "DeployedRuleGroupIdentifier", - exportName: "DeployedRuleGroupIdentifier"+props.config.General.DeployHash, + new cdk.CfnOutput(this, "PostProcessDeployedRuleGroupCapacities", { + value: props.runtimeprops.PostProcessDeployedRuleGroupCapacities.toString(), + description: "PostProcessDeployedRuleGroupCapacities", + exportName: "PostProcessDeployedRuleGroupCapacities"+props.config.General.DeployHash, }); } } From 7a97fa127721bd760ccfd890e04f414187d7aeaf Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 11:20:56 +0100 Subject: [PATCH 06/24] fixes --- lib/plattform-wafv2-cdk-automation-stack.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index eed912fc..5c848d71 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -9,7 +9,6 @@ import { print } from "util"; import { Config } from "./types/config"; import { Runtimeprops } from "./types/runtimeprops"; - function toCamel(o: any) { var newO: any, origKey: any, newKey: any, value: any if (o instanceof Array) { @@ -147,6 +146,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { let ExcludeRules; let OverrideAction; const preProcessRuleGroups = [] + const postProcessRuleGroups = [] if(props.config.WebAcl.PreProcess.ManagedRuleGroups === undefined){ console.log("\nℹ️ No ManagedRuleGroups defined in PreProcess.") } @@ -170,7 +170,6 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} } } - const postProcessRuleGroups = [] if(props.config.WebAcl.PostProcess.ManagedRuleGroups === undefined){ console.log("ℹ️ No ManagedRuleGroups defined in PostProcess.") } @@ -356,7 +355,6 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { console.log(`🖖 Split Rules into ${rulesets.length.toString()} RuleGroups \n ℹ️ AWS Limitation 100 Capacity per RuleGroup\n`); let count = 0 - const preProcessRuleGroups = [] let rulegroupidentifier = "" let name ="" while (count < rulesets.length){ @@ -401,7 +399,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { rulename = props.config.WebAcl.PreProcess.CustomRules[statementindex].Name + "-" + Temp_Hash } else{ - rulename = rulegroupcounter.toString() + rulename = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-pre-" + rulegroupcounter.toString() + "-" +props.config.General.DeployHash } let CfnRuleProperty if("Captcha" in props.config.WebAcl.PreProcess.CustomRules[statementindex].Action){ @@ -628,7 +626,6 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { console.log(`🖖 Split Rules into ${rulesets.length.toString()} RuleGroups \n ℹ️ AWS Limitation 100 Capacity per RuleGroup\n`); let count = 0 - const postProcessRuleGroups = [] let rulegroupidentifier = "" let name ="" while (count < rulesets.length){ @@ -641,7 +638,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { console.log("\n⭕️ Deploy new RuleGroup because the Capacity has changed for " +props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[count] + " !") console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] + "]\n 🟩 New Capacity: [" + rulegroupcapacities[count] +"]") if(typeof props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] !== "undefined"){ - if(props.runtimeprops.PostProcessDeployedRuleGroupNames[count] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString()+ "-" +props.config.General.DeployHash){ + if(props.runtimeprops.PostProcessDeployedRuleGroupNames[count] == props.config.WebAcl.Name + "-post-" + props.config.General.Stage + "-" + count.toString()+ "-" +props.config.General.DeployHash){ name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-postR-" + count.toString() + "-" +props.config.General.DeployHash } else{ @@ -661,7 +658,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } }else{ rulegroupidentifier = "postR"+count.toString() - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-post-" + count.toString() + "-" +props.config.General.DeployHash } const CfnRuleProperties = [] let rulegroupcounter = 0 @@ -670,10 +667,10 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { let rulename = "" if(props.config.WebAcl.PostProcess.CustomRules[statementindex].Name !== undefined){ const Temp_Hash = Date.now().toString(36) - rulename = props.config.WebAcl.PostProcess.CustomRules[statementindex].Name + "-" + Temp_Hash + rulename = props.config.WebAcl.PostProcess.CustomRules[statementindex].Name + "-post-" + Temp_Hash } else{ - rulename = rulegroupcounter.toString() + rulename = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-post-" + rulegroupcounter.toString() + "-" +props.config.General.DeployHash } let CfnRuleProperty if("Captcha" in props.config.WebAcl.PostProcess.CustomRules[statementindex].Action){ From 735ddc577bd9699469527f3bbc7f77c6cd016cc6 Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 13:31:13 +0100 Subject: [PATCH 07/24] improve cli outputs --- bin/plattform-wafv2-cdk-automation.ts | 12 ++++----- lib/plattform-wafv2-cdk-automation-stack.ts | 28 ++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bin/plattform-wafv2-cdk-automation.ts b/bin/plattform-wafv2-cdk-automation.ts index aaa6dec6..bee0a2d0 100644 --- a/bin/plattform-wafv2-cdk-automation.ts +++ b/bin/plattform-wafv2-cdk-automation.ts @@ -318,15 +318,16 @@ if (configFile && fs.existsSync(configFile)) { } let managedrule; let managedrulecapacity = 0; - console.log("\n👓 Get ManagedRule Capacity:\n") + console.log("\n👀 Get ManagedRule Capacity:\n") if(config.WebAcl.PreProcess.ManagedRuleGroups === undefined){ console.log("\n ℹ️ No ManagedRuleGroups defined in PreProcess.") } else{ + console.log(" 🥇 PreProcess: ") for(managedrule of config.WebAcl.PreProcess.ManagedRuleGroups){ const capacity = await GetManagedRuleCapacity(managedrule.Vendor,managedrule.Name,config.WebAcl.Scope,managedrule.Version) managedrule.Capacity = capacity - console.log(" ➕ Capacity for " + managedrule.Name + " is [" + managedrule.Capacity + "]") + console.log(" ➕ Capacity for " + managedrule.Name + " is [" + managedrule.Capacity + "]") managedrulecapacity = managedrulecapacity + capacity } } @@ -334,10 +335,11 @@ if (configFile && fs.existsSync(configFile)) { console.log("\n ℹ️ No ManagedRuleGroups defined in PostProcess.") } else{ + console.log("\n 🥈 PostProcess: ") for(managedrule of config.WebAcl.PostProcess.ManagedRuleGroups){ const capacity = await GetManagedRuleCapacity(managedrule.Vendor,managedrule.Name,config.WebAcl.Scope,managedrule.Version) managedrule.Capacity = capacity - console.log(" ➕ Capacity for " + managedrule.Name + " is [" + managedrule.Capacity + "]") + console.log(" ➕ Capacity for " + managedrule.Name + " is [" + managedrule.Capacity + "]") managedrulecapacity = managedrulecapacity + capacity } } @@ -349,7 +351,7 @@ if (configFile && fs.existsSync(configFile)) { if (total_wcu <= Number(quote_wcu)) { console.log("\n🔎 Capacity Check result: 🟢 \n") console.log(" 💡 Account WAF-WCU Quota: " +Number(quote_wcu).toString()) - console.log(" 🧮 Calculated Custom Rule Capacity is: [" + custom_capacity + "] \n ➕ ManagedRulesCapacity: ["+ managedrulecapacity +"] \n = Total Waf Capacity: " + total_wcu.toString() + "\n") + console.log(" 🧮 Calculated Custom Rule Capacity is: [" + custom_capacity + "] (🥇[" + runtimeprops.PreProcessCapacity + "] + 🥈[" + runtimeprops.PostProcessCapacity + "]) \n ➕ ManagedRulesCapacity: ["+ managedrulecapacity +"] \n = Total Waf Capacity: " + total_wcu.toString() + "\n") } else { console.log("\n🔎 Capacity Check result: 🔴 \n ﹗ Stopping deployment ﹗\n") @@ -367,8 +369,6 @@ if (configFile && fs.existsSync(configFile)) { account: process.env.CDK_DEFAULT_ACCOUNT, }, }); - - console.log("\n🌎 Set CDK Default Region to: " + deploymentregion + " \n📦 Set CDK Default Account to: " + process.env.CDK_DEFAULT_ACCOUNT + "\n") //app.synth() })(); // } diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index 5c848d71..ab290650 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -218,10 +218,10 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { const preProcessRuleGroups = [] const postProcessRuleGroups = [] if(props.config.WebAcl.PreProcess.CustomRules == undefined){ - console.log("\n\nℹ️ No Custom Rules defined in PreProcess.") + console.log("\nℹ️ No Custom Rules defined in PreProcess.") } else{ - console.log("\n\nℹ️ Custom Rules PreProcess: \n") + console.log("\u001b[1m","\n🥇 Custom Rules PreProcess: ","\x1b[0m\n") if (props.runtimeprops.PreProcessCapacity < 100){ const rules = []; let count = 1 @@ -353,7 +353,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { rulegroupcapacities.push(tracker) } - console.log(`🖖 Split Rules into ${rulesets.length.toString()} RuleGroups \n ℹ️ AWS Limitation 100 Capacity per RuleGroup\n`); + console.log(` 🖖 Split Rules into ${rulesets.length.toString()} RuleGroups: \n`); let count = 0 let rulegroupidentifier = "" let name ="" @@ -451,12 +451,12 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { visibilityConfig: { sampledRequestsEnabled: false, cloudWatchMetricsEnabled: false, - metricName: props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash, + metricName: name + "-metric", } }); preProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); - console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + rulegroupcapacities[count].toString() +"]") + console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + rulegroupcapacities[count].toString() +"]") props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count] = rulegroupcapacities[count] props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[count] = rulegroupidentifier props.runtimeprops.PreProcessDeployedRuleGroupNames[count] = name @@ -488,10 +488,10 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } } if(props.config.WebAcl.PostProcess.CustomRules === undefined){ - console.log("\n\nℹ️ No Custom Rules defined in PostProcess.") + console.log("\nℹ️ No Custom Rules defined in PostProcess.") } else{ - console.log("\n\nℹ️ Custom Rules PostProcess: \n") + console.log("\u001b[1m","\n🥈 Custom Rules PostProcess:","\x1b[0m\n") if (props.runtimeprops.PostProcessCapacity < 100){ const rules = []; let count = 1 @@ -502,7 +502,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { rulename = statement.Name + "-post-" + props.config.General.DeployHash } else{ - rulename = props.config.WebAcl.Name + "-post-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + rulename = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-post-" + count.toString() + "-" +props.config.General.DeployHash } let CfnRuleProperty if("Captcha" in statement.Action){ @@ -555,8 +555,8 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { rulegroupidentifier ="postRG" } - if(props.runtimeprops.PostProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-post-" + props.config.General.Stage + "-" +props.config.General.DeployHash){ - name = props.config.General.Prefix.toUpperCase() + "-postG" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash + if(props.runtimeprops.PostProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-post-" +props.config.General.DeployHash){ + name = props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-postG-" +props.config.General.DeployHash } console.log(" 💬 New Name: "+ name) console.log(" 📇 New Identifier: "+ rulegroupidentifier) @@ -570,11 +570,11 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { visibilityConfig: { sampledRequestsEnabled: false, cloudWatchMetricsEnabled: false, - metricName: props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash, + metricName: name + "-metric", } }); postProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); - console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + props.runtimeprops.PostProcessCapacity +"]") + console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + props.runtimeprops.PostProcessCapacity +"]") props.runtimeprops.PostProcessDeployedRuleGroupCapacities.splice(0) props.runtimeprops.PostProcessDeployedRuleGroupIdentifier.splice(0) props.runtimeprops.PostProcessDeployedRuleGroupNames.splice(0) @@ -624,7 +624,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { rulegroupcapacities.push(tracker) } - console.log(`🖖 Split Rules into ${rulesets.length.toString()} RuleGroups \n ℹ️ AWS Limitation 100 Capacity per RuleGroup\n`); + console.log(` 🖖 Split Rules into ${rulesets.length.toString()} RuleGroups:\n`); let count = 0 let rulegroupidentifier = "" let name ="" @@ -727,7 +727,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { }); postProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); - console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + rulegroupcapacities[count].toString() +"]") + console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + rulegroupcapacities[count].toString() +"]") props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] = rulegroupcapacities[count] props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[count] = rulegroupidentifier props.runtimeprops.PostProcessDeployedRuleGroupNames[count] = name From 3488e044e57674118a511bdae5a0cdfe104684d7 Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 14:18:09 +0100 Subject: [PATCH 08/24] add version to cli output --- bin/plattform-wafv2-cdk-automation.ts | 7 +++++-- package.json | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bin/plattform-wafv2-cdk-automation.ts b/bin/plattform-wafv2-cdk-automation.ts index bee0a2d0..56b04c6b 100644 --- a/bin/plattform-wafv2-cdk-automation.ts +++ b/bin/plattform-wafv2-cdk-automation.ts @@ -13,7 +13,8 @@ import * as lodash from "lodash"; import { validate } from "../lib/tools/config-validator"; import {Config} from "../lib/types/config"; import { Runtimeprops } from "../lib/types/runtimeprops"; - +import * as awsfirewallfactoryinfo from "../package.json"; +const afwfver = awsfirewallfactoryinfo.version const runtimeprops: Runtimeprops = {PreProcessCapacity: 0, PostProcessCapacity: 0, PreProcessDeployedRuleGroupCapacities: [], PreProcessRuleCapacities: [], PreProcessDeployedRuleGroupNames: [], PreProcessDeployedRuleGroupIdentifier: [], PostProcessDeployedRuleGroupCapacities: [], PostProcessRuleCapacities: [], PostProcessDeployedRuleGroupNames: [], PostProcessDeployedRuleGroupIdentifier: [] @@ -222,7 +223,8 @@ if (configFile && fs.existsSync(configFile)) { ╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ `); console.log("\x1b[36m","\n by globaldatanet","\x1b[0m"); - console.log("\n👤 AWS Profile used: ","\x1b[33m","\n " + process.env.AWSUME_PROFILE,"\x1b[0m"); + console.log("\n🏷 Version: ","\x1b[4m",afwfver,"\x1b[0m") + console.log("👤 AWS Profile used: ","\x1b[33m","\n " + process.env.AWSUME_PROFILE,"\x1b[0m"); console.log("🌎 CDK deployment region:","\x1b[33m","\n "+deploymentregion,"\x1b[0m \n") if(config.General.DeployHash == ""){ Temp_Hash = Date.now().toString(36) @@ -381,6 +383,7 @@ if (configFile && fs.existsSync(configFile)) { ██║ ██║╚███╔███╔╝███████║ ██║ ██║██║ ██║███████╗╚███╔███╔╝██║ ██║███████╗███████╗ ██║ ██║ ██║╚██████╗ ██║ ╚██████╔╝██║ ██║ ██║ ╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ `); + console.log("\n🏷 Version: ","\x1b[4m",afwfver,"\x1b[0m") console.log("\n 🧪 Validation of your ConfigFile: \n 📂 " + configFile + "\n\n") console.error("\u001B[31m","🚨 Invalid Configuration File 🚨 \n\n","\x1b[0m" + JSON.stringify(validate.errors, null, 2)+ "\n\n"); process.exitCode = 1; diff --git a/package.json b/package.json index d3b280af..2b95ef83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plattform-wafv2-cdk-automation", - "version": "0.1.0", + "version": "2.0.0", "bin": { "plattform-wafv2-cdk-automation": "bin/plattform-wafv2-cdk-automation.js" }, From 0eb964baa6b87edf99092da19b1acdc2db120f7a Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 14:59:33 +0100 Subject: [PATCH 09/24] Adjust example waf --- values/example-waf.json | 58 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/values/example-waf.json b/values/example-waf.json index 593fec36..64aec096 100644 --- a/values/example-waf.json +++ b/values/example-waf.json @@ -14,6 +14,28 @@ "Name": "DAVID", "Scope": "REGIONAL", "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "PreProcess": { + "ManagedRuleGroups": [{ + "Vendor": "AWS", + "Name": "AWSManagedRulesCommonRuleSet", + "ExcludeRules": [{ + "Name": "CrossSiteScripting_BODY" + }, + { + "Name": "GenericRFI_QUERYARGUMENTS" + }, + { + "Name": "NoUserAgent_HEADER" + } + ], + "Version": "", + "OverrideAction": { + "type": "COUNT" + }, + "Capacity": 700 + } + ] + }, "PostProcess": { "CustomRules": [{ "Statement": { @@ -97,35 +119,13 @@ "CloudWatchMetricsEnabled": true } } - ] - }, - "PreProcess": { - "ManagedRuleGroups": [{ - "Vendor": "AWS", - "Name": "AWSManagedRulesCommonRuleSet", - "ExcludeRules": [{ - "Name": "CrossSiteScripting_BODY" - }, - { - "Name": "GenericRFI_QUERYARGUMENTS" - }, - { - "Name": "NoUserAgent_HEADER" - } - ], - "Version": "", - "OverrideAction": { - "type": "COUNT" - }, - "Capacity": 700 - }, - { - "Vendor": "AWS", - "Name": "AWSManagedRulesAmazonIpReputationList", - "Version": "", - "Capacity": 25 - } - ] + ], + "ManagedRuleGroups":[{ + "Vendor": "AWS", + "Name": "AWSManagedRulesAmazonIpReputationList", + "Version": "", + "Capacity": 25 + }] } } } \ No newline at end of file From d7e40acef7d016077813bc4283545803afb55fca Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 17:12:09 +0100 Subject: [PATCH 10/24] fix ruleLabelsBug --- lib/plattform-wafv2-cdk-automation-stack.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index ab290650..141267d0 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -263,7 +263,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { ruleLabels: toCamel(statement.RuleLabels) };} let CfnRuleProperties: wafv2.CfnRuleGroup.RuleProperty - if(CfnRuleProperty.ruleLabels = []){ + if(CfnRuleProperty.ruleLabels == []){ const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty CfnRuleProperties = CfnRulePropertii } @@ -433,7 +433,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(CfnRuleProperty.ruleLabels = []){ + if(CfnRuleProperty.ruleLabels == []){ const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty CfnRuleProperti = CfnRulePropertii } @@ -534,7 +534,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { ruleLabels: toCamel(statement.RuleLabels) };} let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(CfnRuleProperty.ruleLabels = []){ + if(CfnRuleProperty.ruleLabels == []){ const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty CfnRuleProperti = CfnRulePropertii } @@ -542,6 +542,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { CfnRuleProperti = CfnRuleProperty } rules.push(CfnRuleProperti) + console.log(rules) count +=1 } @@ -703,7 +704,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } } let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(CfnRuleProperty.ruleLabels = []){ + if(CfnRuleProperty.ruleLabels == []){ const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty CfnRuleProperti = CfnRulePropertii } From 7ecaae13ee33856904cc39149889f29932d74831 Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 19:40:06 +0100 Subject: [PATCH 11/24] adjustments --- README.md | 20 ++++++++++---------- bin/plattform-wafv2-cdk-automation.ts | 4 ++-- lib/plattform-wafv2-cdk-automation-stack.ts | 1 - 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 988215e2..bb4a03f7 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ +# AWS FIREWALL FACTORY + [![License: Apache2](https://img.shields.io/badge/license-Apache%202-lightgrey.svg)](http://www.apache.org/licenses/) [![cdk](https://img.shields.io/badge/aws_cdk-v2-orange.svg)](https://docs.aws.amazon.com/cdk/v2/guide/home.html) [![latest](https://img.shields.io/badge/latest-release-yellow.svg)](https://github.com/globaldatanet/aws-firewall-factory/releases) +[![gdn](https://img.shields.io/badge/opensource-@globaldatanet-%2300ecbd)](https://globaldatanet.com/opensource) [![dakn](https://img.shields.io/badge/by-dakn-%23ae0009.svg)](https://github.com/daknhh) -[![gdn](https://img.shields.io/badge/by-globaldatanet-%2300ecbd)](https://globaldatanet.com) [![dakn](https://img.shields.io/badge/by-dakn-%23ae0009.svg)](https://github.com/daknhh) - - - - -# Web Application Firewalls at Scale - -AWS Web Application Firewalls (WAFs) protect web applications and APIs from typical attacks from the Internet that can compromise security and availability, and put undue strain on servers and resources. The AWS WAF provides prebuilt security rules that help control bot traffic and block attack patterns. However, with its help, you can also create your own rules based on your specific requirements. In simple scenarios and for smaller applications, this is very easy to implement on an individual basis. However, in larger environments with tens or even hundreds of applications, it is advisable to aim for central governance and automation. This simple solution helps you deploy, update, and stage your Web Application Firewalls while managing them centrally via AWS Firewall Manager. +AWS Web Application Firewalls (WAFs) protect web applications and APIs from typical attacks from the Internet that can compromise security and availability, and put undue strain on servers and resources. The AWS WAF provides prebuilt security rules that help control bot traffic and block attack patterns. However, with its help, you can also create your own rules based on your specific requirements. In simple scenarios and for smaller applications, this is very easy to implement on an individual basis. However, in larger environments with tens or even hundreds of applications, it is advisable to aim for central governance and automation. This simple solution helps you deploy, update, and stage your Web Application Firewalls while managing them centrally via AWS Firewall Manager |Releases |Author | --- | --- | | [Changelog](CHANGELOG.md) - [Features](#Features)| David Krohn
[Linkedin](https://www.linkedin.com/in/daknhh/) - [Blog](https://globaldatanet.com/our-team/david-krohn)| +### Media +If you want to learn something more about the AWS Firewall Factory feel free to look at the following media resources. +- [📺 Webinar: Web Application Firewalls at Scale](https://globaldatanet.com/webinars/aws-security-with-security-in-the-cloud) +- [🎙 Podcast coming soon](https://github.com/richarvey/aws-community-radio/issues/3) ## Architecture ![Architecture](./static/AWSFIREWALLMANAGER.png "Architecture") @@ -52,13 +52,13 @@ AWS Web Application Firewalls (WAFs) protect web applications and APIs from typi 16. Validation of your ConfigFile using Schema validation - if you miss an required parameter in your config file the deployment will stop automatically and show you the missing path. -## Coming soon: +### Coming soon: 1. Deployment via Teamcity -# Deployment via Taskfile +## Deployment via Taskfile 0. Create new json file for you WAF and configure Rules in the JSON (see [example.json](values/example-waf.json) to see structure) 1. Set `PROCESS_PARAMETERS` in `Taskfile.yml` for new json file diff --git a/bin/plattform-wafv2-cdk-automation.ts b/bin/plattform-wafv2-cdk-automation.ts index 56b04c6b..011d507e 100644 --- a/bin/plattform-wafv2-cdk-automation.ts +++ b/bin/plattform-wafv2-cdk-automation.ts @@ -257,7 +257,7 @@ if (configFile && fs.existsSync(configFile)) { let count = 0 let pre_calculate_capacity_sum = 0 if(config.WebAcl.PreProcess.CustomRules === undefined){ - console.log("\n ℹ️ Skip Rule Capacity Calculation for PreProcess Custom Rules.")} + console.log("\n ⏭ Skip Rule Capacity Calculation for PreProcess Custom Rules.")} else{ while (count < config.WebAcl.PreProcess.CustomRules.length) { if("Captcha" in config.WebAcl.PreProcess.CustomRules[count].Action){ @@ -289,7 +289,7 @@ if (configFile && fs.existsSync(configFile)) { count = 0 let post_calculate_capacity_sum = 0 if(config.WebAcl.PostProcess.CustomRules === undefined){ - console.log("\n ℹ️ Skip Rule Capacity Calculation for PostProcess Custom Rules.") + console.log("\n ⏭ Skip Rule Capacity Calculation for PostProcess Custom Rules.") } else{ while (count < config.WebAcl.PostProcess.CustomRules.length) { diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index 141267d0..53b02ad0 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -542,7 +542,6 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { CfnRuleProperti = CfnRuleProperty } rules.push(CfnRuleProperti) - console.log(rules) count +=1 } From 52bbf3d49396e5bd973da55085a55d43d8a0264b Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 19:45:25 +0100 Subject: [PATCH 12/24] readme adjustments --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb4a03f7..475cee03 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,30 @@ AWS Web Application Firewalls (WAFs) protect web applications and APIs from typical attacks from the Internet that can compromise security and availability, and put undue strain on servers and resources. The AWS WAF provides prebuilt security rules that help control bot traffic and block attack patterns. However, with its help, you can also create your own rules based on your specific requirements. In simple scenarios and for smaller applications, this is very easy to implement on an individual basis. However, in larger environments with tens or even hundreds of applications, it is advisable to aim for central governance and automation. This simple solution helps you deploy, update, and stage your Web Application Firewalls while managing them centrally via AWS Firewall Manager +
+
+
+ +- [AWS FIREWALL FACTORY](#aws-firewall-factory) + + [Media](#media) + * [Architecture](#architecture) + + [Prerequisites](#prerequisites-) + * [Features](#features) + + [Coming soon](#coming-soon-) + * [Deployment via Taskfile](#deployment-via-taskfile) + + [👏 Supporters](#---supporters) + +
+ +
|Releases |Author | --- | --- | | [Changelog](CHANGELOG.md) - [Features](#Features)| David Krohn
[Linkedin](https://www.linkedin.com/in/daknhh/) - [Blog](https://globaldatanet.com/our-team/david-krohn)| +
+





+
+ ### Media If you want to learn something more about the AWS Firewall Factory feel free to look at the following media resources. @@ -22,7 +42,7 @@ If you want to learn something more about the AWS Firewall Factory feel free to ![Architecture](./static/AWSFIREWALLMANAGER.png "Architecture") -### Prerequisites: +### Prerequisites 1. An central S3 Bucket with **write** permission for security account needs to be in place. ## Features From 5ee9ce31ddca8b325aa8ae879a2b53399dd54c63 Mon Sep 17 00:00:00 2001 From: daknhh Date: Mon, 14 Feb 2022 19:46:22 +0100 Subject: [PATCH 13/24] readme adjustments --- README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index 475cee03..1e61e29e 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,7 @@ AWS Web Application Firewalls (WAFs) protect web applications and APIs from typical attacks from the Internet that can compromise security and availability, and put undue strain on servers and resources. The AWS WAF provides prebuilt security rules that help control bot traffic and block attack patterns. However, with its help, you can also create your own rules based on your specific requirements. In simple scenarios and for smaller applications, this is very easy to implement on an individual basis. However, in larger environments with tens or even hundreds of applications, it is advisable to aim for central governance and automation. This simple solution helps you deploy, update, and stage your Web Application Firewalls while managing them centrally via AWS Firewall Manager -
-
+
- [AWS FIREWALL FACTORY](#aws-firewall-factory) @@ -21,17 +20,11 @@ AWS Web Application Firewalls (WAFs) protect web applications and APIs from typi * [Deployment via Taskfile](#deployment-via-taskfile) + [👏 Supporters](#---supporters) -
-
|Releases |Author | --- | --- | | [Changelog](CHANGELOG.md) - [Features](#Features)| David Krohn
[Linkedin](https://www.linkedin.com/in/daknhh/) - [Blog](https://globaldatanet.com/our-team/david-krohn)| -
-





-
- ### Media If you want to learn something more about the AWS Firewall Factory feel free to look at the following media resources. From 91aac480633af3f7c92ef51d96826340b18b4447 Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 15 Feb 2022 08:34:59 +0100 Subject: [PATCH 14/24] fix toc --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1e61e29e..87238629 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ AWS Web Application Firewalls (WAFs) protect web applications and APIs from typi * [Features](#features) + [Coming soon](#coming-soon-) * [Deployment via Taskfile](#deployment-via-taskfile) - + [👏 Supporters](#---supporters) + * [👏 Supporters](#---supporters) @@ -42,7 +42,7 @@ If you want to learn something more about the AWS Firewall Factory feel free to 1. Automated Capactiy Calculation via [API - CheckCapacity](https://docs.aws.amazon.com/waf/latest/APIReference/API_CheckCapacity.html) 2. Algorithm to split Rules into RuleGroups -3. Automated Update of RuleGroup if Capacity Changed +3. Automated Update of RuleGroup if Capacity Changed 4. Add [ManagedRuleGroups](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html) via configuration file 5. Automated Generation of draw.io [diagram](https://app.diagrams.net/) for each WAF 6. Checking of the softlimit quota for WCU set in the AWS Account (Stop deployment if Caluclated WCU is above the quota) From 389dd1495f25dc1feb50cb83b168dbbc6b4389e3 Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 15 Feb 2022 09:13:16 +0100 Subject: [PATCH 15/24] adjust readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 87238629..88e59e0e 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,17 @@ If you want to learn something more about the AWS Firewall Factory feel free to | CDK_DIFF | true (generating a cdk before invoking cdk deploy)
false (Skipping cdk diff) | 16. Validation of your ConfigFile using Schema validation - if you miss an required parameter in your config file the deployment will stop automatically and show you the missing path. +17. preProcessRuleGroups and postProcessRuleGroups - you can decide now where the Custom or ManagedRules should be added to. +18. RuleLabels - A label is a string made up of a prefix, optional namespaces, and a name. The components of a label are delimited with a colon. Labels have the following requirements and characteristics: + + - Labels are case-sensitive. + + - Each label namespace or label name can have up to 128 characters. + + - You can specify up to five namespaces in a label. + + - Components of a label are separated by colon (:). ### Coming soon: From 0a8fae8b8035cf359991aec32f7d279f9098d602 Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 15 Feb 2022 09:24:53 +0100 Subject: [PATCH 16/24] adjust readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 88e59e0e..55c4389f 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,9 @@ If you want to learn something more about the AWS Firewall Factory feel free to | CDK_DIFF | true (generating a cdk before invoking cdk deploy)
false (Skipping cdk diff) | 16. Validation of your ConfigFile using Schema validation - if you miss an required parameter in your config file the deployment will stop automatically and show you the missing path. -17. preProcessRuleGroups and postProcessRuleGroups - you can decide now where the Custom or ManagedRules should be added to. +17. PreProcess-and PostProcessRuleGroups - you can decide now where the Custom or ManagedRules should be added to. + +- New Structure see [example json](./values/example-waf.json). 18. RuleLabels - A label is a string made up of a prefix, optional namespaces, and a name. The components of a label are delimited with a colon. Labels have the following requirements and characteristics: From 49f6e9b4dd8d32fdff2c57cc760d9452e0b9064e Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 15 Feb 2022 16:58:30 +0100 Subject: [PATCH 17/24] adjust readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 55c4389f..fb9b20ed 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ If you want to learn something more about the AWS Firewall Factory feel free to 7. Easy configuration of WAF Rules trough json file. 8. Deployment Hash to deploy same WAF more than one time for testing and/or blue/green deployments. 9. Stopping deployment if soft limit will be exceeded: **Firewall Manager policies per organization per Region (L-0B28E140)** - **Maximum number of web ACL capacity units in a web ACL in WAF for regional (L-D9F31E8A)** -10. NEW **RegexMatchStatement** and **IPSetReferenceStatement** is working now 🚀 -11. NEW You can now name your Rules. If you define a Name in your RulesArray the Name + a Base36 Timestamp will be used for creation of your Rule - otherwise a name will be generated. This will help you to query your logs in Athena. The same Rulename also apply to the metric just with adding "-metric" to the name. -12. New Support for Captcha - You can now add Captcha as Action to your WAFs. This help you to block unwanted bot traffic by requiring users to successfully complete challenges before their web request are allowed to reach AWS WAF protected resources. AWS WAF Captcha is available in the US East (N. Virginia), US West (Oregon), Europe (Frankfurt), South America (Sao Paulo), and Asia Pacific (Singapore) AWS Regions and supports Application Load Balancer, Amazon API Gateway, and AWS AppSync resources. +10. **RegexMatchStatement** and **IPSetReferenceStatement** is working now 🚀 +11. You can name your Rules. If you define a Name in your RulesArray the Name + a Base36 Timestamp will be used for creation of your Rule - otherwise a name will be generated. This will help you to query your logs in Athena. The same Rulename also apply to the metric just with adding "-metric" to the name. +12. Support for Captcha - You can add Captcha as Action to your WAFs. This help you to block unwanted bot traffic by requiring users to successfully complete challenges before their web request are allowed to reach AWS WAF protected resources. AWS WAF Captcha is available in the US East (N. Virginia), US West (Oregon), Europe (Frankfurt), South America (Sao Paulo), and Asia Pacific (Singapore) AWS Regions and supports Application Load Balancer, Amazon API Gateway, and AWS AppSync resources. 13. Added S3LoggingBucketName to json. You need to specify the S3 Bucket where the Logs should be placed in now. We also added a Prefix for the logs to be aws conform (Prefix: AWSLogs/AWS_ACCOUNTID/FirewallManager/AWS_REGION/). 14. Added Testing your WAF with [GoTestWAF](https://github.com/wallarm/gotestwaf). To be able to check your waf we introduced the **SecuredDomain** Parameter in the json which should be your Domain which will be checked using the WAF tool. 15. Introduced three new Parameters in the taskfile (**WAF_TEST**,**CREATE_DIAGRAM** and **CDK_DIFF**). From 189b862974b7c162c080455fa9f877e5e3bb0e93 Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 15 Feb 2022 17:08:46 +0100 Subject: [PATCH 18/24] adjust readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb9b20ed..026a6c9f 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,14 @@ If you want to learn something more about the AWS Firewall Factory feel free to 3. Automated Update of RuleGroup if Capacity Changed 4. Add [ManagedRuleGroups](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html) via configuration file 5. Automated Generation of draw.io [diagram](https://app.diagrams.net/) for each WAF -6. Checking of the softlimit quota for WCU set in the AWS Account (Stop deployment if Caluclated WCU is above the quota) +6. Checking of the softlimit quota for [WCU](https://docs.aws.amazon.com/waf/latest/developerguide/how-aws-waf-works.html) set in the AWS Account (Stop deployment if calculated WCU is above the quota) 7. Easy configuration of WAF Rules trough json file. 8. Deployment Hash to deploy same WAF more than one time for testing and/or blue/green deployments. 9. Stopping deployment if soft limit will be exceeded: **Firewall Manager policies per organization per Region (L-0B28E140)** - **Maximum number of web ACL capacity units in a web ACL in WAF for regional (L-D9F31E8A)** 10. **RegexMatchStatement** and **IPSetReferenceStatement** is working now 🚀 11. You can name your Rules. If you define a Name in your RulesArray the Name + a Base36 Timestamp will be used for creation of your Rule - otherwise a name will be generated. This will help you to query your logs in Athena. The same Rulename also apply to the metric just with adding "-metric" to the name. 12. Support for Captcha - You can add Captcha as Action to your WAFs. This help you to block unwanted bot traffic by requiring users to successfully complete challenges before their web request are allowed to reach AWS WAF protected resources. AWS WAF Captcha is available in the US East (N. Virginia), US West (Oregon), Europe (Frankfurt), South America (Sao Paulo), and Asia Pacific (Singapore) AWS Regions and supports Application Load Balancer, Amazon API Gateway, and AWS AppSync resources. -13. Added S3LoggingBucketName to json. You need to specify the S3 Bucket where the Logs should be placed in now. We also added a Prefix for the logs to be aws conform (Prefix: AWSLogs/AWS_ACCOUNTID/FirewallManager/AWS_REGION/). +13. Added S3LoggingBucketName to json. You need to specify the S3 Bucket where the Logs should be placed in now. We also added a Prefix for the logs to be aws conform (Prefix: AWSLogs/*AWS_ACCOUNTID*/FirewallManager/*AWS_REGION*/). 14. Added Testing your WAF with [GoTestWAF](https://github.com/wallarm/gotestwaf). To be able to check your waf we introduced the **SecuredDomain** Parameter in the json which should be your Domain which will be checked using the WAF tool. 15. Introduced three new Parameters in the taskfile (**WAF_TEST**,**CREATE_DIAGRAM** and **CDK_DIFF**). From a8ec353212ae5a48db9b247e5dd2e809e0ea2633 Mon Sep 17 00:00:00 2001 From: daknhh Date: Tue, 15 Feb 2022 17:13:54 +0100 Subject: [PATCH 19/24] improve readme --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 026a6c9f..64c88251 100644 --- a/README.md +++ b/README.md @@ -54,14 +54,17 @@ If you want to learn something more about the AWS Firewall Factory feel free to 12. Support for Captcha - You can add Captcha as Action to your WAFs. This help you to block unwanted bot traffic by requiring users to successfully complete challenges before their web request are allowed to reach AWS WAF protected resources. AWS WAF Captcha is available in the US East (N. Virginia), US West (Oregon), Europe (Frankfurt), South America (Sao Paulo), and Asia Pacific (Singapore) AWS Regions and supports Application Load Balancer, Amazon API Gateway, and AWS AppSync resources. 13. Added S3LoggingBucketName to json. You need to specify the S3 Bucket where the Logs should be placed in now. We also added a Prefix for the logs to be aws conform (Prefix: AWSLogs/*AWS_ACCOUNTID*/FirewallManager/*AWS_REGION*/). 14. Added Testing your WAF with [GoTestWAF](https://github.com/wallarm/gotestwaf). To be able to check your waf we introduced the **SecuredDomain** Parameter in the json which should be your Domain which will be checked using the WAF tool. -15. Introduced three new Parameters in the taskfile (**WAF_TEST**,**CREATE_DIAGRAM** and **CDK_DIFF**). +15. TaskFileParameters: | Parameter | Value | |----------|:-------------:| +| PROCESS_PARAMETERS | path to values file eg. values/example-waf.json | +| SKIP_QUOTA_CHECK |true (Stop deployment if calculated WCU is above the quota)
false (Skipping WCU Check) | | WAF_TEST | true (testing your waf with GoTestWAF)
false (Skipping WAF testing) | | CREATE_DIAGRAM | true (generating a diagram using draw.io)
false (Skipping diagram generation) | | CDK_DIFF | true (generating a cdk before invoking cdk deploy)
false (Skipping cdk diff) | + 16. Validation of your ConfigFile using Schema validation - if you miss an required parameter in your config file the deployment will stop automatically and show you the missing path. 17. PreProcess-and PostProcessRuleGroups - you can decide now where the Custom or ManagedRules should be added to. From 95463a8fcb86406017e9539ea853f170e5c71a74 Mon Sep 17 00:00:00 2001 From: daknhh Date: Wed, 16 Feb 2022 10:10:03 +0100 Subject: [PATCH 20/24] improve splitting --- lib/plattform-wafv2-cdk-automation-stack.ts | 24 ++++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index 53b02ad0..12e93265 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -222,7 +222,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } else{ console.log("\u001b[1m","\n🥇 Custom Rules PreProcess: ","\x1b[0m\n") - if (props.runtimeprops.PreProcessCapacity < 100){ + if (props.runtimeprops.PreProcessCapacity < 1000){ const rules = []; let count = 1 for(const statement of props.config.WebAcl.PreProcess.CustomRules){ @@ -263,7 +263,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { ruleLabels: toCamel(statement.RuleLabels) };} let CfnRuleProperties: wafv2.CfnRuleGroup.RuleProperty - if(CfnRuleProperty.ruleLabels == []){ + if(!!CfnRuleProperty.ruleLabels){ const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty CfnRuleProperties = CfnRulePropertii } @@ -333,7 +333,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } else{ - const threshold = 100 + const threshold = 1000 const rulesets: any[] = [] const indexes: number[] = [] const rulegroupcapacities = [] @@ -432,8 +432,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } } let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - - if(CfnRuleProperty.ruleLabels == []){ + if(!!CfnRuleProperty.ruleLabels){ const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty CfnRuleProperti = CfnRulePropertii } @@ -492,7 +491,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } else{ console.log("\u001b[1m","\n🥈 Custom Rules PostProcess:","\x1b[0m\n") - if (props.runtimeprops.PostProcessCapacity < 100){ + if (props.runtimeprops.PostProcessCapacity < 1000){ const rules = []; let count = 1 @@ -534,7 +533,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { ruleLabels: toCamel(statement.RuleLabels) };} let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(CfnRuleProperty.ruleLabels == []){ + if(!!CfnRuleProperty.ruleLabels){ const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty CfnRuleProperti = CfnRulePropertii } @@ -551,12 +550,11 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { if(props.runtimeprops.PostProcessDeployedRuleGroupCapacities[0] != props.runtimeprops.PostProcessCapacity){ console.log("⭕️ Deploy new RuleGroup because the Capacity has changed!") console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PostProcessDeployedRuleGroupCapacities[0] + "]\n 🟩 New Capacity: [" + props.runtimeprops.PostProcessCapacity+"]") - if(props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[0] == "RuleGroup"){ + if(props.runtimeprops.PostProcessDeployedRuleGroupIdentifier[0] === "PostRuleGroup"){ rulegroupidentifier ="postRG" } - - if(props.runtimeprops.PostProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-post-" +props.config.General.DeployHash){ - name = props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-postG-" +props.config.General.DeployHash + if(props.runtimeprops.PostProcessDeployedRuleGroupNames[0] === props.config.WebAcl.Name + "-post-" + props.config.General.Stage + "-" +props.config.General.DeployHash){ + name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-postG-" +props.config.General.DeployHash } console.log(" 💬 New Name: "+ name) console.log(" 📇 New Identifier: "+ rulegroupidentifier) @@ -604,7 +602,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } else{ - const threshold = 100 + const threshold = 1000 const rulesets: any[] = [] const indexes: number[] = [] const rulegroupcapacities = [] @@ -703,7 +701,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } } let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(CfnRuleProperty.ruleLabels == []){ + if(!!CfnRuleProperty.ruleLabels){ const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty CfnRuleProperti = CfnRulePropertii } From 1e427288e4ab7b090368a814322f28e06226f296 Mon Sep 17 00:00:00 2001 From: daknhh Date: Wed, 16 Feb 2022 13:03:06 +0100 Subject: [PATCH 21/24] Adjuste Changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e397ad6c..65778d27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ - Added PreProcess and PostProcess ℹ️ See [example json](./values/example-waf.json). + +2. Optimized RuleGroup Splitting - RuleGroups will now be splitted into Groups with up to 1000 WCU. + ## 1.0.4 ### Added From 3eacdeb6aca8dfe1f53707ca20cee6c94b939119 Mon Sep 17 00:00:00 2001 From: daknhh Date: Thu, 17 Feb 2022 09:41:37 +0100 Subject: [PATCH 22/24] Fix Rulelables --- lib/plattform-wafv2-cdk-automation-stack.ts | 337 ++++++++++---------- 1 file changed, 169 insertions(+), 168 deletions(-) diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index 12e93265..d288ba32 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -223,34 +223,19 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { else{ console.log("\u001b[1m","\n🥇 Custom Rules PreProcess: ","\x1b[0m\n") if (props.runtimeprops.PreProcessCapacity < 1000){ - const rules = []; - let count = 1 - for(const statement of props.config.WebAcl.PreProcess.CustomRules){ - let rulename = "" - if(statement.Name !== undefined){ - rulename = statement.Name + "-pre-" + props.config.General.DeployHash - } - else{ - rulename = props.config.WebAcl.Name + "-pre-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash - } - let CfnRuleProperty - if("Captcha" in statement.Action){ - CfnRuleProperty = { - name: rulename, - priority: count, - action: toCamel(statement.Action), - statement: toCamel(statement.Statement), - visibilityConfig: { - sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, - cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, - metricName: rulename + "-metric", - }, - captchaConfig: toCamel(statement.CaptchaConfig), - ruleLabels: toCamel(statement.RuleLabels) - } - } - else{ - CfnRuleProperty = { + const rules = []; + let count = 1 + for(const statement of props.config.WebAcl.PreProcess.CustomRules){ + let rulename = "" + if(statement.Name !== undefined){ + rulename = statement.Name + "-pre-" + props.config.General.DeployHash + } + else{ + rulename = props.config.WebAcl.Name + "-pre-" + props.config.General.Stage + "-" + count.toString() + "-" +props.config.General.DeployHash + } + let CfnRuleProperty + if("Captcha" in statement.Action){ + CfnRuleProperty = { name: rulename, priority: count, action: toCamel(statement.Action), @@ -260,76 +245,92 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, metricName: rulename + "-metric", }, + captchaConfig: toCamel(statement.CaptchaConfig), ruleLabels: toCamel(statement.RuleLabels) - };} - let CfnRuleProperties: wafv2.CfnRuleGroup.RuleProperty - if(!!CfnRuleProperty.ruleLabels){ - const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty - CfnRuleProperties = CfnRulePropertii - } - else{ - CfnRuleProperties = CfnRuleProperty } - rules.push(CfnRuleProperties) - count +=1 } + else{ + CfnRuleProperty = { + name: rulename, + priority: count, + action: toCamel(statement.Action), + statement: toCamel(statement.Statement), + visibilityConfig: { + sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + ruleLabels: toCamel(statement.RuleLabels) + }; + } + let CfnRuleProperties: wafv2.CfnRuleGroup.RuleProperty + if(statement.RuleLabels){ + CfnRuleProperties = CfnRuleProperty + } + else{ + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty + CfnRuleProperties = CfnRulePropertii + } + rules.push(CfnRuleProperties) + count +=1 + } - let name = props.config.WebAcl.Name + "-pre-" + props.config.General.Stage + "-" +props.config.General.DeployHash - let rulegroupidentifier = "PreRuleGroup" - if(typeof props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] !== "undefined"){ - if(props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] != props.runtimeprops.PreProcessCapacity){ - console.log("⭕️ Deploy new RuleGroup because the Capacity has changed!") - console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] + "]\n 🟩 New Capacity: [" + props.runtimeprops.PreProcessCapacity+"]") - if(props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[0] == "RuleGroup"){ - rulegroupidentifier ="preRG" - } + let name = props.config.WebAcl.Name + "-pre-" + props.config.General.Stage + "-" +props.config.General.DeployHash + let rulegroupidentifier = "PreRuleGroup" + if(typeof props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] !== "undefined"){ + if(props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] != props.runtimeprops.PreProcessCapacity){ + console.log("⭕️ Deploy new RuleGroup because the Capacity has changed!") + console.log("\n 🟥 Old Capacity: ["+ props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] + "]\n 🟩 New Capacity: [" + props.runtimeprops.PreProcessCapacity+"]") + if(props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[0] == "RuleGroup"){ + rulegroupidentifier ="preRG" + } - if(props.runtimeprops.PreProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash){ - name = props.config.General.Prefix.toUpperCase() + "-G" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash - } - console.log(" 💬 New Name: "+ name) - console.log(" 📇 New Identifier: "+ rulegroupidentifier) + if(props.runtimeprops.PreProcessDeployedRuleGroupNames[0] == props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash){ + name = props.config.General.Prefix.toUpperCase() + "-G" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash } + console.log(" 💬 New Name: "+ name) + console.log(" 📇 New Identifier: "+ rulegroupidentifier) } - const rulegroup = new wafv2.CfnRuleGroup(this,rulegroupidentifier, { - capacity: props.runtimeprops.PreProcessCapacity, - scope: props.config.WebAcl.Scope, - rules: rules, - name: name, - visibilityConfig: { - sampledRequestsEnabled: false, - cloudWatchMetricsEnabled: false, - metricName: props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash, - } - }); - preProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); - console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + props.runtimeprops.PreProcessCapacity +"]") - props.runtimeprops.PreProcessDeployedRuleGroupCapacities.splice(0) - props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.splice(0) - props.runtimeprops.PreProcessDeployedRuleGroupNames.splice(0) + } + const rulegroup = new wafv2.CfnRuleGroup(this,rulegroupidentifier, { + capacity: props.runtimeprops.PreProcessCapacity, + scope: props.config.WebAcl.Scope, + rules: rules, + name: name, + visibilityConfig: { + sampledRequestsEnabled: false, + cloudWatchMetricsEnabled: false, + metricName: props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage + "-" +props.config.General.DeployHash, + } + }); + preProcessRuleGroups.push({"ruleGroupType":"RuleGroup","ruleGroupArn":"${"+ rulegroupidentifier +".Arn}","overrideAction":{"type":"NONE"}}); + console.log(" ➡️ Creating " + rulegroupidentifier + " with calculated capacity: [" + props.runtimeprops.PreProcessCapacity +"]") + props.runtimeprops.PreProcessDeployedRuleGroupCapacities.splice(0) + props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.splice(0) + props.runtimeprops.PreProcessDeployedRuleGroupNames.splice(0) - props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[0] = rulegroupidentifier - props.runtimeprops.PreProcessDeployedRuleGroupNames[0] = name - props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] = props.runtimeprops.PreProcessCapacity + props.runtimeprops.PreProcessDeployedRuleGroupIdentifier[0] = rulegroupidentifier + props.runtimeprops.PreProcessDeployedRuleGroupNames[0] = name + props.runtimeprops.PreProcessDeployedRuleGroupCapacities[0] = props.runtimeprops.PreProcessCapacity - new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupNames", { - value: props.runtimeprops.PreProcessDeployedRuleGroupNames.toString(), - description: "PreProcessDeployedRuleGroupNames", - exportName: "PreProcessDeployedRuleGroupNames"+props.config.General.DeployHash, - }); + new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupNames", { + value: props.runtimeprops.PreProcessDeployedRuleGroupNames.toString(), + description: "PreProcessDeployedRuleGroupNames", + exportName: "PreProcessDeployedRuleGroupNames"+props.config.General.DeployHash, + }); - new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupCapacities", { - value: props.runtimeprops.PreProcessDeployedRuleGroupCapacities.toString(), - description: "PreProcessDeployedRuleGroupCapacities", - exportName: "PreProcessDeployedRuleGroupCapacities"+props.config.General.DeployHash, - }); + new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupCapacities", { + value: props.runtimeprops.PreProcessDeployedRuleGroupCapacities.toString(), + description: "PreProcessDeployedRuleGroupCapacities", + exportName: "PreProcessDeployedRuleGroupCapacities"+props.config.General.DeployHash, + }); - new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupIdentifier", { - value: props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.toString(), - description: "PreProcessDeployedRuleGroupIdentifier", - exportName: "PreProcessDeployedRuleGroupIdentifier"+props.config.General.DeployHash, - }); + new cdk.CfnOutput(this, "PreProcessDeployedRuleGroupIdentifier", { + value: props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.toString(), + description: "PreProcessDeployedRuleGroupIdentifier", + exportName: "PreProcessDeployedRuleGroupIdentifier"+props.config.General.DeployHash, + }); } else{ @@ -432,12 +433,12 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } } let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(!!CfnRuleProperty.ruleLabels){ - const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty - CfnRuleProperti = CfnRulePropertii + if(props.config.WebAcl.PreProcess.CustomRules[statementindex].RuleLabels){ + CfnRuleProperti = CfnRuleProperty } else{ - CfnRuleProperti = CfnRuleProperty + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty + CfnRuleProperti = CfnRulePropertii } CfnRuleProperties.push(CfnRuleProperti) rulegroupcounter++ @@ -521,24 +522,24 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } else{ CfnRuleProperty = { - name: rulename, - priority: count, - action: toCamel(statement.Action), - statement: toCamel(statement.Statement), - visibilityConfig: { - sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, - cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, - metricName: rulename + "-metric", - }, - ruleLabels: toCamel(statement.RuleLabels) - };} + name: rulename, + priority: count, + action: toCamel(statement.Action), + statement: toCamel(statement.Statement), + visibilityConfig: { + sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: statement.VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + ruleLabels: toCamel(statement.RuleLabels) + };} let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(!!CfnRuleProperty.ruleLabels){ - const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty - CfnRuleProperti = CfnRulePropertii + if(statement.RuleLabels){ + CfnRuleProperti = CfnRuleProperty } else{ - CfnRuleProperti = CfnRuleProperty + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty + CfnRuleProperti = CfnRulePropertii } rules.push(CfnRuleProperti) count +=1 @@ -701,12 +702,12 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } } let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(!!CfnRuleProperty.ruleLabels){ - const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty + if(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels){ + const CfnRulePropertii = CfnRuleProperty CfnRuleProperti = CfnRulePropertii } else{ - const CfnRulePropertii = CfnRuleProperty + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty CfnRuleProperti = CfnRulePropertii } CfnRuleProperties.push(CfnRuleProperti) @@ -762,53 +763,53 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { } else{ let mangedrule; - for(mangedrule of props.config.WebAcl.PostProcess.ManagedRuleGroups){ - let ExcludeRules; - let OverrideAction; - if(mangedrule.ExcludeRules){ - ExcludeRules = toCamel(mangedrule.ExcludeRules) - OverrideAction = mangedrule.OverrideAction - } - else{ - ExcludeRules = [] - OverrideAction = { "type": "NONE" } + for(mangedrule of props.config.WebAcl.PostProcess.ManagedRuleGroups){ + let ExcludeRules; + let OverrideAction; + if(mangedrule.ExcludeRules){ + ExcludeRules = toCamel(mangedrule.ExcludeRules) + OverrideAction = mangedrule.OverrideAction + } + else{ + ExcludeRules = [] + OverrideAction = { "type": "NONE" } + } + if(mangedrule.Version == ""){ + postProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": novalue},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + else{ + postProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": mangedrule.Version},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} } - if(mangedrule.Version == ""){ - postProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, - "managedRuleGroupName":mangedrule.Name,"version": novalue},"overrideAction": OverrideAction, - "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} - else{ - postProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, - "managedRuleGroupName":mangedrule.Name,"version": mangedrule.Version},"overrideAction": OverrideAction, - "ruleGroupArn": novalue,"excludeRules": ExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} } - } - if(props.config.WebAcl.PreProcess.ManagedRuleGroups === undefined){ - console.log("ℹ️ No ManagedRuleGroups defined in PreProcess.") - } - else{ - let mangedrule; - for(mangedrule of props.config.WebAcl.PreProcess.ManagedRuleGroups){ - let PreProcessExcludeRules = []; - let OverrideAction; - if(mangedrule.ExcludeRules){ - PreProcessExcludeRules = toCamel(mangedrule.ExcludeRules) - OverrideAction = mangedrule.OverrideAction + if(props.config.WebAcl.PreProcess.ManagedRuleGroups === undefined){ + console.log("ℹ️ No ManagedRuleGroups defined in PreProcess.") } else{ - PreProcessExcludeRules = [] - OverrideAction = { "type": "NONE" } + let mangedrule; + for(mangedrule of props.config.WebAcl.PreProcess.ManagedRuleGroups){ + let PreProcessExcludeRules = []; + let OverrideAction; + if(mangedrule.ExcludeRules){ + PreProcessExcludeRules = toCamel(mangedrule.ExcludeRules) + OverrideAction = mangedrule.OverrideAction + } + else{ + PreProcessExcludeRules = [] + OverrideAction = { "type": "NONE" } + } + if(mangedrule.Version == ""){ + preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": novalue},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": PreProcessExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + else{ + preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, + "managedRuleGroupName":mangedrule.Name,"version": mangedrule.Version},"overrideAction": OverrideAction, + "ruleGroupArn": novalue,"excludeRules": PreProcessExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} + } } - if(mangedrule.Version == ""){ - preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, - "managedRuleGroupName":mangedrule.Name,"version": novalue},"overrideAction": OverrideAction, - "ruleGroupArn": novalue,"excludeRules": PreProcessExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} - else{ - preProcessRuleGroups.push({"managedRuleGroupIdentifier": {"vendorName": mangedrule.Vendor, - "managedRuleGroupName":mangedrule.Name,"version": mangedrule.Version},"overrideAction": OverrideAction, - "ruleGroupArn": novalue,"excludeRules": PreProcessExcludeRules,"ruleGroupType": "ManagedRuleGroup"});} - } - } if(postProcessRuleGroups == []){ const securityservicepolicydata = { "type":"WAFV2", @@ -830,24 +831,24 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { }); } if(preProcessRuleGroups == []){ - const securityservicepolicydata = { - "type":"WAFV2", - "defaultAction":{ "type":"ALLOW" }, - "preProcessRuleGroups": [], - "postProcessRuleGroups": postProcessRuleGroups, - "overrideCustomerWebACLAssociation":true, - "loggingConfiguration": { - "logDestinationConfigs":["${S3DeliveryStream.Arn}"] + const securityservicepolicydata = { + "type":"WAFV2", + "defaultAction":{ "type":"ALLOW" }, + "preProcessRuleGroups": [], + "postProcessRuleGroups": postProcessRuleGroups, + "overrideCustomerWebACLAssociation":true, + "loggingConfiguration": { + "logDestinationConfigs":["${S3DeliveryStream.Arn}"] + } } - } - const fmsPolicy = new fms.CfnPolicy(this, "CfnPolicy", { - excludeResourceTags: false, - remediationEnabled: false, - resourceType: props.config.WebAcl.Type, - policyName: props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage+ "-" +props.config.General.DeployHash, - includeMap: {account: props.config.General.DeployTo }, - securityServicePolicyData: {"Type": "WAFV2","ManagedServiceData": cdk.Fn.sub(JSON.stringify(securityservicepolicydata))} - }); + const fmsPolicy = new fms.CfnPolicy(this, "CfnPolicy", { + excludeResourceTags: false, + remediationEnabled: false, + resourceType: props.config.WebAcl.Type, + policyName: props.config.General.Prefix.toUpperCase() + "-" + props.config.WebAcl.Name + "-" + props.config.General.Stage+ "-" +props.config.General.DeployHash, + includeMap: {account: props.config.General.DeployTo }, + securityServicePolicyData: {"Type": "WAFV2","ManagedServiceData": cdk.Fn.sub(JSON.stringify(securityservicepolicydata))} + }); } if(preProcessRuleGroups != [] && postProcessRuleGroups != []){ const securityservicepolicydata = { @@ -868,7 +869,7 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { includeMap: {account: props.config.General.DeployTo }, securityServicePolicyData: {"Type": "WAFV2","ManagedServiceData": cdk.Fn.sub(JSON.stringify(securityservicepolicydata))} }); - } + } } const options = { flag : "w", force: true }; From c33355385b12962ba983d2f90ebd5fa511b3c66f Mon Sep 17 00:00:00 2001 From: daknhh Date: Fri, 18 Feb 2022 13:37:22 +0100 Subject: [PATCH 23/24] Adjust readme --- README.md | 4 ++-- static/example_deployment.jpg | Bin 275480 -> 0 bytes static/example_deployment.png | Bin 0 -> 156000 bytes 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 static/example_deployment.jpg create mode 100644 static/example_deployment.png diff --git a/README.md b/README.md index 64c88251..600e5c26 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AWS FIREWALL FACTORY +# AWS FIREWALL FACTORY v2 [![License: Apache2](https://img.shields.io/badge/license-Apache%202-lightgrey.svg)](http://www.apache.org/licenses/) [![cdk](https://img.shields.io/badge/aws_cdk-v2-orange.svg)](https://docs.aws.amazon.com/cdk/v2/guide/home.html) [![latest](https://img.shields.io/badge/latest-release-yellow.svg)](https://github.com/globaldatanet/aws-firewall-factory/releases) @@ -93,7 +93,7 @@ If you want to learn something more about the AWS Firewall Factory feel free to 2. Assume AWS Profile `awsume PROFILENAME` 3. Enter `task deploy` -![Example Deployment](./static/example_deployment.jpg "Example Deployment") +![Example Deployment](./static/example_deployment.png "Example Deployment") ### 👏 Supporters diff --git a/static/example_deployment.jpg b/static/example_deployment.jpg deleted file mode 100644 index bcc7638d072679cd58898d5488e8ff8fea8c8c81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275480 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

)^|LsrnWV1L*nJey+uba1h0xY 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