diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..5035c46c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +dist +cdk.out \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..7943b508 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,45 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "semi": [2, "always"], + "eqeqeq": 2, + "indent": [2, 2, {"SwitchCase": 1}], + "quotes": ["error", "double"], + "linebreak-style": "off", + "array-bracket-newline": "off", + "array-bracket-spacing": ["error", "never"], + "no-trailing-spaces": "off", + "padded-blocks": "off", + "arrow-body-style": "off", + "init-declarations": "off", + "comma-dangle": "off", + "keyword-spacing": [0, {"before": true, "after": true, "overrides": null}], + "prefer-template": "off", + "id-blacklist": "off", + "no-console": "off", + "no-sync": "off", + "complexity": "off", + "max-statements": "off", + "array-element-newline": "off", + "object-curly-spacing": "off", + "template-curly-spacing": "off", + "camelcase": "off", + "no-use-before-define": "off", + "id-length": "off", + "id-match": "off", + "max-len": "off", + "no-magic-numbers": "off", + "no-underscore-dangle": "off", + "no-process-env": "off", + "func-style": ["error", "declaration", { "allowArrowFunctions": true }] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..bd81d818 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out +cdk.context.json +package-lock.json \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..c1d6d45d --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/CHANGELOG.md b/CHANGELOG.md index 65778d27..7fbcb790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ ## Released +## 2.1.0 + +### Added + +- Added Linting with typescript-eslint +- Added .gitignore and .npmignore file +- Added 2 functions for building service data (managed & custom rules) to remove redundant code + +### Changed + +- Refactoring bin file: outsource capacity checks & other functions to helpers.ts +- Transform capacity.json to Typescript Type Rule +- Start refactoring lib file: get rid of redundant code and use JS shortcuts +- Extend types of the Config interface +- Restructuring runtime properties: introduce separate layer for PreProcess and PostProcess +- New types for Firewall Manager API and CDK mapping ## 2.0.0 ### Added diff --git a/Taskfile.yml b/Taskfile.yml index 65748254..fddf786b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -67,4 +67,9 @@ tasks: silent: true preconditions: - sh: if [[ {{.WAF_TEST}} = true ]] ; then exit 0; else exit 1; fi - msg: โญ Skipping WAF Testing ๐Ÿงช \ No newline at end of file + msg: โญ Skipping WAF Testing ๐Ÿงช + validateconfig: + desc: Validation of the current config + cmds: + - ts-node test/config-loader.ts + silent: true \ No newline at end of file diff --git a/bin/plattform-wafv2-cdk-automation.ts b/bin/plattform-wafv2-cdk-automation.ts index 011d507e..14aa1756 100644 --- a/bin/plattform-wafv2-cdk-automation.ts +++ b/bin/plattform-wafv2-cdk-automation.ts @@ -1,398 +1,102 @@ #!/usr/bin/env node import { PlattformWafv2CdkAutomationStack } from "../lib/plattform-wafv2-cdk-automation-stack"; import * as cdk from "aws-cdk-lib"; -import * as fs from "fs"; -import { WAFV2Client, CheckCapacityCommandOutput, CheckCapacityCommand, CheckCapacityCommandInput, DescribeManagedRuleGroupCommand, DescribeManagedRuleGroupCommandInput } from "@aws-sdk/client-wafv2"; -import * as quota from "@aws-sdk/client-service-quotas"; -import * as cloudformation from "@aws-sdk/client-cloudformation" -import { FMSClient, ListPoliciesCommand, ListPoliciesCommandInput } from "@aws-sdk/client-fms"; -import { exit, off, prependOnceListener } from "process"; -import * as template from "../values/calculatecapacity.json"; -import { print } from "util"; -import * as lodash from "lodash"; +import { realpathSync, existsSync } from "fs"; 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: [] -} +import { Config } from "../lib/types/config"; +import { isPolicyQuotaReached, isWcuQuotaReached, setOutputsFromStack, initRuntimeProperties } from "../lib/tools/helpers"; +import * as packageJsonObject from "../package.json"; -function str2ab(str: string): Uint8Array { - var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char - var bufView = new Uint8Array(buf); - for (var i = 0, strLen = str.length; i < strLen; i++) { - bufView[i] = str.charCodeAt(i); - } - return bufView; -} +/** + * Version of the AWS Firewall Factory - extracted from package.json + */ +const FIREWALL_FACTORY_VERSION = packageJsonObject.version; -function convertRules(o: any) { - var newO: any, origKey: any, value: any - if (o instanceof Array) { - return o.map(function (value) { - if (typeof value === "object") { - value = convertRules(value) - } - return value - }) - } else { - newO = {} - for (origKey in o) { - if (o.hasOwnProperty(origKey)) { - value = o[origKey] - if (value instanceof Array || (value !== null && value.constructor === Object)) { - value = convertRules(value) - } - if (origKey == "SearchString") { - value = str2ab(o[origKey]) - } - newO[origKey] = value - } - } - } - return newO -} - -async function ListPolicies(): Promise { - const client = new FMSClient({ region: deploymentregion }); - const input: ListPoliciesCommandInput = { - }; - const command = new ListPoliciesCommand(input); - const response = await client.send(command); - return response.PolicyList?.length || 0 -} - -async function CheckCapacity(Scope: string, calculated_capacity_json: object): Promise { - const client = new WAFV2Client({ region: deploymentregion }); - const newRules = convertRules(calculated_capacity_json) - const input: CheckCapacityCommandInput = { - Scope: Scope, - Rules: newRules - }; - const command = new CheckCapacityCommand(input); - const response: any = await client.send(command); - return response.Capacity | 0 -} -async function CheckQuota(Quoata: string): Promise{ - let current_quota = 0 - const quoata_client = new quota.ServiceQuotasClient({ region: deploymentregion }); - const input: quota.GetAWSDefaultServiceQuotaCommandInput = { - QuotaCode: Quoata, - ServiceCode: "fms" - }; - const command = new quota.GetAWSDefaultServiceQuotaCommand(input); - const responsequoata = await quoata_client.send(command); - if(responsequoata.Quota?.Adjustable == true){ - const input: quota.ListRequestedServiceQuotaChangeHistoryByQuotaCommandInput = { - QuotaCode: Quoata, - ServiceCode: "fms" - }; - const command = new quota.ListRequestedServiceQuotaChangeHistoryByQuotaCommand(input); - const newquota = await quoata_client.send(command); - if(newquota.RequestedQuotas != []){ - if(newquota.RequestedQuotas?.length || 0 == 0){ - const sortquota = lodash.sortBy(newquota.RequestedQuotas,["Created"]); - if(sortquota?.length == 1){ - if(sortquota?.[0].Status != "APPROVED"){ - console.log("โ„น๏ธ There is an open Quota request for " + Quoata + " but it is still not approved using DEFAULT Quota.") - current_quota = responsequoata.Quota?.Value || 0 - return current_quota - } - if(sortquota?.[0].Status == "APPROVED"){ - current_quota = sortquota?.[0].DesiredValue || 0 - return current_quota - } - } - } - else{ - current_quota = responsequoata.Quota?.Value || 0 - return current_quota - } - } - else{ - current_quota = responsequoata.Quota?.Value || 0 - return current_quota - } - } - current_quota = responsequoata.Quota?.Value || 0 - return current_quota -} - -async function GetManagedRuleCapacity(Vendor: string, Name: string, Scope: string, Version: string): Promise{ - const client = new WAFV2Client({ region: deploymentregion }); - if(Version == ""){ - const input: DescribeManagedRuleGroupCommandInput = { - VendorName: Vendor, - Name: Name, - Scope: Scope - } - const command = new DescribeManagedRuleGroupCommand(input); - const response: any = await client.send(command); - return response.Capacity | 0 - } - else{ - const input: DescribeManagedRuleGroupCommandInput = { - VendorName: Vendor, - Name: Name, - Scope: Scope, - VersionName: Version - } - const command = new DescribeManagedRuleGroupCommand(input); - const response: any = await client.send(command); - return response.Capacity | 0 - } -} +/** + * relative path to config file imported from the env PROCESS_PARAMETERS + */ +const configFile = process.env.PROCESS_PARAMETERS; -async function GetOutputsFromStack(StackName:string,config: Config): Promise{ - const cloudformation_client = new cloudformation.CloudFormationClient({ region: deploymentregion }); - const params ={ - StackName: StackName - } - const command = new cloudformation.DescribeStacksCommand(params); - const responsestack = await cloudformation_client.send(command); - if(responsestack.Stacks?.[0].StackName !== undefined && responsestack.Stacks?.[0].Outputs !== undefined){ - for(const output of responsestack.Stacks?.[0].Outputs){ - if(output.OutputKey == "DeployedRuleGroupNames") - { - runtimeprops.PreProcessDeployedRuleGroupNames = output.OutputValue?.split(",",output.OutputValue?.length) || [] - } - else if(output.OutputKey == "DeployedRuleGroupIdentifier") - { - runtimeprops.PreProcessDeployedRuleGroupIdentifier = output.OutputValue?.split(",",output.OutputValue?.length) || [] - } - else if(output.OutputKey == "DeployedRuleGroupCapacities") - { - const arrayOfNumbers = output.OutputValue?.split(",",output.OutputValue?.length).map(Number) || []; - runtimeprops.PreProcessDeployedRuleGroupCapacities = arrayOfNumbers - } - if(output.OutputKey == "PreProcessDeployedRuleGroupNames") - { - runtimeprops.PreProcessDeployedRuleGroupNames = output.OutputValue?.split(",",output.OutputValue?.length) || [] - } - else if(output.OutputKey == "PreProcessDeployedRuleGroupIdentifier") - { - runtimeprops.PreProcessDeployedRuleGroupIdentifier = output.OutputValue?.split(",",output.OutputValue?.length) || [] - } - else if(output.OutputKey == "PreProcessDeployedRuleGroupCapacities") - { - const arrayOfNumbers = output.OutputValue?.split(",",output.OutputValue?.length).map(Number) || []; - runtimeprops.PreProcessDeployedRuleGroupCapacities = arrayOfNumbers - } - if(output.OutputKey == "PostProcessDeployedRuleGroupNames") - { - runtimeprops.PostProcessDeployedRuleGroupNames = output.OutputValue?.split(",",output.OutputValue?.length) || [] - } - else if(output.OutputKey == "PostProcessDeployedRuleGroupIdentifier") - { - runtimeprops.PostProcessDeployedRuleGroupIdentifier = output.OutputValue?.split(",",output.OutputValue?.length) || [] - } - else if(output.OutputKey == "PostProcessDeployedRuleGroupCapacities") - { - const arrayOfNumbers = output.OutputValue?.split(",",output.OutputValue?.length).map(Number) || []; - runtimeprops.PostProcessDeployedRuleGroupCapacities = arrayOfNumbers - } - } - } -} +/** + * the region into which the stack is deployed + */ +let deploymentRegion = ""; -// Export to Outputs Capacity Names etc. -let deploymentregion = "" -const configFile = process.env.PROCESS_PARAMETERS; -if (configFile && fs.existsSync(configFile)) { - const config: Config = require(fs.realpathSync(configFile)); +if (configFile && existsSync(configFile)) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const config: Config = require(realpathSync(configFile)); if (validate(config)){ - const app = new cdk.App(); - runtimeprops.PostProcessRuleCapacities = [] - runtimeprops.PreProcessRuleCapacities = [] - let Temp_Hash - if(config.WebAcl.Scope == "CLOUDFRONT"){ - deploymentregion = "us-east-1" + if(config.WebAcl.Scope === "CLOUDFRONT"){ + deploymentRegion = "us-east-1"; } else{ - deploymentregion = process.env.REGION || "eu-central-1" + deploymentRegion = process.env.REGION || "eu-central-1"; } console.log(` โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ - โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• + โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• + โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ•”โ• + โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ + โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• `); console.log("\x1b[36m","\n by globaldatanet","\x1b[0m"); - console.log("\n๐Ÿท Version: ","\x1b[4m",afwfver,"\x1b[0m") + console.log("\n๐Ÿท Version: ","\x1b[4m",FIREWALL_FACTORY_VERSION,"\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) - config.General.DeployHash = Temp_Hash - console.log("#๏ธโƒฃ Generated Deployment Hash for this WAF: "+ config.General.DeployHash) - } - else{ - console.log("#๏ธโƒฃ Deployment Hash for this WAF: "+ config.General.DeployHash) - } - console.log("๐Ÿ”ฅ Deploy FMS Policy: " + config.General.Prefix.toUpperCase() + "-" + config.WebAcl.Name.toUpperCase()+ "-" + config.General.Stage + "-" + config.General.DeployHash + "\n โฆ‚ Type: " +config.WebAcl.Type + "\n๐Ÿ“š Stackname: " + config.General.Prefix.toUpperCase() + "-WAF-" + config.WebAcl.Name.toUpperCase() +"-"+config.General.Stage.toUpperCase() +"-"+config.General.DeployHash.toUpperCase()); + console.log("๐ŸŒŽ CDK deployment region:","\x1b[33m","\n "+deploymentRegion,"\x1b[0m \n"); (async () => { - let exitCode = 0; - const StackName = config.General.Prefix.toUpperCase() + "-WAF-" + config.WebAcl.Name.toUpperCase() +"-"+config.General.Stage.toUpperCase() +"-"+config.General.DeployHash.toUpperCase() - if(Temp_Hash === config.General.DeployHash){ - const policies = await ListPolicies(); - if(process.env.SKIP_QUOTA_CHECK == "true"){ - console.log("โ—๏ธ SKIPPING Quota Check for Quota: L-0B28E140") - } - else{ - const quota_policies = await CheckQuota("L-0B28E140"); - if(quota_policies <= policies){ - console.log("\n๐Ÿšจ You are about to exceed the limit for Policies per region.\n Region Quota: " +quota_policies + "\n Deployed Policies: " + policies + "\n ๏น— Stopping deployment ๏น—") - exitCode = 1; + const isNewStack = (config.General.DeployHash === ""); + const runtimeProperties = initRuntimeProperties(); + if(isNewStack){ + console.log("โ„น First Deployment of this WAF."); + const tempHash = Date.now().toString(36); + config.General.DeployHash = tempHash; + console.log("#๏ธโƒฃ Generated Deployment Hash for this WAF: "+ config.General.DeployHash); + if (process.env.SKIP_QUOTA_CHECK === "true") { + console.log("โ—๏ธ SKIPPING Quota Check for Policies."); + } else { + const policyQuotaReached = await isPolicyQuotaReached(deploymentRegion); + if (policyQuotaReached) { + console.error("\u001B[31m","๐Ÿšจ Exit process due Quota Check for Policies ๐Ÿšจ \n\n","\x1b[0m" + "\n\n"); + process.exit(1); } } - console.log("โ„น First Deployment of this WAF.") - }else{ - await GetOutputsFromStack(StackName, config); } - 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.")} else{ - while (count < config.WebAcl.PreProcess.CustomRules.length) { - if("Captcha" in config.WebAcl.PreProcess.CustomRules[count].Action){ - const rule_calculated_capacity_json = []; - const temp_template = template; - temp_template.Statement = config.WebAcl.PreProcess.CustomRules[count].Statement; - temp_template.Action = config.WebAcl.PreProcess.CustomRules[count].Action; - temp_template.CaptchaConfig = config.WebAcl.PreProcess.CustomRules[count].CaptchaConfig; - rule_calculated_capacity_json.push(temp_template); - const capacity = await CheckCapacity(config.WebAcl.Scope, rule_calculated_capacity_json); - runtimeprops.PreProcessRuleCapacities.push(capacity); - } - else{ - const rule_calculated_capacity_json = []; - const temp_template = template; - temp_template.Statement = config.WebAcl.PreProcess.CustomRules[count].Statement; - temp_template.Action = config.WebAcl.PreProcess.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.PreProcessRuleCapacities.push(capacity); - } - count++ - } - pre_calculate_capacity_sum = runtimeprops.PreProcessRuleCapacities.reduce(function (a, b) { - return a + b; - }, 0); + await setOutputsFromStack(deploymentRegion, runtimeProperties, config); + console.log("#๏ธโƒฃ Deployment Hash for this WAF: "+ config.General.DeployHash); } - 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.") - } - 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){ - temp_template.CaptchaConfig = config.WebAcl.PostProcess.CustomRules[count].CaptchaConfig; - } - else{ - delete temp_template.CaptchaConfig - } - 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) { - return a + b; - }, 0); - } - let managedrule; - let managedrulecapacity = 0; - 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 + "]") - managedrulecapacity = managedrulecapacity + capacity - } - } - if(config.WebAcl.PostProcess.ManagedRuleGroups === undefined){ - 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 + "]") - managedrulecapacity = managedrulecapacity + capacity - } - } - runtimeprops.PreProcessCapacity = pre_calculate_capacity_sum - runtimeprops.PostProcessCapacity = post_calculate_capacity_sum - const custom_capacity = runtimeprops.PreProcessCapacity + runtimeprops.PostProcessCapacity - const total_wcu = runtimeprops.PreProcessCapacity + runtimeprops.PostProcessCapacity + managedrulecapacity - const quote_wcu = await CheckQuota("L-D86ED2F3"); - 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 + "] (๐Ÿฅ‡[" + 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") - 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") - exitCode = 1; - } - if(exitCode == 1){ - process.exitCode = 1; + console.log("๐Ÿ”ฅ Deploy FMS Policy: " + config.General.Prefix.toUpperCase() + "-" + config.WebAcl.Name.toUpperCase()+ "-" + config.General.Stage + "-" + config.General.DeployHash + "\n โฆ‚ Type: " +config.WebAcl.Type + "\n๐Ÿ“š Stackname: " + config.General.Prefix.toUpperCase() + "-WAF-" + config.WebAcl.Name.toUpperCase() +"-"+config.General.Stage.toUpperCase() +"-"+config.General.DeployHash.toUpperCase()); + const wcuQuotaReached = await isWcuQuotaReached(deploymentRegion, runtimeProperties, config); + if(wcuQuotaReached) { + console.error("\u001B[31m","๐Ÿšจ Exit process due Quota Check for WCU ๐Ÿšจ \n\n","\x1b[0m" + "\n\n"); + process.exit(1); } + const app = new cdk.App(); new PlattformWafv2CdkAutomationStack(app, config.General.Prefix.toUpperCase() + "-WAF-" + config.WebAcl.Name.toUpperCase() +"-"+config.General.Stage.toUpperCase() +"-"+config.General.DeployHash.toUpperCase(), { - config, runtimeprops, + config, runtimeProperties: runtimeProperties, env: { - region: deploymentregion, + region: deploymentRegion, account: process.env.CDK_DEFAULT_ACCOUNT, }, }); - //app.synth() })(); - // } - }else { + } else { console.log(` โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ - โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• + โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• + โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ•”โ• + โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ + โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• `); - console.log("\n๐Ÿท Version: ","\x1b[4m",afwfver,"\x1b[0m") - console.log("\n ๐Ÿงช Validation of your ConfigFile: \n ๐Ÿ“‚ " + configFile + "\n\n") + console.log("\n๐Ÿท Version: ","\x1b[4m",FIREWALL_FACTORY_VERSION,"\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; + process.exit(1); } } else { console.log("File", configFile, "not found. - NO CDK ERROR"); } -function elseif() { - throw new Error("Function not implemented."); -} diff --git a/cdk.out/cdk.out b/cdk.out/cdk.out index c1ee7b9a..5bdbc9d3 100644 --- a/cdk.out/cdk.out +++ b/cdk.out/cdk.out @@ -1 +1 @@ -{"version":"14.0.0"} \ No newline at end of file +{"version":"16.0.0"} \ No newline at end of file diff --git a/lib/plattform-wafv2-cdk-automation-stack.ts b/lib/plattform-wafv2-cdk-automation-stack.ts index d272fc7e..34f3b5b8 100644 --- a/lib/plattform-wafv2-cdk-automation-stack.ts +++ b/lib/plattform-wafv2-cdk-automation-stack.ts @@ -1,42 +1,44 @@ import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; -import {aws_wafv2 as wafv2} from "aws-cdk-lib"; -import {aws_fms as fms} from "aws-cdk-lib"; -import {aws_kinesisfirehose as firehouse} from "aws-cdk-lib"; -import {aws_iam as iam} from "aws-cdk-lib"; -import {aws_logs as logs} from "aws-cdk-lib"; +import { aws_wafv2 as wafv2 } from "aws-cdk-lib"; +import { aws_fms as fms } from "aws-cdk-lib"; +import { aws_kinesisfirehose as firehouse } from "aws-cdk-lib"; +import { aws_iam as iam } from "aws-cdk-lib"; +import { aws_logs as logs } from "aws-cdk-lib"; import { Config } from "./types/config"; -import { Runtimeprops } from "./types/runtimeprops"; +import { ManagedRuleGroup, ManagedServiceData, ServiceDataManagedRuleGroup, ServiceDataRuleGroup, Rule } from "./types/fms"; +import { RuntimeProperties, ProcessProperties } from "./types/runtimeprops"; import { promises as fsp } from "fs"; -import { toCamel } from "./tools/camel-case" +import { toAwsCamel } from "./tools/helpers"; export interface ConfigStackProps extends cdk.StackProps { readonly config: Config; - runtimeprops: Runtimeprops; + runtimeProperties: RuntimeProperties; } - export class PlattformWafv2CdkAutomationStack extends cdk.Stack { constructor(scope: Construct, id: string, props: ConfigStackProps) { super(scope, id, props); const account_id = cdk.Aws.ACCOUNT_ID; const region = cdk.Aws.REGION; - if(props.runtimeprops.PreProcessDeployedRuleGroupCapacities === undefined){ - console.log("โš™๏ธ Initialize PreProcess lists for Update mechanism.") - props.runtimeprops.PreProcessDeployedRuleGroupCapacities = [] - props.runtimeprops.PreProcessDeployedRuleGroupNames = [] - props.runtimeprops.PreProcessDeployedRuleGroupIdentifier = []} - if(props.runtimeprops.PostProcessDeployedRuleGroupCapacities === undefined){ - console.log("โš™๏ธ Initialize PostProcess lists for Update mechanism.") - props.runtimeprops.PostProcessDeployedRuleGroupCapacities = [] - props.runtimeprops.PostProcessDeployedRuleGroupNames = [] - props.runtimeprops.PostProcessDeployedRuleGroupIdentifier = []} - const CfnRole = new iam.CfnRole(this, "KinesisS3DeliveryRole",{ - assumeRolePolicyDocument: {"Version":"2012-10-17","Statement":[{"Sid":"","Effect":"Allow","Principal":{"Service":"firehose.amazonaws.com"},"Action":"sts:AssumeRole"}]} - }) - const CfnLogGroup = new logs.CfnLogGroup(this, "KinesisErrorLogging",{ - retentionInDays: 90}) + const CfnRole = new iam.CfnRole(this, "KinesisS3DeliveryRole", { + assumeRolePolicyDocument: { + Version: "2012-10-17", + Statement: [ + { + Sid: "", + Effect: "Allow", + Principal: { Service: "firehose.amazonaws.com" }, + Action: "sts:AssumeRole", + }, + ], + }, + }); + + const CfnLogGroup = new logs.CfnLogGroup(this, "KinesisErrorLogging", { + retentionInDays: 90, + }); const policy = { Version: "2012-10-17", @@ -50,797 +52,631 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack { "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:PutObject", - "s3:PutObjectAcl" + "s3:PutObjectAcl", ], Resource: [ - "arn:aws:s3:::"+props.config.General.S3LoggingBucketName, - "arn:aws:s3:::"+props.config.General.S3LoggingBucketName +"/*" - ] + "arn:aws:s3:::" + props.config.General.S3LoggingBucketName, + "arn:aws:s3:::" + props.config.General.S3LoggingBucketName + "/*", + ], }, { Effect: "Allow", - Action: [ - "logs:PutLogEvents" - ], - Resource: [ - CfnLogGroup.attrArn - ] + Action: ["logs:PutLogEvents"], + Resource: [CfnLogGroup.attrArn], }, { Effect: "Allow", - Action: [ - "kms:Decrypt", - "kms:GenerateDataKey" - ], - Resource: [ - props.config.General.FireHoseKeyArn - ] - } - ] - } + Action: ["kms:Decrypt", "kms:GenerateDataKey"], + Resource: [props.config.General.FireHoseKeyArn], + }, + ], + }; - new iam.CfnPolicy(this, "KinesisS3DeliveryPolicy",{ + new iam.CfnPolicy(this, "KinesisS3DeliveryPolicy", { policyDocument: policy, policyName: "firehose_delivery_policy", - roles: [CfnRole.ref] - }) - - new firehouse.CfnDeliveryStream(this, "S3DeliveryStream",{ - deliveryStreamName: "aws-waf-logs-"+props.config.General.Prefix+"-kinesis-wafv2log-"+props.config.WebAcl.Name+props.config.General.Stage+props.config.General.DeployHash, + roles: [CfnRole.ref], + }); + + new firehouse.CfnDeliveryStream(this, "S3DeliveryStream", { + deliveryStreamName: + "aws-waf-logs-" + + props.config.General.Prefix + + "-kinesis-wafv2log-" + + props.config.WebAcl.Name + + props.config.General.Stage + + props.config.General.DeployHash, extendedS3DestinationConfiguration: { - bucketArn:"arn:aws:s3:::"+props.config.General.S3LoggingBucketName, - encryptionConfiguration:{kmsEncryptionConfig:{awskmsKeyArn:props.config.General.FireHoseKeyArn}}, + bucketArn: "arn:aws:s3:::" + props.config.General.S3LoggingBucketName, + encryptionConfiguration: { + kmsEncryptionConfig: { + awskmsKeyArn: props.config.General.FireHoseKeyArn, + }, + }, roleArn: CfnRole.attrArn, - bufferingHints: {sizeInMBs:50, intervalInSeconds:60}, + bufferingHints: { sizeInMBs: 50, intervalInSeconds: 60 }, compressionFormat: "UNCOMPRESSED", - prefix: "AWSLogs/"+ account_id +"/FirewallManager/"+region+"/", - errorOutputPrefix: "AWSLogs/"+ account_id +"/FirewallManager/"+region+"/Errors" + prefix: "AWSLogs/" + account_id + "/FirewallManager/" + region + "/", + errorOutputPrefix: + "AWSLogs/" + account_id + "/FirewallManager/" + region + "/Errors", }, + }); + + const preProcessRuleGroups = []; + const postProcessRuleGroups = []; + console.log("Creating DEFAULT Policy."); + if (props.config.WebAcl.PreProcess.ManagedRuleGroups) { + preProcessRuleGroups.push(...buildServiceDataManagedRGs(props.config.WebAcl.PreProcess.ManagedRuleGroups)); + } else { + console.log("\nโ„น๏ธ No ManagedRuleGroups defined in PreProcess."); + } + if (props.config.WebAcl.PostProcess.ManagedRuleGroups) { + postProcessRuleGroups.push(...buildServiceDataManagedRGs(props.config.WebAcl.PostProcess.ManagedRuleGroups)); + } else { + console.log("โ„น๏ธ No ManagedRuleGroups defined in PostProcess."); + } + if (props.config.WebAcl.PreProcess.CustomRules) { + const customRgs = buildServiceDataCustomRGs(this, "Pre", props.runtimeProperties.PreProcess.Capacity, props.config.General.DeployHash, props.config.WebAcl.Name, props.config.WebAcl.Scope, props.config.General.Stage, props.runtimeProperties.PreProcess, props.config.General.Prefix, props.config.WebAcl.PreProcess.CustomRules); + preProcessRuleGroups.push(...customRgs); + } else { + console.log("\nโ„น๏ธ No Custom Rules defined in PreProcess."); + } + if (props.config.WebAcl.PostProcess.CustomRules) { + const customRgs = buildServiceDataCustomRGs(this, "Post", props.runtimeProperties.PostProcess.Capacity, props.config.General.DeployHash, props.config.WebAcl.Name, props.config.WebAcl.Scope, props.config.General.Stage, props.runtimeProperties.PostProcess, props.config.General.Prefix, props.config.WebAcl.PostProcess.CustomRules); + postProcessRuleGroups.push(...customRgs); + } else { + console.log("\nโ„น๏ธ No Custom Rules defined in PostProcess."); + } - }) + const managedServiceData : ManagedServiceData = { + type: "WAFV2", + defaultAction: { type: "ALLOW" }, + preProcessRuleGroups: preProcessRuleGroups, + postProcessRuleGroups: postProcessRuleGroups, + overrideCustomerWebACLAssociation: true, + loggingConfiguration: { + logDestinationConfigs: ["${S3DeliveryStream.Arn}"], + }, + }; + + 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(managedServiceData) + ), + }, + }); - if(props.config.WebAcl.PreProcess.CustomRules === undefined && props.config.WebAcl.PostProcess.CustomRules === undefined) - { - console.log("Creating DEFAULT Policy.") - const novalue = null - let mangedrule; - let ExcludeRules; - let OverrideAction; - const preProcessRuleGroups = [] - const postProcessRuleGroups = [] - if(props.config.WebAcl.PreProcess.ManagedRuleGroups === undefined){ - console.log("\nโ„น๏ธ No ManagedRuleGroups defined in PreProcess.") - } - else{ - 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"});} + const options = { flag: "w", force: true }; + (async () => { + try { + if (process.env.PROCESS_PARAMETERS) { + await fsp.writeFile( + process.env.PROCESS_PARAMETERS, + JSON.stringify(props.config, null, 2), + options + ); } + } catch (error) { + console.log("Error " + error); } - if(props.config.WebAcl.PostProcess.ManagedRuleGroups === undefined){ - console.log("โ„น๏ธ No ManagedRuleGroups defined in PostProcess.") + })(); + } +} + +function buildServiceDataManagedRGs(managedRuleGroups: ManagedRuleGroup[]) : ServiceDataManagedRuleGroup[] { + const cfnManagedRuleGroup : ServiceDataManagedRuleGroup[] = []; + for (const managedRuleGroup of managedRuleGroups) { + cfnManagedRuleGroup.push({ + managedRuleGroupIdentifier: { + vendorName: managedRuleGroup.Vendor, + managedRuleGroupName: managedRuleGroup.Name, + version: managedRuleGroup.Version !== "" ? managedRuleGroup.Version : null, + }, + overrideAction: managedRuleGroup.ExcludeRules && managedRuleGroup.OverrideAction ? managedRuleGroup.OverrideAction : { type: "NONE" }, + ruleGroupArn: null, + excludeRules: managedRuleGroup.ExcludeRules ? toAwsCamel(managedRuleGroup.ExcludeRules) : [], + ruleGroupType: "ManagedRuleGroup" + }); + } + return cfnManagedRuleGroup; +} + +function buildServiceDataCustomRGs(scope: Construct, type: "Pre" | "Post", capacity: number, deployHash: string, webaclName: string, webAclScope: string, stage: string, processRuntimeProps: ProcessProperties, prefix: string, ruleGroupSet: Rule[]) : ServiceDataRuleGroup[] { + const serviceDataRuleGroup : ServiceDataRuleGroup[] = []; + let icon; + if (type === "Pre") { + icon = "๐Ÿฅ‡ "; + } else { + icon = "๐Ÿฅˆ"; + } + console.log( + "\u001b[1m", + "\n"+icon+" Custom Rules " + type + "Process: ", + "\x1b[0m\n" + ); + if (capacity < 1000) { + const rules = []; + let count = 1; + for (const statement of ruleGroupSet) { + let rulename = ""; + if (statement.Name !== undefined) { + rulename = + statement.Name + "-" + type.toLocaleLowerCase() + "-" + deployHash; + } else { + rulename = + webaclName + + "-" + + type.toLocaleLowerCase() + + "-" + + stage + + "-" + + count.toString() + + "-" + + deployHash; } - else{ - 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"});} - } + let CfnRuleProperty; + if ("Captcha" in statement.Action) { + CfnRuleProperty = { + name: rulename, + priority: count, + action: toAwsCamel(statement.Action), + statement: toAwsCamel(statement.Statement), + visibilityConfig: { + sampledRequestsEnabled: + statement.VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: + statement.VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + captchaConfig: toAwsCamel(statement.CaptchaConfig), + ruleLabels: toAwsCamel(statement.RuleLabels), + }; + } else { + CfnRuleProperty = { + name: rulename, + priority: count, + action: toAwsCamel(statement.Action), + statement: toAwsCamel(statement.Statement), + visibilityConfig: { + sampledRequestsEnabled: + statement.VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: + statement.VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + ruleLabels: toAwsCamel(statement.RuleLabels), + }; } - const securityservicepolicydata = { - "type":"WAFV2", - "defaultAction":{ "type":"ALLOW" }, - "preProcessRuleGroups": preProcessRuleGroups, - "postProcessRuleGroups": postProcessRuleGroups, - "overrideCustomerWebACLAssociation":true, - "loggingConfiguration": { - "logDestinationConfigs":["${S3DeliveryStream.Arn}"] - } + let CfnRuleProperties: wafv2.CfnRuleGroup.RuleProperty; + if (statement.RuleLabels) { + CfnRuleProperties = CfnRuleProperty; + } else { + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty; + CfnRuleProperties = CfnRulePropertii; } - - 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))} - }); - + rules.push(CfnRuleProperties); + count += 1; } - else{ - const preProcessRuleGroups = [] - const postProcessRuleGroups = [] - if(props.config.WebAcl.PreProcess.CustomRules === undefined){ - console.log("\nโ„น๏ธ No Custom Rules defined in PreProcess.") - } - 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 = { - 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" - } - - 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) - } - } - 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, - }); + let name = + webaclName + + "-"+type.toLocaleLowerCase()+"-" + + stage + + "-" + + deployHash; + let rulegroupidentifier = type + "RuleGroup"; + if (processRuntimeProps.DeployedRuleGroupCapacities[0]) { + if ( + processRuntimeProps.DeployedRuleGroupCapacities[0] !== + capacity + ) { + console.log( + "โญ•๏ธ Deploy new RuleGroup because the Capacity has changed!" + ); + console.log( + "\n ๐ŸŸฅ Old Capacity: [" + + processRuntimeProps.DeployedRuleGroupCapacities[0] + + "]\n ๐ŸŸฉ New Capacity: [" + + processRuntimeProps.Capacity + + "]" + ); + if ( + processRuntimeProps.DeployedRuleGroupIdentifier[0] === + "RuleGroup" + ) { + rulegroupidentifier = type + "RG"; } - else{ - const threshold = 1000 - 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`); - let count = 0 - let rulegroupidentifier = "" - let name ="" - while (count < rulesets.length){ - if(typeof props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count] !== "undefined"){ - if(rulegroupcapacities[count] === props.runtimeprops.PreProcessDeployedRuleGroupCapacities[count]){ - 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 + "-preR-" + count.toString() + "-" +props.config.General.DeployHash - } - else{ - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-pre-" + count.toString() + "-" +props.config.General.DeployHash - } - console.log(" ๐Ÿ’ฌ New Name: "+ name) - } - 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") - } - } - }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 = 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){ - 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), - ruleLabels: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].RuleLabels) - } - } - 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", - }, - ruleLabels: toCamel(props.config.WebAcl.PreProcess.CustomRules[statementindex].RuleLabels) - } - } - let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(props.config.WebAcl.PreProcess.CustomRules[statementindex].RuleLabels){ - CfnRuleProperti = CfnRuleProperty - } - else{ - const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty - CfnRuleProperti = CfnRulePropertii - } - CfnRuleProperties.push(CfnRuleProperti) - rulegroupcounter++ - } - new wafv2.CfnRuleGroup(this,rulegroupidentifier, { - capacity: rulegroupcapacities[count], - scope: props.config.WebAcl.Scope, - rules: CfnRuleProperties, - name: name, - visibilityConfig: { - sampledRequestsEnabled: false, - cloudWatchMetricsEnabled: false, - metricName: name + "-metric", - } - }); - - 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 lenght = rulesets.length - props.runtimeprops.PreProcessDeployedRuleGroupCapacities.splice(lenght) - props.runtimeprops.PreProcessDeployedRuleGroupIdentifier.splice(lenght) - props.runtimeprops.PreProcessDeployedRuleGroupNames.splice(lenght) - - 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, - }); + if ( + processRuntimeProps.DeployedRuleGroupNames[0] === + webaclName + + "-" + + stage + + "-" + + deployHash + ) { + name = + prefix.toUpperCase() + + "-G" + + webaclName + + "-" + + stage + + "-" + + deployHash; } + console.log(" ๐Ÿ’ฌ New Name: " + name); + console.log(" ๐Ÿ“‡ New Identifier: " + rulegroupidentifier); } - if(props.config.WebAcl.PostProcess.CustomRules === undefined){ - console.log("\nโ„น๏ธ No Custom Rules defined in PostProcess.") - } - else{ - console.log("\u001b[1m","\n๐Ÿฅˆ Custom Rules PostProcess:","\x1b[0m\n") - if (props.runtimeprops.PostProcessCapacity < 1000){ - const rules = []; - let count = 1 - - for(const statement of props.config.WebAcl.PostProcess.CustomRules){ - let rulename = "" - if(statement.Name !== undefined){ - rulename = statement.Name + "-post-" + props.config.General.DeployHash - } - else{ - rulename = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-post-" + 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 = { - 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(statement.RuleLabels){ - CfnRuleProperti = CfnRuleProperty - } - else{ - const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty - CfnRuleProperti = CfnRulePropertii - } - rules.push(CfnRuleProperti) - count +=1 - } - - 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] === "PostRuleGroup"){ - rulegroupidentifier ="postRG" - } - 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) - } + } + new wafv2.CfnRuleGroup(scope, rulegroupidentifier, { + capacity: processRuntimeProps.Capacity, + scope: webAclScope, + rules: rules, + name: name, + visibilityConfig: { + sampledRequestsEnabled: false, + cloudWatchMetricsEnabled: false, + metricName: + prefix.toUpperCase() + + "-" + + webaclName + + "-" + + stage + + "-" + + deployHash, + }, + }); + serviceDataRuleGroup.push({ + ruleGroupType: "RuleGroup", + ruleGroupArn: "${" + rulegroupidentifier + ".Arn}", + overrideAction: { type: "NONE" }, + }); + console.log( + " โžก๏ธ Creating " + + rulegroupidentifier + + " with calculated capacity: [" + + processRuntimeProps.Capacity + + "]" + ); + processRuntimeProps.DeployedRuleGroupCapacities.splice(0); + processRuntimeProps.DeployedRuleGroupIdentifier.splice(0); + processRuntimeProps.DeployedRuleGroupNames.splice(0); + + processRuntimeProps.DeployedRuleGroupIdentifier[0] = + rulegroupidentifier; + processRuntimeProps.DeployedRuleGroupNames[0] = name; + processRuntimeProps.DeployedRuleGroupCapacities[0] = + processRuntimeProps.Capacity; + + new cdk.CfnOutput(scope, "PreProcessDeployedRuleGroupNames", { + value: + processRuntimeProps.DeployedRuleGroupNames.toString(), + description: "PreProcessDeployedRuleGroupNames", + exportName: + "PreProcessDeployedRuleGroupNames" + + deployHash, + }); + + new cdk.CfnOutput(scope, "PreProcessDeployedRuleGroupCapacities", { + value: + processRuntimeProps.DeployedRuleGroupCapacities.toString(), + description: "PreProcessDeployedRuleGroupCapacities", + exportName: + "PreProcessDeployedRuleGroupCapacities" + + deployHash, + }); + + new cdk.CfnOutput(scope, "PreProcessDeployedRuleGroupIdentifier", { + value: + processRuntimeProps.DeployedRuleGroupIdentifier.toString(), + description: "PreProcessDeployedRuleGroupIdentifier", + exportName: + "PreProcessDeployedRuleGroupIdentifier" + + deployHash, + }); + } else { + const threshold = 1000; + const rulesets: any[] = []; + const indexes: number[] = []; + const rulegroupcapacities = []; + while ( + indexes.length < processRuntimeProps.RuleCapacities.length + ) { + let tracker = 0; + const ruleset: any[] = []; + processRuntimeProps.RuleCapacities.map((v, i) => { + if (!indexes.find((e) => e === i + 1)) { + if (v + tracker <= threshold) { + tracker += v; + ruleset.push(i); + indexes.push(i + 1); } - new wafv2.CfnRuleGroup(this,rulegroupidentifier, { - capacity: props.runtimeprops.PostProcessCapacity, - scope: props.config.WebAcl.Scope, - rules: rules, - name: name, - visibilityConfig: { - sampledRequestsEnabled: false, - cloudWatchMetricsEnabled: false, - metricName: name + "-metric", - } - }); - 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, - }); - } - else{ - const threshold = 1000 - 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) - } + }); + rulesets.push(ruleset); + rulegroupcapacities.push(tracker); + } - console.log(` ๐Ÿ–– Split Rules into ${rulesets.length.toString()} RuleGroups:\n`); - let count = 0 - let rulegroupidentifier = "" - let name ="" - while (count < rulesets.length){ - if(typeof props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count] !== "undefined"){ - if(rulegroupcapacities[count] === props.runtimeprops.PostProcessDeployedRuleGroupCapacities[count]){ - 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 + "-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{ - 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 = "postG"+count.toString() - } - else{ - rulegroupidentifier = "postR"+count.toString() - } - console.log(" ๐Ÿ“‡ New Identifier: "+ rulegroupidentifier + "\n") - } - } - }else{ - rulegroupidentifier = "postR"+count.toString() - name = props.config.WebAcl.Name + "-" + props.config.General.Stage + "-post-" + 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 + "-post-" + Temp_Hash - } - else{ - 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){ - 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), - ruleLabels: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels) - } - } - 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", - }, - ruleLabels: toCamel(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels) - } - } - let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty - if(props.config.WebAcl.PostProcess.CustomRules[statementindex].RuleLabels){ - const CfnRulePropertii = CfnRuleProperty - CfnRuleProperti = CfnRulePropertii - } - else{ - const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty - CfnRuleProperti = CfnRulePropertii - } - CfnRuleProperties.push(CfnRuleProperti) - rulegroupcounter++ + console.log( + ` ๐Ÿ–– Split Rules into ${rulesets.length.toString()} RuleGroups: \n` + ); + let count = 0; + let rulegroupidentifier = ""; + let name = ""; + while (count < rulesets.length) { + if (processRuntimeProps.DeployedRuleGroupCapacities[count]) { + if ( + rulegroupcapacities[count] === + processRuntimeProps.DeployedRuleGroupCapacities[count] + ) { + rulegroupidentifier = type + "R" + count.toString(); + name = + webaclName + + "-" + + type.toLocaleLowerCase() + + "-" + + stage + + "-" + + count.toString() + + "-" + + deployHash; + } else { + console.log( + "\nโญ•๏ธ Deploy new RuleGroup because the Capacity has changed for " + + processRuntimeProps.DeployedRuleGroupIdentifier[ + count + ] + + " !" + ); + console.log( + "\n ๐ŸŸฅ Old Capacity: [" + + processRuntimeProps.DeployedRuleGroupCapacities[ + count + ] + + "]\n ๐ŸŸฉ New Capacity: [" + + rulegroupcapacities[count] + + "]" + ); + if (processRuntimeProps.DeployedRuleGroupCapacities[count]) { + if ( + processRuntimeProps.DeployedRuleGroupNames[ + count + ] === + webaclName + + "-" + + stage + + "-" + + count.toString() + + "-" + + deployHash + ) { + name = + webaclName + + "-" + + stage + + "-"+ type.toLocaleLowerCase() + "R-" + + count.toString() + + "-" + + deployHash; + } else { + name = + webaclName + + "-" + + stage + + "-"+ type.toLocaleLowerCase() +"-" + + count.toString() + + "-" + + deployHash; } - 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) - - new cdk.CfnOutput(this, "PostProcessDeployedRuleGroupNames", { - value: props.runtimeprops.PostProcessDeployedRuleGroupNames.toString(), - description: "PostProcessDeployedRuleGroupNames", - exportName: "PostProcessDeployedRuleGroupNames"+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, "PostProcessDeployedRuleGroupCapacities", { - value: props.runtimeprops.PostProcessDeployedRuleGroupCapacities.toString(), - description: "PostProcessDeployedRuleGroupCapacities", - exportName: "PostProcessDeployedRuleGroupCapacities"+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; - 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(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 + console.log(" ๐Ÿ’ฌ New Name: " + name); } - else{ - PreProcessExcludeRules = [] - OverrideAction = { "type": "NONE" } + if (processRuntimeProps.DeployedRuleGroupIdentifier[count]) { + if ( + processRuntimeProps.DeployedRuleGroupIdentifier[ + count + ] === + "R" + count.toString() + ) { + rulegroupidentifier = type + "G" + count.toString(); + } else { + rulegroupidentifier = type + "R" + count.toString(); + } + console.log( + " ๐Ÿ“‡ New Identifier: " + rulegroupidentifier + "\n" + ); } - 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"});} } + } else { + rulegroupidentifier = type + "R" + count.toString(); + name = + webaclName + + "-" + + stage + + "-" + + count.toString() + + "-" + + deployHash; } - if(postProcessRuleGroups === []){ - const securityservicepolicydata = { - "type":"WAFV2", - "defaultAction":{ "type":"ALLOW" }, - "preProcessRuleGroups": preProcessRuleGroups, - "postProcessRuleGroups": [], - "overrideCustomerWebACLAssociation":true, - "loggingConfiguration": { - "logDestinationConfigs":["${S3DeliveryStream.Arn}"] - } + const CfnRuleProperties = []; + let rulegroupcounter = 0; + while (rulegroupcounter < rulesets[count].length) { + const statementindex = rulesets[count][rulegroupcounter]; + let rulename = ""; + if ( + ruleGroupSet[statementindex] + .Name !== undefined + ) { + const Temp_Hash = Date.now().toString(36); + rulename = + ruleGroupSet[statementindex] + .Name + + "-" + + Temp_Hash; + } else { + rulename = + webaclName + + "-" + + stage + + "-"+type.toLocaleLowerCase()+"-" + + rulegroupcounter.toString() + + "-" + + deployHash; } - 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 === []){ - const securityservicepolicydata = { - "type":"WAFV2", - "defaultAction":{ "type":"ALLOW" }, - "preProcessRuleGroups": [], - "postProcessRuleGroups": postProcessRuleGroups, - "overrideCustomerWebACLAssociation":true, - "loggingConfiguration": { - "logDestinationConfigs":["${S3DeliveryStream.Arn}"] - } + let CfnRuleProperty; + if ( + "Captcha" in + ruleGroupSet[statementindex] + .Action + ) { + CfnRuleProperty = { + name: rulename, + priority: rulegroupcounter, + action: toAwsCamel( + ruleGroupSet[statementindex] + .Action + ), + statement: toAwsCamel( + ruleGroupSet[statementindex] + .Statement + ), + visibilityConfig: { + sampledRequestsEnabled: + ruleGroupSet[statementindex] + .VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: + ruleGroupSet[statementindex] + .VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + captchaConfig: toAwsCamel( + ruleGroupSet[statementindex] + .CaptchaConfig + ), + ruleLabels: toAwsCamel( + ruleGroupSet[statementindex] + .RuleLabels + ), + }; + } else { + CfnRuleProperty = { + name: rulename, + priority: rulegroupcounter, + action: toAwsCamel( + ruleGroupSet[statementindex] + .Action + ), + statement: toAwsCamel( + ruleGroupSet[statementindex] + .Statement + ), + visibilityConfig: { + sampledRequestsEnabled: + ruleGroupSet[statementindex] + .VisibilityConfig.SampledRequestsEnabled, + cloudWatchMetricsEnabled: + ruleGroupSet[statementindex] + .VisibilityConfig.CloudWatchMetricsEnabled, + metricName: rulename + "-metric", + }, + ruleLabels: toAwsCamel( + ruleGroupSet[statementindex] + .RuleLabels + ), + }; } - 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}"] - } + let CfnRuleProperti: wafv2.CfnRuleGroup.RuleProperty; + if ( + ruleGroupSet[statementindex] + .RuleLabels + ) { + CfnRuleProperti = CfnRuleProperty; + } else { + const { ruleLabels, ...CfnRulePropertii } = CfnRuleProperty; + CfnRuleProperti = CfnRulePropertii; } - 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))} - }); + CfnRuleProperties.push(CfnRuleProperti); + rulegroupcounter++; } - } + new wafv2.CfnRuleGroup(scope, rulegroupidentifier, { + capacity: rulegroupcapacities[count], + scope: webAclScope, + rules: CfnRuleProperties, + name: name, + visibilityConfig: { + sampledRequestsEnabled: false, + cloudWatchMetricsEnabled: false, + metricName: name + "-metric", + }, + }); - const options = { flag : "w", force: true }; - (async () => { - try { - if (process.env.PROCESS_PARAMETERS) { - await fsp.writeFile(process.env.PROCESS_PARAMETERS,JSON.stringify(props.config,null,2),options); - } - } catch (error) { - console.log("Error " + error) - } - })(); + serviceDataRuleGroup.push({ + ruleGroupType: "RuleGroup", + ruleGroupArn: "${" + rulegroupidentifier + ".Arn}", + overrideAction: { type: "NONE" }, + }); + console.log( + " โžก๏ธ Creating " + + rulegroupidentifier + + " with calculated capacity: [" + + rulegroupcapacities[count].toString() + + "]" + ); + processRuntimeProps.DeployedRuleGroupCapacities[count] = + rulegroupcapacities[count]; + processRuntimeProps.DeployedRuleGroupIdentifier[count] = + rulegroupidentifier; + processRuntimeProps.DeployedRuleGroupNames[count] = name; + count++; + } + const lenght = rulesets.length; + processRuntimeProps.DeployedRuleGroupCapacities.splice( + lenght + ); + processRuntimeProps.DeployedRuleGroupIdentifier.splice( + lenght + ); + processRuntimeProps.DeployedRuleGroupNames.splice(lenght); + + new cdk.CfnOutput(scope, "PreProcessDeployedRuleGroupNames", { + value: + processRuntimeProps.DeployedRuleGroupNames.toString(), + description: "PreProcessDeployedRuleGroupNames", + exportName: + "PreProcessDeployedRuleGroupNames" + + deployHash, + }); + + new cdk.CfnOutput(scope, "PreProcessDeployedRuleGroupCapacities", { + value: + processRuntimeProps.DeployedRuleGroupCapacities.toString(), + description: "PreProcessDeployedRuleGroupCapacities", + exportName: + "PreProcessDeployedRuleGroupCapacities" + + deployHash, + }); + + new cdk.CfnOutput(scope, "PreProcessDeployedRuleGroupIdentifier", { + value: + processRuntimeProps.DeployedRuleGroupIdentifier.toString(), + description: "PreProcessDeployedRuleGroupIdentifier", + exportName: + "PreProcessDeployedRuleGroupIdentifier" + + deployHash, + }); } - + return serviceDataRuleGroup; } \ No newline at end of file diff --git a/lib/tools/camel-case.ts b/lib/tools/camel-case.ts deleted file mode 100644 index be7d4423..00000000 --- a/lib/tools/camel-case.ts +++ /dev/null @@ -1,39 +0,0 @@ -export function toCamel(o: any) { - let newO: any, origKey: any, newKey: any, value: any - if (o instanceof Array) { - return o.map(function(value) { - if (typeof value === "object") { - value = toCamel(value) - } - if(value === "aRN"){ - value = "arn" - } - if(value === "iPSetReferenceStatement"){ - value = "ipSetReferenceStatement" - } - return value - }) - } else { - newO = {} - for (origKey in o) { - if (Object.prototype.hasOwnProperty.call(o, origKey)) { - newKey = (origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey).toString() - if(newKey === "aRN"){ - newKey = "arn" - } - if(newKey === "iPSetReferenceStatement"){ - newKey = "ipSetReferenceStatement" - } - value = o[origKey] - if (value instanceof Array || (value !== null && value.constructor === Object)) { - value = toCamel(value) - if(value === "aRN"){ - value = "arn" - } - } - newO[newKey] = value - } - } - } - return newO -} \ No newline at end of file diff --git a/lib/tools/config-validator.ts b/lib/tools/config-validator.ts index d39df9b5..60cae837 100644 --- a/lib/tools/config-validator.ts +++ b/lib/tools/config-validator.ts @@ -1,4 +1,4 @@ -import Ajv, {JSONSchemaType} from "ajv" +import Ajv, {JSONSchemaType} from "ajv"; import {Config} from "../types/config"; import { resolve } from "path"; import * as TJS from "typescript-json-schema"; diff --git a/lib/tools/helpers.ts b/lib/tools/helpers.ts new file mode 100644 index 00000000..bb3fa276 --- /dev/null +++ b/lib/tools/helpers.ts @@ -0,0 +1,533 @@ +import { WAFV2Client, CheckCapacityCommand, CheckCapacityCommandInput, DescribeManagedRuleGroupCommand, DescribeManagedRuleGroupCommandInput } from "@aws-sdk/client-wafv2"; +import * as quota from "@aws-sdk/client-service-quotas"; +import * as cloudformation from "@aws-sdk/client-cloudformation"; +import { FMSClient, ListPoliciesCommand, ListPoliciesCommandInput } from "@aws-sdk/client-fms"; +import { Rule } from "../types/fms"; +import * as lodash from "lodash"; +import { RuntimeProperties } from "../types/runtimeprops"; +import { Config } from "../types/config"; + +/** + * Service Quota Code for Firewall Manager Total WAF WCU in account & region + */ +const WCU_QUOTA_CODE = "L-D86ED2F3"; + +/** + * Service Quota Code for Firewall Manager policies per organization per Region + */ +const POLICY_QUOTA_CODE = "L-0B28E140"; + +/** + * Get the current count of security policies in the deployment account and region + * @param deploymentRegion + * @returns A promise with the current policy count + */ +async function getPolicyCount(deploymentRegion: string): Promise { + const client = new FMSClient({ region: deploymentRegion }); + const input: ListPoliciesCommandInput = { + }; + const command = new ListPoliciesCommand(input); + const response = await client.send(command); + return response.PolicyList?.length || 0; +} + +/** + * + * @param deploymentRegion AWS region, e.g. eu-central-1 + * @param scope whether scope is REGIONAL or CLOUDFRONT + * @param rules rules for which you want to calculate the capacity + * @returns the total capacity of the supplied rules + */ +async function getTotalCapacityOfRules(deploymentRegion: string, scope: "REGIONAL" | "CLOUDFRONT", rules: Rule[]): Promise { + const client = new WAFV2Client({ region: deploymentRegion }); + const input: CheckCapacityCommandInput = { + Scope: scope, + Rules: convertPropValuesToUint8Array(rules, "SearchString") + }; + const command = new CheckCapacityCommand(input); + const response : any = await client.send(command); + return response.Capacity || 0; +} + +/** + * + * @param deploymentRegion AWS region, e.g. eu-central-1 + * @param quotaCode AWS Quota Code for the FMS Service Quota + * @returns returns the specified quota of the FMS Service + */ +async function getFmsQuota(deploymentRegion: string, quotaCode: string): Promise{ + let current_quota = 0; + const quoata_client = new quota.ServiceQuotasClient({ region: deploymentRegion }); + const input: quota.GetAWSDefaultServiceQuotaCommandInput = { + QuotaCode: quotaCode, + ServiceCode: "fms" + }; + const command = new quota.GetAWSDefaultServiceQuotaCommand(input); + const responsequoata = await quoata_client.send(command); + if(responsequoata.Quota?.Adjustable === true){ + const input: quota.ListRequestedServiceQuotaChangeHistoryByQuotaCommandInput = { + QuotaCode: quotaCode, + ServiceCode: "fms" + }; + const command = new quota.ListRequestedServiceQuotaChangeHistoryByQuotaCommand(input); + const newquota = await quoata_client.send(command); + if(newquota.RequestedQuotas !== []){ + if(newquota.RequestedQuotas?.length || 0 === 0){ + const sortquota = lodash.sortBy(newquota.RequestedQuotas,["Created"]); + if(sortquota?.length === 1){ + if(sortquota?.[0].Status !== "APPROVED"){ + console.log("โ„น๏ธ There is an open Quota request for " + quotaCode + " but it is still not approved using DEFAULT Quota."); + current_quota = responsequoata.Quota?.Value || 0; + return current_quota; + } + if(sortquota?.[0].Status === "APPROVED"){ + current_quota = sortquota?.[0].DesiredValue || 0; + return current_quota; + } + } + } + else{ + current_quota = responsequoata.Quota?.Value || 0; + return current_quota; + } + } + else{ + current_quota = responsequoata.Quota?.Value || 0; + return current_quota; + } + } + current_quota = responsequoata.Quota?.Value || 0; + return current_quota; +} + +/** + * + * @param deploymentRegion AWS region, e.g. eu-central-1 + * @param vendor vendor of the Managed Rule Group + * @param rgName vame of the Managed Rule Group + * @param scope whether scope is REGIONAL or CLOUDFRONT + * @param version version of the Managed Rule Group + * @returns returns the capacity of the Managed Rule Group + */ +async function getManagedRuleCapacity(deploymentRegion: string, vendor: string, rgName: string, scope: string, version: string): Promise{ + const client = new WAFV2Client({ region: deploymentRegion }); + if(version === ""){ + const input: DescribeManagedRuleGroupCommandInput = { + VendorName: vendor, + Name: rgName, + Scope: scope + }; + const command = new DescribeManagedRuleGroupCommand(input); + const response: any = await client.send(command); + return response.Capacity || 0; + } + else{ + const input: DescribeManagedRuleGroupCommandInput = { + VendorName: vendor, + Name: rgName, + Scope: scope, + VersionName: version + }; + const command = new DescribeManagedRuleGroupCommand(input); + const response : any = await client.send(command); + return response.Capacity || 0; + } +} + +/** + * Writes outputs from an existing stack into the specified runtime props + * @param deploymentRegion AWS region, e.g. eu-central-1 + * @param runtimeprops runtime properties, where to write stack outputs into + * @param config the config object from the values json + */ +export async function setOutputsFromStack(deploymentRegion: string, runtimeprops: RuntimeProperties, config: Config): Promise{ + const StackName = + config.General.Prefix.toUpperCase() + + "-WAF-" + + config.WebAcl.Name.toUpperCase() + + "-" + + config.General.Stage.toUpperCase() + + "-" + + config.General.DeployHash.toUpperCase(); + + const cloudformation_client = new cloudformation.CloudFormationClient({ region: deploymentRegion }); + const params ={ + StackName + }; + const command = new cloudformation.DescribeStacksCommand(params); + const responsestack = await cloudformation_client.send(command); + if(responsestack.Stacks?.[0].StackName && responsestack.Stacks?.[0].Outputs !== undefined){ + for(const output of responsestack.Stacks?.[0].Outputs){ + if(output.OutputKey === "DeployedRuleGroupNames") + { + runtimeprops.PreProcess.DeployedRuleGroupNames = output.OutputValue?.split(",",output.OutputValue?.length) || []; + } + else if(output.OutputKey === "DeployedRuleGroupIdentifier") + { + runtimeprops.PreProcess.DeployedRuleGroupIdentifier = output.OutputValue?.split(",",output.OutputValue?.length) || []; + } + else if(output.OutputKey === "DeployedRuleGroupCapacities") + { + const arrayOfNumbers = output.OutputValue?.split(",",output.OutputValue?.length).map(Number) || []; + runtimeprops.PreProcess.DeployedRuleGroupCapacities = arrayOfNumbers; + } + if(output.OutputKey === "PreProcessDeployedRuleGroupNames") + { + runtimeprops.PreProcess.DeployedRuleGroupNames = output.OutputValue?.split(",",output.OutputValue?.length) || []; + } + else if(output.OutputKey === "PreProcessDeployedRuleGroupIdentifier") + { + runtimeprops.PreProcess.DeployedRuleGroupIdentifier = output.OutputValue?.split(",",output.OutputValue?.length) || []; + } + else if(output.OutputKey === "PreProcessDeployedRuleGroupCapacities") + { + const arrayOfNumbers = output.OutputValue?.split(",",output.OutputValue?.length).map(Number) || []; + runtimeprops.PreProcess.DeployedRuleGroupCapacities = arrayOfNumbers; + } + if(output.OutputKey === "PostProcessDeployedRuleGroupNames") + { + runtimeprops.PostProcess.DeployedRuleGroupNames = output.OutputValue?.split(",",output.OutputValue?.length) || []; + } + else if(output.OutputKey === "PostProcessDeployedRuleGroupIdentifier") + { + runtimeprops.PostProcess.DeployedRuleGroupIdentifier = output.OutputValue?.split(",",output.OutputValue?.length) || []; + } + else if(output.OutputKey === "PostProcessDeployedRuleGroupCapacities") + { + const arrayOfNumbers = output.OutputValue?.split(",",output.OutputValue?.length).map(Number) || []; + runtimeprops.PostProcess.DeployedRuleGroupCapacities = arrayOfNumbers; + } + } + } +} + +/** + * calculate the capacities for managed and custom rules and apply them to runtime properties + * @param config configuration object of the values.json + * @param deploymentRegion AWS region, e.g. eu-central-1 + * @param runtimeProperties runtime properties object, where to store capacities + */ +async function calculateCapacities( + config: Config, + deploymentRegion: string, + runtimeProperties: RuntimeProperties +): Promise { + + let count = 0; + if (!config.WebAcl.PreProcess.CustomRules) { + 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) { + const rules : Rule[] = []; + const { CloudWatchMetricsEnabled, SampledRequestsEnabled } = + config.WebAcl.PreProcess.CustomRules[count].VisibilityConfig; + const rule: Rule = { + Statement: config.WebAcl.PreProcess.CustomRules[count].Statement, + Name: "Rule", + Action: config.WebAcl.PreProcess.CustomRules[count].Action, + CaptchaConfig: + config.WebAcl.PreProcess.CustomRules[count].CaptchaConfig, + VisibilityConfig: { + CloudWatchMetricsEnabled, + SampledRequestsEnabled, + MetricName: "Metric" + Math.random().toString(), + }, + }; + rules.push(rule); + const capacity = await getTotalCapacityOfRules( + deploymentRegion, + config.WebAcl.Scope, + rules + ); + runtimeProperties.PreProcess.RuleCapacities.push(capacity); + } else { + const rule_calculated_capacity_json = []; + const { CloudWatchMetricsEnabled, SampledRequestsEnabled } = + config.WebAcl.PreProcess.CustomRules[count].VisibilityConfig; + const temp_template: Rule = { + Statement: config.WebAcl.PreProcess.CustomRules[count].Statement, + Name: "Rule", + Action: config.WebAcl.PreProcess.CustomRules[count].Action, + VisibilityConfig: { + CloudWatchMetricsEnabled, + SampledRequestsEnabled, + MetricName: "Metric" + Math.random().toString(), + }, + }; + rule_calculated_capacity_json.push(temp_template); + const capacity = await getTotalCapacityOfRules( + deploymentRegion, + config.WebAcl.Scope, + rule_calculated_capacity_json + ); + runtimeProperties.PreProcess.RuleCapacities.push(capacity); + } + count++; + } + runtimeProperties.PreProcess.Capacity = runtimeProperties.PreProcess.RuleCapacities.reduce( + function (a, b) { + return a + b; + }, + 0 + ); + } + count = 0; + let PostProcessCapacity = 0; + if (!config.WebAcl.PostProcess.CustomRules) { + console.log( + "\n โญ Skip Rule Capacity Calculation for PostProcess Custom Rules." + ); + } else { + while (count < config.WebAcl.PostProcess.CustomRules.length) { + const rule_calculated_capacity_json = []; + const { CloudWatchMetricsEnabled, SampledRequestsEnabled } = + config.WebAcl.PostProcess.CustomRules[count].VisibilityConfig; + const rule: Rule = { + Statement: config.WebAcl.PostProcess.CustomRules[count].Statement, + Name: "Rule", + Action: config.WebAcl.PostProcess.CustomRules[count].Action, + VisibilityConfig: { + CloudWatchMetricsEnabled, + SampledRequestsEnabled, + MetricName: "Metric" + Math.random().toString(), + }, + }; + if ("Captcha" in config.WebAcl.PostProcess.CustomRules[count].Action) { + rule.CaptchaConfig = + config.WebAcl.PostProcess.CustomRules[count].CaptchaConfig; + } + if (config.WebAcl.PostProcess.CustomRules[count].RuleLabels) { + rule.RuleLabels = + config.WebAcl.PostProcess.CustomRules[count].RuleLabels; + } + rule_calculated_capacity_json.push(rule); + const capacity = await getTotalCapacityOfRules( + deploymentRegion, + config.WebAcl.Scope, + rule_calculated_capacity_json + ); + runtimeProperties.PostProcess.RuleCapacities.push(capacity); + count++; + } + PostProcessCapacity = runtimeProperties.PostProcess.RuleCapacities.reduce( + function (a, b) { + return a + b; + }, + 0 + ); + } + console.log("\n๐Ÿ‘€ Get ManagedRule Capacity:\n"); + if (!config.WebAcl.PreProcess.ManagedRuleGroups) { + console.log("\n โ„น๏ธ No ManagedRuleGroups defined in PreProcess."); + } else { + console.log(" ๐Ÿฅ‡ PreProcess: "); + for (const managedrule of config.WebAcl.PreProcess.ManagedRuleGroups) { + const capacity = await getManagedRuleCapacity( + deploymentRegion, + managedrule.Vendor, + managedrule.Name, + config.WebAcl.Scope, + managedrule.Version + ); + managedrule.Capacity = capacity; + console.log( + " โž• Capacity for " + + managedrule.Name + + " is [" + + managedrule.Capacity + + "]" + ); + runtimeProperties.ManagedRuleCapacity += capacity; + } + } + if (!config.WebAcl.PostProcess.ManagedRuleGroups) { + console.log("\n โ„น๏ธ No ManagedRuleGroups defined in PostProcess."); + } else { + console.log("\n ๐Ÿฅˆ PostProcess: "); + for (const managedrule of config.WebAcl.PostProcess.ManagedRuleGroups) { + const capacity = await getManagedRuleCapacity( + deploymentRegion, + managedrule.Vendor, + managedrule.Name, + config.WebAcl.Scope, + managedrule.Version + ); + managedrule.Capacity = capacity; + console.log( + " โž• Capacity for " + + managedrule.Name + + " is [" + + managedrule.Capacity + + "]" + ); + runtimeProperties.ManagedRuleCapacity += capacity; + } + } + runtimeProperties.PostProcess.Capacity = PostProcessCapacity; +} + +/** + * The functiion calculates the current security policy count in the account & region and checks if exceeds the current quota + * @param deploymentRegion AWS region, e.g. eu-central-1 + * @returns whether policy limit is reached + */ +export async function isPolicyQuotaReached(deploymentRegion: string): Promise { + const policyCount = await getPolicyCount(deploymentRegion); + const fmsPolicyQuota = await getFmsQuota(deploymentRegion, POLICY_QUOTA_CODE); + const policyLimitReached = fmsPolicyQuota <= policyCount; + if (policyLimitReached) { + console.log( + "\n๐Ÿšจ You are about to exceed the limit for Policies per region.\n Region Quota: " + + fmsPolicyQuota + + "\n Deployed Policies: " + + policyCount + + "\n ๏น— Stopping deployment ๏น—" + ); + } + return policyLimitReached; +} + +/** + * The function checks if the total WCU of all configured rules exceeds the WCU quota in account & region + * @param deploymentRegion AWS region, e.g. eu-central-1 + * @param runtimeProps runtime properties object, where to store capacities + * @param config configuration object of the values.json + * @returns whether WCU limit is reached + */ +export async function isWcuQuotaReached(deploymentRegion: string, runtimeProps: RuntimeProperties, config: Config): Promise { + await calculateCapacities(config, deploymentRegion, runtimeProps); + const custom_capacity = runtimeProps.PreProcess.Capacity + runtimeProps.PostProcess.Capacity; + const total_wcu = runtimeProps.PreProcess.Capacity + runtimeProps.PostProcess.Capacity + runtimeProps.ManagedRuleCapacity; + const quote_wcu = await getFmsQuota(deploymentRegion, WCU_QUOTA_CODE); + const wcuLimitReached = (total_wcu > Number(quote_wcu)); + if (wcuLimitReached) { + console.log("\n๐Ÿ”Ž Capacity Check result: ๐Ÿ”ด \n ๏น— Stopping deployment ๏น—\n"); + console.log(" ๐Ÿ’ก Account WAF-WCU Quota: " +Number(quote_wcu).toString()); + console.log(" ๐Ÿงฎ Calculated Custom Rule Capacity is: [" + custom_capacity + "] \n โž• ManagedRulesCapacity: ["+ runtimeProps.ManagedRuleCapacity +"] \n ๏ผ Total Waf Capacity: " + total_wcu.toString() + "\n"); + } + else { + 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 + "] (๐Ÿฅ‡[" + runtimeProps.PreProcess.Capacity + "] + ๐Ÿฅˆ[" + runtimeProps.PostProcess.Capacity + "]) \n โž• ManagedRulesCapacity: ["+ runtimeProps.ManagedRuleCapacity +"] \n ๏ผ Total Waf Capacity: " + total_wcu.toString() + "\n"); + } + return wcuLimitReached; +} + +/** + * initialize a runtime properties object + * @returns the runtime properties object + */ +export function initRuntimeProperties() : RuntimeProperties { + return { + ManagedRuleCapacity: 0, + PostProcess: { + Capacity: 0, + DeployedRuleGroupCapacities: [], + DeployedRuleGroupIdentifier: [], + DeployedRuleGroupNames: [], + RuleCapacities: [] + }, + PreProcess: { + Capacity: 0, + DeployedRuleGroupCapacities: [], + DeployedRuleGroupIdentifier: [], + DeployedRuleGroupNames: [], + RuleCapacities: [] + }, + }; +} + +/** + * The function converts the value of all properties with supplied name into a Uint8Array + * @param rulesObject Rules Object or Array of Rules Object + * @param propertyName name of the properties which have to be converted + * @returns converted Rules + */ +function convertPropValuesToUint8Array(rulesObject: any, propertyName: string): any { + const convertedObject: any = {}; + let value: any; + if (rulesObject instanceof Array) { + return rulesObject.map(function (value) { + if (typeof value === "object") { + value = convertPropValuesToUint8Array(value, propertyName); + } + return value; + }); + } else { + for (const origKey in rulesObject) { + if (Object.prototype.hasOwnProperty.call(rulesObject,origKey)) { + value = rulesObject[origKey]; + if (value instanceof Array || (value !== null && value.constructor === Object)) { + value = convertPropValuesToUint8Array(value, propertyName); + } + if (origKey === propertyName) { + value = convertStringToUint8Array(rulesObject[origKey]); + } + convertedObject[origKey] = value; + } + } + } + return convertedObject; +} + +/** + * The function returns Uint8 representation of a string + * @param stringToConvert string which has to be converted to Uint8Array + * @returns the desired Uint8Array representation of the string + */ +function convertStringToUint8Array(stringToConvert: string): Uint8Array { + const buf = new ArrayBuffer(stringToConvert.length * 2); // 2 bytes for each char + const bufView = new Uint8Array(buf); + for (let i = 0, strLen = stringToConvert.length; i < strLen; i++) { + bufView[i] = stringToConvert.charCodeAt(i); + } + return bufView; +} + +/** + * Function to transform property names into camel case like AWS needs it + * @param o object which property names has to be transformed to camel case + * @returns the object with the transformed property names in camel case + */ +export function toAwsCamel(o: any): any { + let newO: any, origKey: any, newKey: any, value: any; + if (o instanceof Array) { + return o.map(function(value) { + if (typeof value === "object") { + value = toAwsCamel(value); + } + if(value === "aRN"){ + value = "arn"; + } + if(value === "iPSetReferenceStatement"){ + value = "ipSetReferenceStatement"; + } + return value; + }); + } else { + newO = {}; + for (origKey in o) { + if (Object.prototype.hasOwnProperty.call(o, origKey)) { + newKey = (origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey).toString(); + if(newKey === "aRN"){ + newKey = "arn"; + } + if(newKey === "iPSetReferenceStatement"){ + newKey = "ipSetReferenceStatement"; + } + value = o[origKey]; + if (value instanceof Array || (value !== null && value.constructor === Object)) { + value = toAwsCamel(value); + if(value === "aRN"){ + value = "arn"; + } + } + newO[newKey] = value; + } + } + } + return newO; +} \ No newline at end of file diff --git a/lib/types/config.ts b/lib/types/config.ts index 4fc4eef8..c64b2459 100644 --- a/lib/types/config.ts +++ b/lib/types/config.ts @@ -1,11 +1,4 @@ -interface RulesArray{ - Name?: string, - Statement: any, - Action: any, - VisibilityConfig: any, - CaptchaConfig?: any, -} - +import { Rule, ManagedRuleGroup } from "./fms"; export interface Config { readonly General: { readonly Prefix: string, @@ -18,25 +11,14 @@ export interface Config { }, readonly WebAcl:{ readonly Name: string, - readonly Scope: string, + readonly Scope: "CLOUDFRONT" | "REGIONAL", readonly Type: string, - readonly PreProcess: { - CustomRules?: Array | undefined, - ManagedRuleGroups?: any[] | undefined; - } - readonly PostProcess:{ - CustomRules?: Array | undefined, - ManagedRuleGroups?: any[] | undefined; - } + readonly PreProcess: RuleGroupSet, + readonly PostProcess: RuleGroupSet }, } - -interface RulesArray{ - Name?: string, - Statement: any, - Action: any, - VisibilityConfig: any, - CaptchaConfig?: any, - RuleLabels?: any +export interface RuleGroupSet { + CustomRules?: Rule[], + ManagedRuleGroups?: ManagedRuleGroup[]; } \ No newline at end of file diff --git a/lib/types/fms.ts b/lib/types/fms.ts new file mode 100644 index 00000000..de3c5629 --- /dev/null +++ b/lib/types/fms.ts @@ -0,0 +1,79 @@ +export interface ManagedRuleGroup { + Vendor: string, + Name: string, + Version: string, + Capacity: number, + ExcludeRules?: NameObject[], + OverrideAction?: { + type: "COUNT" | "NONE" + } +} + +export interface Rule { + Name?: string, + Statement: any, + Action: Action, + VisibilityConfig: { + SampledRequestsEnabled: boolean, + CloudWatchMetricsEnabled: boolean, + MetricName?: string + }, + CaptchaConfig?: { + ImmunityTimeProperty?: { + ImmunityTime: number + } + }, + RuleLabels?: NameObject[] +} + +export interface ManagedServiceData { + type: string, + defaultAction: { + type: "ALLOW" | "DENY" | "COUNT" | "NONE" + }, + preProcessRuleGroups: any, + postProcessRuleGroups: any, + overrideCustomerWebACLAssociation: boolean, + loggingConfiguration: { + logDestinationConfigs: string[] + } +} + +type NameObject = { + Name: string +} + +export interface ServiceDataManagedRuleGroup extends ServiceDataAbstactRuleGroup { + managedRuleGroupIdentifier: { + vendorName: string, + managedRuleGroupName: string, + version: string | null, + }, + excludeRules: any, + ruleGroupType: "ManagedRuleGroup", +} + +export interface ServiceDataRuleGroup extends ServiceDataAbstactRuleGroup { + ruleGroupType: "RuleGroup" +} + +interface ServiceDataAbstactRuleGroup { + overrideAction: { + type: "ALLOW" | "DENY" | "NONE" | "COUNT" + }, + ruleGroupArn: string | null, + ruleGroupType: string +} + +type Action = | { + Block: Record +} +| { + Allow: Record +} +| { + Count: Record +} +| { + Captcha: Record +} \ No newline at end of file diff --git a/lib/types/runtimeprops.ts b/lib/types/runtimeprops.ts index 7f08e2af..f6ac8b99 100644 --- a/lib/types/runtimeprops.ts +++ b/lib/types/runtimeprops.ts @@ -1,12 +1,13 @@ -export interface Runtimeprops { - PreProcessCapacity: number, - PostProcessCapacity: number, - PreProcessRuleCapacities: number[], - PostProcessRuleCapacities: number[], - PreProcessDeployedRuleGroupCapacities: number[], - PreProcessDeployedRuleGroupNames: string[], - PreProcessDeployedRuleGroupIdentifier: string[], - PostProcessDeployedRuleGroupCapacities: number[], - PostProcessDeployedRuleGroupNames: string[], - PostProcessDeployedRuleGroupIdentifier: string[], +export interface RuntimeProperties { + PreProcess: ProcessProperties, + PostProcess: ProcessProperties, + ManagedRuleCapacity: number +} + +export interface ProcessProperties { + Capacity: number, + RuleCapacities: number[], + DeployedRuleGroupCapacities: number[], + DeployedRuleGroupNames: string[], + DeployedRuleGroupIdentifier: string[] } \ No newline at end of file diff --git a/package.json b/package.json index 2b95ef83..9bf422fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plattform-wafv2-cdk-automation", - "version": "2.0.0", + "version": "2.1.0", "bin": { "plattform-wafv2-cdk-automation": "bin/plattform-wafv2-cdk-automation.js" }, @@ -27,12 +27,13 @@ "typescript": "~3.9.7" }, "dependencies": { - "@aws-sdk/client-cloudformation": "^3.40.0", - "@aws-sdk/client-fms": "^3.43.0", - "@aws-sdk/client-service-quotas": "^3.38.0", - "@aws-sdk/client-wafv2": "^3.48.0", + "@aws-sdk/client-cloudformation": "^3.52.0", + "@aws-sdk/client-fms": "^3.52.0", + "@aws-sdk/client-service-quotas": "^3.52.0", + "@aws-sdk/client-wafv2": "^3.52.0", "@mhlabs/cfn-diagram": "^1.1.32", "@types/lodash": "^4.14.178", + "ajv": "^8.10.0", "aws-cdk-lib": "^2.8.0", "constructs": "^10.0.0", "lodash": "^4.17.21", diff --git a/values/calculatecapacity.json b/values/calculatecapacity.json deleted file mode 100644 index 749981e2..00000000 --- a/values/calculatecapacity.json +++ /dev/null @@ -1,16 +0,0 @@ -{ -"Name": "TEST", -"Priority": 0, -"Statement": { -}, -"Action":{ -}, -"CaptchaConfig":{ -}, -"VisibilityConfig": { - "SampledRequestsEnabled": false, - "CloudWatchMetricsEnabled": false, - "MetricName": "TEST" -}, -"RuleLabels": [] -}