diff --git a/CHANGELOG.md b/CHANGELOG.md index 09418c3a..d19d6965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,15 @@ # Change Log ## Released +## 3.3.0 +### Added +- Refactor of `bin/aws-firewall-factory.ts`, grouping duplicated code on a function, adding comments and better organizing the file. +- Refactor of `lib/firewall-stack.ts`, outsource the creation of the CloudWatch Dashboard into an own Construct + +- Adds a centralized IPSets management feature. + No more we'll have to be manually updating ipsets across multiple AWS accounts, it can be defined in code and replicated for use by WAF rules everywhere its needed. Check the examples for defining ipsets and using them in the WebACLs on `values/ip-sets-managed.json`. +- Logging to S3, you can now decide if you want to send your WAF logs directly to S3 or via Firehose ## 3.2.6 ### Add - Linting Github Action for typescript 18 & 20 @@ -24,7 +32,7 @@ ## 3.2.4 ### Fixed - Update TestCases for WAF Testing - - community-user-agent testcases + - community-user-agent testcases - improve owasp testcases - Update Testing bin Version @@ -43,6 +51,7 @@ ### Added - Added Linting command `lint` to npm scripts which can be run via `npm run lint`โ—Š + ## 3.2.2 ### Fixed - Bump @aws-sdk/client-pricing from 3.332.0 to 3.341.0 @@ -50,6 +59,7 @@ - Bump @aws-sdk/client-cloudwatch from 3.321.1 to 3.341.0 - Bump @aws-sdk/client-service-quotas from 3.321.1 to 3.342.0 - Bump @aws-sdk/client-fms from 3.332.0 to 3.342.0 + ## 3.2.1 ### Fixed - Bump aws-cdk from 2.74.0 to 2.79.1ย  @@ -61,6 +71,7 @@ - Bump @typescript-eslint/parser from 4.32.0 to 4.4.0 - Bump eslint-config-standard from 16.0.3 to 17.0.0 - Bump eslint from 7.32.0 to 8.4.0 + ## 3.2.0 ### Fixed - conflict peer dependency on package.json diff --git a/README.md b/README.md index d43c96ec..bbc37703 100644 --- a/README.md +++ b/README.md @@ -138,9 +138,12 @@ If you want to learn more about the AWS Firewall Factory feel free to look at th See example: ![FirewallDashboard](./static/FirewallDashboard.jpg) +21. Centralized IPSets management - No more we'll have to be manually updating ipsets across multiple AWS accounts, it can be defined in code and replicated for use by WAF rules everywhere its needed. Check the examples for defining ipsets and using them in the WebACLs on `values/ip-sets-managed.json`. + + ## ๐Ÿ›ก๏ธ Deployment
diff --git a/bin/aws-firewall-factory.ts b/bin/aws-firewall-factory.ts index 6a71693e..28022841 100644 --- a/bin/aws-firewall-factory.ts +++ b/bin/aws-firewall-factory.ts @@ -1,155 +1,120 @@ #!/usr/bin/env node -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { FirewallStack } from "../lib/firewall-stack"; import { PrerequisitesStack } from "../lib/prerequisites-stack"; import * as cdk from "aws-cdk-lib"; import { realpathSync, existsSync } from "fs"; -import { validatewaf, validateprerequisites } from "../lib/tools/config-validator"; +import { validateWaf, validatePrerequisites, wrongLoggingConfiguration } from "../lib/tools/config-validator"; import { Config, Prerequisites, PriceRegions, RegionString } from "../lib/types/config"; -import { isPolicyQuotaReached, isWcuQuotaReached, setOutputsFromStack, initRuntimeProperties } from "../lib/tools/helpers"; +import { isPolicyQuotaReached, isWcuQuotaReached, setOutputsFromStack, initRuntimeProperties, outputInfoBanner } from "../lib/tools/helpers"; import {isPriceCalculated, getCurrentPrices} from "../lib/tools/price-calculator"; -import * as packageJsonObject from "../package.json"; - - -/** - * Version of the AWS Firewall Factory - extracted from package.json - */ -const FIREWALL_FACTORY_VERSION = packageJsonObject.version; - +import { ValidateFunction } from "ajv"; /** * relative path to config file imported from the env PROCESS_PARAMETERS */ const CONFIGFILE = process.env.PROCESS_PARAMETERS; -/** - * the region into which the stack is deployed - */ -let deploymentRegion = ""; +const logInvalidConfigFileAndExit = (config:Config,invalidFileType: "ConfigFile" | "IPSet", validationFilePath: string, ajvValidatorFunction: ValidateFunction): void => { + outputInfoBanner(config); + console.log(`\n ๐Ÿงช Validation of your ${invalidFileType}: \n ๐Ÿ“‚ ` + validationFilePath + "\n\n"); + console.error("\u001B[31m",`๐Ÿšจ Invalid ${invalidFileType} File ๐Ÿšจ \n\n`,"\x1b[0m" + JSON.stringify(ajvValidatorFunction.errors, null, 2)+ "\n\n"); + process.exit(1); +}; + +if(!CONFIGFILE || !existsSync(CONFIGFILE)) { + console.log("Config file ", CONFIGFILE, " not found. - NO CDK ERROR"); + process.exit(1); +} -if (CONFIGFILE && existsSync(CONFIGFILE)) { - // eslint-disable-next-line @typescript-eslint/no-var-requires +void (async () => { + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment const config: Config = require(realpathSync(CONFIGFILE)); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const prerequisites: Prerequisites = require(realpathSync(CONFIGFILE)); - if(process.env.PREREQUISITE === "true"){ - if(validateprerequisites(prerequisites)){ - // eslint-disable-next-line @typescript-eslint/require-await - void (async () => { - console.log(` - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ - โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• - `); - console.log("\x1b[36m","\n by globaldatanet","\x1b[0m"); - console.log("\n๐Ÿท Version: ","\x1b[4m",FIREWALL_FACTORY_VERSION,"\x1b[0m"); - console.log("๐Ÿ‘ค AWS Account used: ","\x1b[33m","\n " + process.env.CDK_DEFAULT_ACCOUNT,"\x1b[0m"); - console.log("๐ŸŒŽ CDK deployment region:","\x1b[33m","\n "+process.env.AWS_REGION,"\x1b[0m \n"); + if (!validateWaf(config)) logInvalidConfigFileAndExit(config,"ConfigFile", realpathSync(CONFIGFILE), validateWaf); - console.log("โ„น๏ธ Deploying Prequisites Stack."); - const app = new cdk.App(); - new PrerequisitesStack(app, config.General.Prefix.toUpperCase() + "-AWS-FIREWALL-FACTORY-PREQUISITES", { - prerequisites, - env: { - region: process.env.AWS_REGION, - account: process.env.CDK_DEFAULT_ACCOUNT, - }, - }); - })();} - else { - console.log(` - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ - โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• - `); - 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(validateprerequisites.errors, null, 2)+ "\n\n"); - process.exit(1); - } + // --------------------------------------------------------------------- + // Deploying prerequisite stack + + if(process.env.PREREQUISITE === "true") { + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment + const prerequisites: Prerequisites = require(realpathSync(CONFIGFILE)); + if(!validatePrerequisites(prerequisites)) logInvalidConfigFileAndExit(config,"ConfigFile", realpathSync(CONFIGFILE), validatePrerequisites); + + outputInfoBanner(config); + + console.log("โ„น๏ธ Deploying Prerequisites Stack."); + const app = new cdk.App(); + new PrerequisitesStack(app, config.General.Prefix.toUpperCase() + "-AWS-FIREWALL-FACTORY-PREQUISITES", { + prerequisites, + env: { + region: process.env.AWS_REGION, + account: process.env.CDK_DEFAULT_ACCOUNT, + }, + }); } - else{ - if (validatewaf(config)){ - if(config.WebAcl.Scope === "CLOUDFRONT"){ - deploymentRegion = "us-east-1"; - } - else{ - deploymentRegion = process.env.REGION || "eu-central-1"; - } - console.log(` - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ - โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• - `); - console.log("\x1b[36m","\n by globaldatanet","\x1b[0m"); - console.log("\n๐Ÿท Version: ","\x1b[4m",FIREWALL_FACTORY_VERSION,"\x1b[0m"); - console.log("๐Ÿ‘ค AWS Account used: ","\x1b[33m","\n " + process.env.CDK_DEFAULT_ACCOUNT,"\x1b[0m"); - console.log("๐ŸŒŽ CDK deployment region:","\x1b[33m","\n "+deploymentRegion,"\x1b[0m \n"); - void (async () => { - 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); - } - } - } - else{ - await setOutputsFromStack(deploymentRegion, runtimeProperties, config); - 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: ","\u001b[32m",config.General.Prefix.toUpperCase() + "-WAF-" + config.WebAcl.Name.toUpperCase() +"-"+config.General.Stage.toUpperCase() +"-"+config.General.DeployHash.toUpperCase(),"\u001b[0m"); - 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"); + + // --------------------------------------------------------------------- + // Deploying Firewall stack + + else { + const deploymentRegion= outputInfoBanner(config); + 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","๐Ÿšจ ERROR: Exit process due Quota Check for Policies ๐Ÿšจ \n\n","\x1b[0m" + "\n\n"); process.exit(1); } - const app = new cdk.App(); - new FirewallStack(app, config.General.Prefix.toUpperCase() + "-WAF-" + config.WebAcl.Name.toUpperCase() +"-"+config.General.Stage.toUpperCase() +"-"+config.General.DeployHash.toUpperCase(), { - config, runtimeProperties: runtimeProperties, - env: { - region: deploymentRegion, - account: process.env.CDK_DEFAULT_ACCOUNT, - }, - }); - await getCurrentPrices(PriceRegions[deploymentRegion as RegionString], runtimeProperties, config,deploymentRegion); - await isPriceCalculated(runtimeProperties); - })(); - } else { - console.log(` - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•— โ•šโ–ˆโ–ˆโ•”โ• - โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ - โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• - `); - 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(validatewaf.errors, null, 2)+ "\n\n"); + } + } + else{ + await setOutputsFromStack(deploymentRegion, runtimeProperties, config); + 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: ","\u001b[32m",config.General.Prefix.toUpperCase() + "-WAF-" + config.WebAcl.Name.toUpperCase() +"-"+config.General.Stage.toUpperCase() +"-"+config.General.DeployHash.toUpperCase(),"\u001b[0m"); + + console.log("\n ๐Ÿ“‘ Logging:"); + if(config.General.LoggingConfiguration ==="Firehose"){ + console.log(" ๐Ÿงฏ " + config.General.LoggingConfiguration); + console.log(" โš™๏ธ [" + config.General.S3LoggingBucketName +"]"); + } + if(config.General.LoggingConfiguration ==="S3"){ + console.log(" ๐Ÿชฃ " + config.General.LoggingConfiguration); + console.log(" โš™๏ธ [" + config.General.S3LoggingBucketName +"]"); + } + if(Array.isArray(config.WebAcl.IPSets) && config.WebAcl.IPSets.length > 0) { + console.log("\n๐‚ IPSets"); + for(const ipSet of config.WebAcl.IPSets) { + console.log(" โž• " + ipSet.Name); + console.log(" โš™๏ธ [" + ipSet.IPAddressVersion + "] | ๐ŸŒŽ [" + config.WebAcl.Scope+ "]"); + } + } + const wcuQuotaReached = await isWcuQuotaReached(deploymentRegion, runtimeProperties, config); + if(wcuQuotaReached) { + console.error("\u001B[31m","๐Ÿšจ ERROR: Exit process due Quota Check for WCU ๐Ÿšจ \n\n","\x1b[0m" + "\n\n"); process.exit(1); } - } -} -else { - console.log("File", CONFIGFILE, "not found. - NO CDK ERROR"); -} + if(wrongLoggingConfiguration(config)){ + console.error("\u001B[31m"," ๐Ÿšจ ERROR: Amazon S3 bucket name is invalid ๐Ÿšจ ", "\x1b[0m" +"\n ๐Ÿชฃ Amazon S3 bucket name must begin with \"aws-waf-logs-\" followed by at least one \n of the following characters [a-z0-9_.-]\n\n","\x1b[0m" + "\n\n"); + process.exit(1); + } + const app = new cdk.App(); + new FirewallStack(app, config.General.Prefix.toUpperCase() + "-WAF-" + config.WebAcl.Name.toUpperCase() +"-"+config.General.Stage.toUpperCase() +"-"+config.General.DeployHash.toUpperCase(), { + config, runtimeProperties: runtimeProperties, + env: { + region: deploymentRegion, + account: process.env.CDK_DEFAULT_ACCOUNT + }, + }); + await getCurrentPrices(PriceRegions[deploymentRegion as RegionString], runtimeProperties, config,deploymentRegion); + await isPriceCalculated(runtimeProperties); + } +})(); \ No newline at end of file diff --git a/lib/constructs/cloudwatch.ts b/lib/constructs/cloudwatch.ts new file mode 100644 index 00000000..991772fd --- /dev/null +++ b/lib/constructs/cloudwatch.ts @@ -0,0 +1,231 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/restrict-plus-operands */ +import { aws_cloudwatch as cloudwatch } from "aws-cdk-lib"; +import * as packageJsonObject from "../../package.json"; +import * as cdk from "aws-cdk-lib"; +import { Config } from "../types/config"; +import { Construct } from "constructs"; + +const REGION = cdk.Aws.REGION; + +/** + * Version of the AWS Firewall Factory - extracted from package.json + */ +const FIREWALL_FACTORY_VERSION = packageJsonObject.version; + +export class WafCloudWatchDashboard extends Construct { + + constructor(scope: Construct, id: string, config: Config,managedRuleGroupsInfo:string[]) { + super(scope, id); + console.log("\n๐ŸŽจ Creating central CloudWatch Dashboard \n ๐Ÿ“Š DashboardName: ","\u001b[32m", config.General.Prefix.toUpperCase() + +"-" + +config.WebAcl.Name + +"-" + +config.General.Stage + +"-" + +config.General.DeployHash,"\u001b[0m"); + console.log(" โ„น๏ธ Warnings for Math expressions can be ignored."); + const cwdashboard = new cloudwatch.Dashboard(this, "dashboard", { + dashboardName: config.General.Prefix.toUpperCase() + + "-" + + config.WebAcl.Name + + "-" + + config.General.Stage + + "-" + + config.General.DeployHash, + periodOverride: cloudwatch.PeriodOverride.AUTO, + start: "-PT24H" + }); + const webaclName = config.General.Prefix.toUpperCase() + +"-" + +config.WebAcl.Name + +"-" + +config.General.Stage + +"-" + +config.General.DeployHash; + const webaclNamewithPrefix = "FMManagedWebACLV2-" + config.General.Prefix.toUpperCase() + +"-" + +config.WebAcl.Name + +"-" + +config.General.Stage + +"-" + +config.General.DeployHash; + + if(config.WebAcl.IncludeMap.account){ + const infowidget = new cloudwatch.TextWidget({ + markdown: "# ๐Ÿ”ฅ "+webaclName+"\n + ๐Ÿ— Deployed to: \n\n ๐Ÿ“ฆ Accounts: "+config.WebAcl.IncludeMap.account.toString() + "\n\n ๐ŸŒŽ REGION: " + REGION + "\n\n ๐Ÿ’ก Type: " + config.WebAcl.Type, + width: 14, + height: 4 + }); + + const securedDomain = config.General.SecuredDomain.toString(); + + const app = new cloudwatch.TextWidget({ + markdown: "โš™๏ธ Used [ManagedRuleGroups](https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups.html):\n" + managedRuleGroupsInfo.toString().replace(/,/g,"\n - ") + "\n\n--- \n\n\nโ„น๏ธ Link to your secured [Application]("+securedDomain+")", + width: 7, + height: 4 + }); + let fwmessage = ""; + if(process.env.LASTEST_FIREWALLFACTORY_VERSION !== FIREWALL_FACTORY_VERSION){ + fwmessage = "๐Ÿšจ old or beta version"; + } + else{ + fwmessage = "๐Ÿ’š latest version"; + } + const fwfactory = new cloudwatch.TextWidget({ + markdown: "**AWS FIREWALL FACTORY** \n\n ![Image](https://github.com/globaldatanet/aws-firewall-factory/raw/master/static/icon/firewallfactory.png) \n\n ๐Ÿท Version: [" + FIREWALL_FACTORY_VERSION + "](https://github.com/globaldatanet/aws-firewall-factory/releases/tag/" + FIREWALL_FACTORY_VERSION + ") \n" + fwmessage, + width: 3, + height: 4 + }); + const firstrow = new cloudwatch.Row(infowidget,app,fwfactory); + cwdashboard.addWidgets(firstrow); + for(const account of config.WebAcl.IncludeMap.account){ + // eslint-disable-next-line no-useless-escape + const countexpression = "SEARCH('{AWS\/WAFV2,\REGION,\WebACL,\Rule} \WebACL="+webaclNamewithPrefix+" \MetricName=\"\CountedRequests\"', '\Sum', 300)"; + + const countedRequests = new cloudwatch.GraphWidget({ + title: "๐Ÿ”ข Counted Requests in " + account, + width: 8, + height: 8 + }); + countedRequests.addLeftMetric( + new cloudwatch.MathExpression({ + expression: countexpression, + usingMetrics: {}, + label: "CountedRequests", + searchAccount: account, + searchRegion: REGION, + color: "#9dbcd4" + })); + // eslint-disable-next-line no-useless-escape + const blockedexpression = "SEARCH('{AWS\/WAFV2,\REGION,\WebACL,\Rule} \WebACL="+webaclNamewithPrefix+" \MetricName=\"\BlockedRequests\"', '\Sum', 300)"; + const blockedRequests = new cloudwatch.GraphWidget({ + title: "โŒ Blocked Requests in " + account, + width: 8, + height: 8 + }); + blockedRequests.addLeftMetric( + new cloudwatch.MathExpression({ + expression: blockedexpression, + usingMetrics: {}, + label: "BlockedRequests", + searchAccount: account, + searchRegion: REGION, + color: "#ff0000" + })); + // eslint-disable-next-line no-useless-escape + const allowedexpression = "SEARCH('{AWS\/WAFV2,\REGION,\WebACL,\Rule} \WebACL="+webaclNamewithPrefix+" \MetricName=\"\AllowedRequests\"', '\Sum', 300)"; + const allowedRequests = new cloudwatch.GraphWidget({ + title: "โœ… Allowed Requests in " + account, + width: 8, + height: 8 + }); + allowedRequests.addLeftMetric( + new cloudwatch.MathExpression({ + expression: allowedexpression, + usingMetrics: {}, + label: "AllowedRequests", + searchAccount: account, + searchRegion: REGION, + color: "#00FF00" + })); + // eslint-disable-next-line no-useless-escape + const sinlevaluecountedrequestsexpression = "SEARCH('{AWS\/WAFV2,\Rule,\WebACL,\REGION} \WebACL="+webaclNamewithPrefix+" \MetricName=\"CountedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; + // eslint-disable-next-line no-useless-escape + const expression1 = "SEARCH('{AWS\/WAFV2,\Rule,\WebACL,\REGION} \WebACL="+webaclNamewithPrefix+" \MetricName=\"AllowedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; + // eslint-disable-next-line no-useless-escape + const expression2 = "SEARCH('{AWS\/WAFV2,\Rule,\WebACL,\REGION} \WebACL="+webaclNamewithPrefix+" \MetricName=\"BlockedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; + // eslint-disable-next-line no-useless-escape + const expression3 = "SEARCH('{AWS\/WAFV2,\LabelName,\LabelNamespace,\WebACL,\REGION} \WebACL="+webaclNamewithPrefix+" \LabelNamespace=\"awswaf:managed:aws:bot-control:bot:category\" \MetricName=\"AllowedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; + // eslint-disable-next-line no-useless-escape + const expression4 = "SEARCH('{AWS\/WAFV2,\LabelName,\LabelNamespace,\WebACL,\REGION} \WebACL="+webaclNamewithPrefix+" \LabelNamespace=\"awswaf:managed:aws:bot-control:bot:category\" \MetricName=\"BlockedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; + const expression5 = "SUM([e3,e4])"; + const expression6 = "SUM([e1,e2,-e3,-e4])"; + + const botrequestsvsnonbotrequests = new cloudwatch.GraphWidget({ + title: "๐Ÿค– Bot requests vs ๐Ÿ˜ Non-bot requests in " + account, + width: 24, + height: 8 + }); + + botrequestsvsnonbotrequests.addLeftMetric( + new cloudwatch.MathExpression({ + expression: expression5, + usingMetrics: { + "e3": new cloudwatch.MathExpression({expression: expression3,searchAccount: account, searchRegion: REGION}), + "e4": new cloudwatch.MathExpression({expression: expression4,searchAccount: account, searchRegion: REGION}) + }, + label: "Bot requests", + searchAccount: account, + searchRegion: REGION, + color: "#ff0000" + })); + botrequestsvsnonbotrequests.addLeftMetric(new cloudwatch.MathExpression({ + expression: expression6, + usingMetrics: { + "e1": new cloudwatch.MathExpression({expression: expression1,searchAccount: account, searchRegion: REGION}), + "e2": new cloudwatch.MathExpression({expression: expression2,searchAccount: account, searchRegion: REGION}), + "e3": new cloudwatch.MathExpression({expression: expression3,searchAccount: account, searchRegion: REGION}), + "e4": new cloudwatch.MathExpression({expression: expression4,searchAccount: account, searchRegion: REGION}) + }, + label: "Non-bot requests", + searchAccount: account, + searchRegion: REGION, + color: "#00FF00" + })); + + + const sinlevaluecountedrequests = new cloudwatch.SingleValueWidget({ + title: "๐Ÿ”ข Counted Request in " + account, + metrics: [ + new cloudwatch.MathExpression({ + expression: "SUM(" +sinlevaluecountedrequestsexpression +")", + usingMetrics: {}, + label: "CountedRequests", + searchAccount: account, + searchRegion: REGION, + color: "#9dbcd4" + }) + ], + width: 8, + height: 3 + }); + const singlevalueallowedrequest = new cloudwatch.SingleValueWidget({ + title: "โœ… Allowed Request in " + account, + metrics: [ + new cloudwatch.MathExpression({ + expression: "SUM(" +expression1 +")", + usingMetrics: {}, + label: "AllowedRequests", + searchAccount: account, + searchRegion: REGION, + color: "#00FF00" + }) + ], + width: 8, + height: 3 + }); + const singlevaluebockedrequest = new cloudwatch.SingleValueWidget({ + title: "โŒ Blocked Request in " + account, + metrics: [ + new cloudwatch.MathExpression({ + expression: "SUM(" +expression2 +")", + usingMetrics: {}, + label: "BlockedRequests", + searchAccount: account, + searchRegion: REGION, + color: "#ff0000" + }) + ], + width: 8, + height: 3 + }); + const row = new cloudwatch.Row(sinlevaluecountedrequests,singlevalueallowedrequest,singlevaluebockedrequest); + const row2 = new cloudwatch.Row(botrequestsvsnonbotrequests); + const row1 = new cloudwatch.Row(countedRequests,allowedRequests, blockedRequests); + cwdashboard.addWidgets(row,row1,row2); + } + } + } +} \ No newline at end of file diff --git a/lib/firewall-stack.ts b/lib/firewall-stack.ts index 260b7ece..f4bbe22f 100644 --- a/lib/firewall-stack.ts +++ b/lib/firewall-stack.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; +import cloneDeep from "lodash/cloneDeep"; 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"; @@ -11,15 +12,9 @@ import { aws_logs as logs } from "aws-cdk-lib"; import { Config, CustomResponseBodies } from "./types/config"; import { ManagedRuleGroup, ManagedServiceData, ServiceDataManagedRuleGroup, ServiceDataRuleGroup, Rule } from "./types/fms"; import { RuntimeProperties, ProcessProperties } from "./types/runtimeprops"; +import {WafCloudWatchDashboard} from "./constructs/cloudwatch"; import { promises as fsp } from "fs"; import { toAwsCamel } from "./tools/helpers"; -import { aws_cloudwatch as cloudwatch } from "aws-cdk-lib"; -import * as packageJsonObject from "../package.json"; - -/** - * Version of the AWS Firewall Factory - extracted from package.json - */ -const FIREWALL_FACTORY_VERSION = packageJsonObject.version; export interface ConfigStackProps extends cdk.StackProps { readonly config: Config; @@ -31,87 +26,124 @@ export class FirewallStack extends cdk.Stack { super(scope, id, props); const accountId = cdk.Aws.ACCOUNT_ID; const region = cdk.Aws.REGION; + let loggingConfiguration; + if(props.config.General.LoggingConfiguration === "Firehose"){ + 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: { + const policy = { Version: "2012-10-17", Statement: [ { - Sid: "", Effect: "Allow", - Principal: { Service: "firehose.amazonaws.com" }, - Action: "sts:AssumeRole", + Action: [ + "s3:AbortMultipartUpload", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:PutObject", + "s3:PutObjectAcl", + ], + Resource: [ + "arn:aws:s3:::" + props.config.General.S3LoggingBucketName, + "arn:aws:s3:::" + props.config.General.S3LoggingBucketName + "/*", + ], + }, + { + Effect: "Allow", + Action: ["logs:PutLogEvents"], + Resource: [cfnLogGroup.attrArn], + }, + { + Effect: "Allow", + Action: ["kms:Decrypt", "kms:GenerateDataKey"], + Resource: [props.config.General.FireHoseKeyArn], }, ], - }, - }); - - const cfnLogGroup = new logs.CfnLogGroup(this, "KinesisErrorLogging", { - retentionInDays: 90, - }); - - const policy = { - Version: "2012-10-17", - Statement: [ - { - Effect: "Allow", - Action: [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - "s3:PutObjectAcl", - ], - Resource: [ - "arn:aws:s3:::" + props.config.General.S3LoggingBucketName, - "arn:aws:s3:::" + props.config.General.S3LoggingBucketName + "/*", - ], - }, - { - Effect: "Allow", - Action: ["logs:PutLogEvents"], - Resource: [cfnLogGroup.attrArn], - }, - { - Effect: "Allow", - Action: ["kms:Decrypt", "kms:GenerateDataKey"], - Resource: [props.config.General.FireHoseKeyArn], - }, - ], - }; + }; - new iam.CfnPolicy(this, "KinesisS3DeliveryPolicy", { - policyDocument: policy, - policyName: "firehose_delivery_policy", - roles: [cfnRole.ref], - }); + new iam.CfnPolicy(this, "KinesisS3DeliveryPolicy", { + policyDocument: policy, + policyName: "firehose_delivery_policy", + roles: [cfnRole.ref], + }); - new firehouse.CfnDeliveryStream(this, "S3DeliveryStream", { - deliveryStreamName: + 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, + extendedS3DestinationConfiguration: { + bucketArn: "arn:aws:s3:::" + props.config.General.S3LoggingBucketName, + encryptionConfiguration: { + kmsEncryptionConfig: { + awskmsKeyArn: props.config.General.FireHoseKeyArn || "", + }, }, - }, - roleArn: cfnRole.attrArn, - bufferingHints: { sizeInMBs: 50, intervalInSeconds: 60 }, - compressionFormat: "UNCOMPRESSED", - prefix: "AWSLogs/" + accountId + "/FirewallManager/" + region + "/", - errorOutputPrefix: + roleArn: cfnRole.attrArn, + bufferingHints: { sizeInMBs: 50, intervalInSeconds: 60 }, + compressionFormat: "UNCOMPRESSED", + prefix: "AWSLogs/" + accountId + "/FirewallManager/" + region + "/", + errorOutputPrefix: "AWSLogs/" + accountId + "/FirewallManager/" + region + "/Errors", - }, - }); + }, + }); + loggingConfiguration = "${S3DeliveryStream.Arn}"; + } + if(props.config.General.LoggingConfiguration === "S3"){ + loggingConfiguration = "arn:aws:s3:::"+props.config.General.S3LoggingBucketName; + } + // -------------------------------------------------------------------- + // IPSets + const ipSets: cdk.aws_wafv2.CfnIPSet[] = []; + if(props.config.WebAcl.IPSets) { + const ipSetsNames: string[] =[]; + for(const ipSet of props.config.WebAcl.IPSets) { + const addresses: string[] = []; + for(const address of ipSet.Addresses) { + if(typeof address === "string") addresses.push(address); + else addresses.push(address.IP); + } + + const cfnipset = new wafv2.CfnIPSet(this, ipSet.Name+props.config.General.DeployHash, { + name: `${props.config.General.Prefix}-${props.config.General.Stage}-${ipSet.Name}-${props.config.General.DeployHash}`, + description: ipSet.Description ? ipSet.Description : "IP Set created by AWS Firewall Factory \n used in " +props.config.General.Prefix.toUpperCase() + + "-" + + props.config.WebAcl.Name + + "-" + + props.config.General.Stage + + "-" + + props.config.General.DeployHash + " Firewall", + addresses: addresses, + ipAddressVersion: ipSet.IPAddressVersion, + scope: props.config.WebAcl.Scope, + tags: ipSet.Tags ? ipSet.Tags : undefined + }); + ipSetsNames.push(ipSet.Name); + ipSets.push(cfnipset); + } + } + // -------------------------------------------------------------------- const preProcessRuleGroups = []; const postProcessRuleGroups = []; if (props.config.WebAcl.PreProcess.ManagedRuleGroups) { @@ -144,7 +176,7 @@ export class FirewallStack extends cdk.Stack { postProcessRuleGroups: postProcessRuleGroups, overrideCustomerWebACLAssociation: true, loggingConfiguration: { - logDestinationConfigs: ["${S3DeliveryStream.Arn}"], + logDestinationConfigs: [loggingConfiguration || ""], }, }; const cfnPolicyProps = { @@ -172,222 +204,18 @@ export class FirewallStack extends cdk.Stack { excludeResourceTags: props.config.WebAcl.ExcludeResourceTags ? props.config.WebAcl.ExcludeResourceTags : false, policyDescription: props.config.WebAcl.Description ? props.config.WebAcl.Description : undefined }; - new fms.CfnPolicy(this, "CfnPolicy", cfnPolicyProps); - - if(props.config.General.CreateDashboard && props.config.General.CreateDashboard === true) { - console.log("\n๐ŸŽจ Creating central CloudWatch Dashboard \n ๐Ÿ“Š DashboardName: ","\u001b[32m", props.config.General.Prefix.toUpperCase() + - "-" + - props.config.WebAcl.Name + - "-" + - props.config.General.Stage + - "-" + - props.config.General.DeployHash,"\u001b[0m"); - console.log(" โ„น๏ธ Warnings for Math expressions can be ignored."); - const cwdashboard = new cloudwatch.Dashboard(this, "cloudwatch-dashboard", { - dashboardName: props.config.General.Prefix.toUpperCase() + - "-" + - props.config.WebAcl.Name + - "-" + - props.config.General.Stage + - "-" + - props.config.General.DeployHash, - periodOverride: cloudwatch.PeriodOverride.AUTO, - start: "-PT24H" - }); - const webaclName = props.config.General.Prefix.toUpperCase() + - "-" + - props.config.WebAcl.Name + - "-" + - props.config.General.Stage + - "-" + - props.config.General.DeployHash; - const webaclNamewithPrefix = "FMManagedWebACLV2-" + props.config.General.Prefix.toUpperCase() + - "-" + - props.config.WebAcl.Name + - "-" + - props.config.General.Stage + - "-" + - props.config.General.DeployHash; - - - - if(props.config.WebAcl.IncludeMap.account){ - const infowidget = new cloudwatch.TextWidget({ - markdown: "# ๐Ÿ”ฅ "+webaclName+"\n + ๐Ÿ— Deployed to: \n\n ๐Ÿ“ฆ Accounts: "+props.config.WebAcl.IncludeMap.account.toString() + "\n\n ๐ŸŒŽ Region: " + region + "\n\n ๐Ÿ’ก Type: " + props.config.WebAcl.Type, - width: 14, - height: 4 - }); - - const securedDomain = props.config.General.SecuredDomain.toString(); - - const app = new cloudwatch.TextWidget({ - markdown: "โš™๏ธ Used [ManagedRuleGroups](https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups.html):\n" + MANAGEDRULEGROUPSINFO.toString().replace(/,/g,"\n - ") + "\n\n--- \n\n\nโ„น๏ธ Link to your secured [Application]("+securedDomain+")", - width: 7, - height: 4 - }); - let fwmessage = ""; - if(process.env.LASTEST_FIREWALLFACTORY_VERSION !== FIREWALL_FACTORY_VERSION){ - fwmessage = "๐Ÿšจ old or beta version"; - } - else{ - fwmessage = "๐Ÿ’š latest version"; - } - const fwfactory = new cloudwatch.TextWidget({ - markdown: "**AWS FIREWALL FACTORY** \n\n ![Image](https://github.com/globaldatanet/aws-firewall-factory/raw/master/static/icon/firewallfactory.png) \n\n ๐Ÿท Version: [" + FIREWALL_FACTORY_VERSION + "](https://github.com/globaldatanet/aws-firewall-factory/releases/tag/" + FIREWALL_FACTORY_VERSION + ") \n" + fwmessage, - width: 3, - height: 4 - }); - const firstrow = new cloudwatch.Row(infowidget,app,fwfactory); - cwdashboard.addWidgets(firstrow); - for(const account of props.config.WebAcl.IncludeMap.account){ - // eslint-disable-next-line no-useless-escape - const countexpression = "SEARCH('{AWS\/WAFV2,\Region,\WebACL,\Rule} \WebACL="+webaclNamewithPrefix+" \MetricName=\"\CountedRequests\"', '\Sum', 300)"; - - const countedRequests = new cloudwatch.GraphWidget({ - title: "๐Ÿ”ข Counted Requests in " + account, - width: 8, - height: 8 - }); - countedRequests.addLeftMetric( - new cloudwatch.MathExpression({ - expression: countexpression, - usingMetrics: {}, - label: "CountedRequests", - searchAccount: account, - searchRegion: region, - color: "#9dbcd4" - })); - // eslint-disable-next-line no-useless-escape - const blockedexpression = "SEARCH('{AWS\/WAFV2,\Region,\WebACL,\Rule} \WebACL="+webaclNamewithPrefix+" \MetricName=\"\BlockedRequests\"', '\Sum', 300)"; - const blockedRequests = new cloudwatch.GraphWidget({ - title: "โŒ Blocked Requests in " + account, - width: 8, - height: 8 - }); - blockedRequests.addLeftMetric( - new cloudwatch.MathExpression({ - expression: blockedexpression, - usingMetrics: {}, - label: "BlockedRequests", - searchAccount: account, - searchRegion: region, - color: "#ff0000" - })); - // eslint-disable-next-line no-useless-escape - const allowedexpression = "SEARCH('{AWS\/WAFV2,\Region,\WebACL,\Rule} \WebACL="+webaclNamewithPrefix+" \MetricName=\"\AllowedRequests\"', '\Sum', 300)"; - const allowedRequests = new cloudwatch.GraphWidget({ - title: "โœ… Allowed Requests in " + account, - width: 8, - height: 8 - }); - allowedRequests.addLeftMetric( - new cloudwatch.MathExpression({ - expression: allowedexpression, - usingMetrics: {}, - label: "AllowedRequests", - searchAccount: account, - searchRegion: region, - color: "#00FF00" - })); - // eslint-disable-next-line no-useless-escape - const sinlevaluecountedrequestsexpression = "SEARCH('{AWS\/WAFV2,\Rule,\WebACL,\Region} \WebACL="+webaclNamewithPrefix+" \MetricName=\"CountedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; - // eslint-disable-next-line no-useless-escape - const expression1 = "SEARCH('{AWS\/WAFV2,\Rule,\WebACL,\Region} \WebACL="+webaclNamewithPrefix+" \MetricName=\"AllowedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; - // eslint-disable-next-line no-useless-escape - const expression2 = "SEARCH('{AWS\/WAFV2,\Rule,\WebACL,\Region} \WebACL="+webaclNamewithPrefix+" \MetricName=\"BlockedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; - // eslint-disable-next-line no-useless-escape - const expression3 = "SEARCH('{AWS\/WAFV2,\LabelName,\LabelNamespace,\WebACL,\Region} \WebACL="+webaclNamewithPrefix+" \LabelNamespace=\"awswaf:managed:aws:bot-control:bot:category\" \MetricName=\"AllowedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; - // eslint-disable-next-line no-useless-escape - const expression4 = "SEARCH('{AWS\/WAFV2,\LabelName,\LabelNamespace,\WebACL,\Region} \WebACL="+webaclNamewithPrefix+" \LabelNamespace=\"awswaf:managed:aws:bot-control:bot:category\" \MetricName=\"BlockedRequests\" \Rule=\"ALL\"', '\Sum', 300)"; - const expression5 = "SUM([e3,e4])"; - const expression6 = "SUM([e1,e2,-e3,-e4])"; - - const botrequestsvsnonbotrequests = new cloudwatch.GraphWidget({ - title: "๐Ÿค– Bot requests vs ๐Ÿ˜ Non-bot requests in " + account, - width: 24, - height: 8 - }); - - botrequestsvsnonbotrequests.addLeftMetric( - new cloudwatch.MathExpression({ - expression: expression5, - usingMetrics: { - "e3": new cloudwatch.MathExpression({expression: expression3,searchAccount: account, searchRegion: region}), - "e4": new cloudwatch.MathExpression({expression: expression4,searchAccount: account, searchRegion: region}) - }, - label: "Bot requests", - searchAccount: account, - searchRegion: region, - color: "#ff0000" - })); - botrequestsvsnonbotrequests.addLeftMetric(new cloudwatch.MathExpression({ - expression: expression6, - usingMetrics: { - "e1": new cloudwatch.MathExpression({expression: expression1,searchAccount: account, searchRegion: region}), - "e2": new cloudwatch.MathExpression({expression: expression2,searchAccount: account, searchRegion: region}), - "e3": new cloudwatch.MathExpression({expression: expression3,searchAccount: account, searchRegion: region}), - "e4": new cloudwatch.MathExpression({expression: expression4,searchAccount: account, searchRegion: region}) - }, - label: "Non-bot requests", - searchAccount: account, - searchRegion: region, - color: "#00FF00" - })); - - - const sinlevaluecountedrequests = new cloudwatch.SingleValueWidget({ - title: "๐Ÿ”ข Counted Request in " + account, - metrics: [ - new cloudwatch.MathExpression({ - expression: "SUM(" +sinlevaluecountedrequestsexpression +")", - usingMetrics: {}, - label: "CountedRequests", - searchAccount: account, - searchRegion: region, - color: "#9dbcd4" - }) - ], - width: 8, - height: 3 - }); - const singlevalueallowedrequest = new cloudwatch.SingleValueWidget({ - title: "โœ… Allowed Request in " + account, - metrics: [ - new cloudwatch.MathExpression({ - expression: "SUM(" +expression1 +")", - usingMetrics: {}, - label: "AllowedRequests", - searchAccount: account, - searchRegion: region, - color: "#00FF00" - }) - ], - width: 8, - height: 3 - }); - const singlevaluebockedrequest = new cloudwatch.SingleValueWidget({ - title: "โŒ Blocked Request in " + account, - metrics: [ - new cloudwatch.MathExpression({ - expression: "SUM(" +expression2 +")", - usingMetrics: {}, - label: "BlockedRequests", - searchAccount: account, - searchRegion: region, - color: "#ff0000" - }) - ], - width: 8, - height: 3 - }); - const row = new cloudwatch.Row(sinlevaluecountedrequests,singlevalueallowedrequest,singlevaluebockedrequest); - const row2 = new cloudwatch.Row(botrequestsvsnonbotrequests); - const row1 = new cloudwatch.Row(countedRequests,allowedRequests, blockedRequests); - cwdashboard.addWidgets(row,row1,row2); - } + const fmspolicy = new fms.CfnPolicy(this, "CfnPolicy", cfnPolicyProps); + if(ipSets.length !== 0){ + for(const ipSet of ipSets){ + fmspolicy.addDependency(ipSet); } } + + if(props.config.General.CreateDashboard && props.config.General.CreateDashboard === true) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + new WafCloudWatchDashboard(this, "cloudwatch",props.config, MANAGEDRULEGROUPSINFO); + } const options = { flag: "w", force: true }; void (async () => { try { @@ -446,7 +274,8 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac "\n"+icon+" Custom Rules " + type + "Process: ", "\x1b[0m\n" ); - if (capacity < 1000) { + + if (capacity < 1500) { const rules = []; let count = 1; for (const statement of ruleGroupSet) { @@ -466,15 +295,24 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac "-" + deployHash; } + + + + // Add Fn::Sub for replacing IPSets logical name with its real ARN after deployment + const subStatement = cloneDeep(statement.Statement); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if (subStatement.IPSetReferenceStatement && !subStatement.IPSetReferenceStatement.ARN.startsWith("arn")) { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + subStatement.IPSetReferenceStatement.ARN = cdk.Fn.getAtt(subStatement.IPSetReferenceStatement.ARN+deployHash, "Arn"); + } let cfnRuleProperty; if ("Captcha" in statement.Action) { cfnRuleProperty = { name: rulename, priority: count, - + action: toAwsCamel(statement.Action), - - statement: toAwsCamel(statement.Statement), + statement: toAwsCamel(subStatement), visibilityConfig: { sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, @@ -482,20 +320,15 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac statement.VisibilityConfig.CloudWatchMetricsEnabled, metricName: rulename + "-metric", }, - captchaConfig: toAwsCamel(statement.CaptchaConfig), - ruleLabels: toAwsCamel(statement.RuleLabels), }; } else { cfnRuleProperty = { name: rulename, priority: count, - action: toAwsCamel(statement.Action), - // fixes cloudformation warning "required key [Name] not found" in statements like "SingleHeader" - - statement: JSON.parse(JSON.stringify(toAwsCamel(statement.Statement))?.replace(/name/g,"Name")), + statement: toAwsCamel(subStatement), visibilityConfig: { sampledRequestsEnabled: statement.VisibilityConfig.SampledRequestsEnabled, @@ -503,7 +336,6 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac statement.VisibilityConfig.CloudWatchMetricsEnabled, metricName: rulename + "-metric", }, - ruleLabels: toAwsCamel(statement.RuleLabels), }; } @@ -582,7 +414,7 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac cstResBodies = undefined; } - new wafv2.CfnRuleGroup(scope, rulegroupidentifier, { + const rulegroup = new wafv2.CfnRuleGroup(scope, rulegroupidentifier, { capacity: processRuntimeProps.Capacity, scope: webAclScope, rules: rules, @@ -651,7 +483,7 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac deployHash, }); } else { - const threshold = 1000; + const threshold = 1500; const rulesets: any[] = []; const indexes: number[] = []; const rulegroupcapacities = []; @@ -803,7 +635,17 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac "-" + deployHash; } + let cfnRuleProperty; + + // Add Fn::Sub for replacing IPSets logical name with its real ARN after deployment + const subStatement = cloneDeep(ruleGroupSet[statementindex].Statement); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if (subStatement.IPSetReferenceStatement && !subStatement.IPSetReferenceStatement.ARN.startsWith("arn")) { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + subStatement.IPSetReferenceStatement.ARN = cdk.Fn.getAtt(subStatement.IPSetReferenceStatement.ARN+deployHash, "Arn"); + } + if ( "Captcha" in ruleGroupSet[statementindex] @@ -816,10 +658,7 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac ruleGroupSet[statementindex] .Action ), - statement: toAwsCamel( - ruleGroupSet[statementindex] - .Statement - ), + statement: toAwsCamel(subStatement), visibilityConfig: { sampledRequestsEnabled: ruleGroupSet[statementindex] @@ -846,10 +685,7 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac ruleGroupSet[statementindex] .Action ), - statement: toAwsCamel( - ruleGroupSet[statementindex] - .Statement - ), + statement: toAwsCamel(subStatement), visibilityConfig: { sampledRequestsEnabled: ruleGroupSet[statementindex] @@ -890,7 +726,7 @@ function buildServiceDataCustomRgs(scope: Construct, type: "Pre" | "Post", capac cstResBodies = undefined; } - new wafv2.CfnRuleGroup(scope, rulegroupidentifier, { + const rulegroup = new wafv2.CfnRuleGroup(scope, rulegroupidentifier, { capacity: rulegroupcapacities[count], scope: webAclScope, rules: cfnRuleProperties, diff --git a/lib/tools/config-validator.ts b/lib/tools/config-validator.ts index eac4f13e..4d706a4d 100644 --- a/lib/tools/config-validator.ts +++ b/lib/tools/config-validator.ts @@ -21,8 +21,24 @@ const wafschema = TJS.generateSchema(program, "Config", settings); const prerequisitesschema = TJS.generateSchema(program, "Prerequisites", settings); + const ajv = new Ajv(); -export const validatewaf = ajv.compile(wafschema as JSONSchemaType); +export const validateWaf = ajv.compile(wafschema as JSONSchemaType); + +export const validatePrerequisites = ajv.compile(prerequisitesschema as JSONSchemaType); + -export const validateprerequisites = ajv.compile(prerequisitesschema as JSONSchemaType); \ No newline at end of file +/** + * The function will check if s3 bucket is Parameter is starting with aws-waf-logs- if Logging Configuration is set to S3 + * @param config Config + */ +export function wrongLoggingConfiguration(config: Config): boolean{ + if(config.General.LoggingConfiguration === "S3"){ + if(!config.General.S3LoggingBucketName.startsWith("aws-waf-logs-")){ + return true; + } + return false; + } + return false; +} diff --git a/lib/tools/generate-skeleton.ts b/lib/tools/generate-skeleton.ts index 1df6ecf1..b0618840 100644 --- a/lib/tools/generate-skeleton.ts +++ b/lib/tools/generate-skeleton.ts @@ -9,6 +9,7 @@ const skeletonConfig : Config = { S3LoggingBucketName: "myBucketName", SecuredDomain: ["yourapp.."], CreateDashboard: true, + LoggingConfiguration: "Firehose", }, WebAcl: { IncludeMap: { diff --git a/lib/tools/get-owasp-top10-waf.ts b/lib/tools/get-owasp-top10-waf.ts index 782588e9..97f29ae7 100644 --- a/lib/tools/get-owasp-top10-waf.ts +++ b/lib/tools/get-owasp-top10-waf.ts @@ -9,6 +9,7 @@ const owasptop10Config : Config = { S3LoggingBucketName: "myBucketName", SecuredDomain: ["yourapp.."], CreateDashboard: true, + LoggingConfiguration: "Firehose", }, WebAcl: { IncludeMap: { diff --git a/lib/tools/helpers.ts b/lib/tools/helpers.ts index 57b15e82..82211d55 100644 --- a/lib/tools/helpers.ts +++ b/lib/tools/helpers.ts @@ -13,6 +13,10 @@ import { Rule } from "../types/fms"; import * as lodash from "lodash"; import { RuntimeProperties } from "../types/runtimeprops"; import { Config } from "../types/config"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +import cfonts = require("cfonts"); +import * as packageJsonObject from "../../package.json"; + /** * Service Quota Code for Firewall Manager Total WAF WCU in account & region */ @@ -246,12 +250,29 @@ async function calculateCapacities( ): Promise { let count = 0; + console.log("\n๐Ÿ‘€ Get CustomRule Capacity:") if (!config.WebAcl.PreProcess.CustomRules) { console.log( "\n โญ Skip Rule Capacity Calculation for PreProcess Custom Rules." ); } else { while (count < config.WebAcl.PreProcess.CustomRules.length) { + // Manually calculate and return capacity if rule has a ipset statements with a logical ID entry (e.g. ${IPsString.Arn}) + // This means the IPSet will be created by this repo, maybe it doesn't exists yet. That fails this function. That's why the code below is needed. + const ipSetReferenceStatement = config.WebAcl.PreProcess.CustomRules[count].Statement.IPSetReferenceStatement; + if(ipSetReferenceStatement && !ipSetReferenceStatement.ARN.startsWith("arn:aws:")) { + runtimeProperties.PreProcess.CustomRuleCount += 1; + if("Captcha" in config.WebAcl.PreProcess.CustomRules[count].Action) runtimeProperties.PreProcess.CustomCaptchaRuleCount += 1; + // Capacity for IPSet statements: + // "WCUs โ€“ 2 WCU for most. If you configure the statement to use forwarded IP addresses and specify a position of ANY, increase the WCU usage by 4." + // https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-ipset-match.html + let ipSetRuleCapacity = 2; + if(ipSetReferenceStatement.IPSetForwardedIPConfig?.Position === "ANY") ipSetRuleCapacity += 4; + runtimeProperties.PreProcess.RuleCapacities.push(ipSetRuleCapacity); + + count++; + continue; + } runtimeProperties.PreProcess.CustomRuleCount += 1; if ("Captcha" in config.WebAcl.PreProcess.CustomRules[count].Action) { runtimeProperties.PreProcess.CustomCaptchaRuleCount += 1; @@ -324,6 +345,23 @@ async function calculateCapacities( ); } else { while (count < config.WebAcl.PostProcess.CustomRules.length) { + // Manually calculate and return capacity if rule has a ipset statements with a logical ID entry (e.g. ${IPsString.Arn}) + // This means the IPSet will be created by this repo, maybe it doesn't exists yet. That fails this function. That's why the code below is needed. + const ipSetReferenceStatement = config.WebAcl.PostProcess.CustomRules[count].Statement.IPSetReferenceStatement; + if(ipSetReferenceStatement && !ipSetReferenceStatement.ARN.startsWith("arn:aws:")) { + runtimeProperties.PostProcess.CustomRuleCount += 1; + if("Captcha" in config.WebAcl.PostProcess.CustomRules[count].Action) runtimeProperties.PostProcess.CustomCaptchaRuleCount += 1; + // Capacity for IPSet statements: + // "WCUs โ€“ 2 WCU for most. If you configure the statement to use forwarded IP addresses and specify a position of ANY, increase the WCU usage by 4." + // https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-ipset-match.html + let ipSetRuleCapacity = 2; + if(ipSetReferenceStatement.IPSetForwardedIPConfig?.Position === "ANY") ipSetRuleCapacity += 4; + runtimeProperties.PostProcess.RuleCapacities.push(ipSetRuleCapacity); + + count++; + continue; + } + runtimeProperties.PostProcess.CustomRuleCount += 1; const ruleCalculatedCapacityJson = []; const { CloudWatchMetricsEnabled: cloudWatchMetricsEnabled, SampledRequestsEnabled: sampledRequestsEnabled } = @@ -609,4 +647,42 @@ export function toAwsCamel(o: any): any { } } return newO; -} \ No newline at end of file +} + +/** + * Version of the AWS Firewall Factory - extracted from package.json + */ +const FIREWALL_FACTORY_VERSION = packageJsonObject.version; + + +/** + * The function will display info banner and returns deploymentRegion for WAF Stack + * @param config configuration object of the values.json + * @return deploymentRegion AWS region, e.g. eu-central-1 + */ +export const outputInfoBanner = (config:Config): string => { + /** + * the region into which the stack is deployed + */ + let deploymentRegion = ""; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + cfonts.say("AWS FIREWALL FACTORY", {font: "block",align: "left",colors: ["#00ecbd"],background: "transparent",letterSpacing: 0,lineHeight: 0,space: true,maxLength: "13",gradient: false,independentGradient: false,transitionGradient: false,env: "node",width:"80%"}); + console.log("\n ยฉ by globaldatanet"); + console.log("\n๐Ÿท Version: ","\x1b[4m",FIREWALL_FACTORY_VERSION,"\x1b[0m"); + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + console.log("๐Ÿ‘ค AWS Account used: ","\x1b[33m","\n " + process.env.CDK_DEFAULT_ACCOUNT,"\x1b[0m"); + if(process.env.PREREQUISITE === "true"){ + console.log("๐ŸŒŽ CDK deployment region:","\x1b[33m","\n "+process.env.AWS_REGION,"\x1b[0m \n"); + } + else{ + if(config.WebAcl.Scope === "CLOUDFRONT"){ + deploymentRegion = "us-east-1"; + } + else{ + deploymentRegion = process.env.REGION || "eu-central-1"; + } + console.log("๐ŸŒŽ CDK deployment region:","\x1b[33m","\n "+deploymentRegion,"\x1b[0m \n"); + } + return deploymentRegion; +}; \ No newline at end of file diff --git a/lib/tools/price-calculator.ts b/lib/tools/price-calculator.ts index 77b64e01..0b454a9a 100644 --- a/lib/tools/price-calculator.ts +++ b/lib/tools/price-calculator.ts @@ -167,7 +167,7 @@ async function getProductPrice(deploymentRegion: PriceRegions, servicecode: stri * @param runtimeProps runtime properties object, where to get prices * @returns whether price is successfully calculated or not */ -export async function isPriceCalculated(runtimeProps: RuntimeProperties): Promise { +export async function isPriceCalculated(runtimeProps: RuntimeProperties): Promise { const preprocessfixedcost = (runtimeProps.PreProcess.CustomRuleCount * runtimeProps.Pricing.Rule) + runtimeProps.PreProcess.CustomRuleGroupCount + runtimeProps.PreProcess.ManagedRuleGroupCount; const postprocessfixedcost = (runtimeProps.PostProcess.CustomRuleCount * runtimeProps.Pricing.Rule) + runtimeProps.PostProcess.CustomRuleGroupCount + runtimeProps.PostProcess.ManagedRuleGroupCount; const captchacost = (runtimeProps.PostProcess.CustomCaptchaRuleCount + runtimeProps.PreProcess.CustomCaptchaRuleCount) * runtimeProps.Pricing.Captcha; @@ -194,7 +194,7 @@ export async function isPriceCalculated(runtimeProps: RuntimeProperties): Promis (runtimeProps.Pricing.Dashboard !== 0) ? console.log(" The deployed WAF includes CloudWatch Dashboard and you have more than 3 Dashboards (Free tier), so you will need to pay " + runtimeProps.Pricing.Dashboard+ "$ for this CloudWatch Dashboard. \n These costs are already included in the price calculation.") : ""; (shieldSubscriptionState === "Active") ? console.log(" AWS WAF WebACLs or Rules created by Firewall Manager - are Included in AWS Shield Advanced. More information at https://aws.amazon.com/firewall-manager/pricing/.") : ""; console.log("\n\n"); - const pricecalculated = "True"; + const pricecalculated = true; return pricecalculated; } diff --git a/lib/types/config.ts b/lib/types/config.ts index aace1a18..4ce80960 100644 --- a/lib/types/config.ts +++ b/lib/types/config.ts @@ -1,12 +1,14 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Rule, ManagedRuleGroup } from "./fms"; import { aws_fms as fms } from "aws-cdk-lib"; +import { CfnTag } from "aws-cdk-lib"; export interface Config { readonly General: { readonly Prefix: string, readonly Stage: string, - readonly FireHoseKeyArn: string, + readonly LoggingConfiguration: "S3" | "Firehose" + readonly FireHoseKeyArn?: string, readonly S3LoggingBucketName: string, DeployHash: string, readonly SecuredDomain: Array, @@ -14,6 +16,7 @@ export interface Config { }, readonly WebAcl:{ readonly Name: string, + /** * @TJS-pattern ^([\p{L}\p{Z}\p{N}_.:\/=+\-@]*)$ */ @@ -27,6 +30,7 @@ export interface Config { readonly ExcludeResourceTags?: boolean, readonly RemediationEnabled?: boolean, readonly ResourcesCleanUp?: boolean, + readonly IPSets?: IPSet[], readonly PreProcess: RuleGroupSet, readonly PostProcess: RuleGroupSet, }, @@ -101,4 +105,28 @@ export interface RuleGroupSet { CustomResponseBodies?: CustomResponseBodies, CustomRules?: Rule[], ManagedRuleGroups?: ManagedRuleGroup[]; -} \ No newline at end of file +} + +/** + * @TJS-pattern (?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}\/(3[0-2]|[12]?[0-9])$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$) +*/ +type IPAddress = string; // The regex above matches both IPv4 and IPv6 in CIDR notation, e.g. 123.4.3.0/32 + +interface IPAddressWithDescription { + Description: string, + IP: IPAddress +} + +export interface IPSet { + /** + * @TJS-pattern ^[a-zA-Z0-9]+$ + */ + Name: string, // This name will be used as a CloudFormation logical ID, so it can't have a already used name and must be alphanumeric + /* + * @TJS-pattern ^[\w+=:#@\/\-,\.][\w+=:#@\/\-,\.\s]+[\w+=:#@\/\-,\.]$ + */ + Description?: string, + Addresses: Array, + IPAddressVersion: "IPV4" | "IPV6", + Tags?: CfnTag[] +} diff --git a/package-lock.json b/package-lock.json index 69bf8f10..84e2f3ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "aws-firewall-factory", - "version": "3.2.6", + "version": "3.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aws-firewall-factory", - "version": "3.2.6", + "version": "3.3.0", "dependencies": { "@aws-sdk/client-cloudformation": "^3.353.0", "@aws-sdk/client-cloudwatch": "^3.353.0", @@ -19,6 +19,7 @@ "@types/lodash": "4.14.178", "ajv": "8.10.0", "aws-cdk-lib": "2.77.0", + "cfonts": "^3.2.0", "constructs": "10.2.25", "lodash": "4.17.21", "process": "0.11.10", @@ -4002,6 +4003,35 @@ "inherits": "^2.0.1" } }, + "node_modules/cfonts": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cfonts/-/cfonts-3.2.0.tgz", + "integrity": "sha512-CFGxRY6aBuOgK85bceCpmMMhuyO6IwcAyyeapB//DtRzm7NbAEsDuuZzBoQxVonz+C2BmZ3swqB/YgcmW+rh3A==", + "dependencies": { + "supports-color": "^8", + "window-size": "^1.1.1" + }, + "bin": { + "cfonts": "bin/index.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cfonts/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4424,6 +4454,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6155,6 +6196,17 @@ "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==" }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -6246,6 +6298,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -6261,6 +6324,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -7284,6 +7360,14 @@ "node": ">=6" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -9322,6 +9406,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/window-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-1.1.1.tgz", + "integrity": "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==", + "dependencies": { + "define-property": "^1.0.0", + "is-number": "^3.0.0" + }, + "bin": { + "window-size": "cli.js" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/window-size/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/window-size/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/package.json b/package.json index f3b7cda1..5a82cc3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aws-firewall-factory", - "version": "3.2.6", + "version": "3.3.0", "bin": { "firewallfactory": "bin/aws-firewall-factory.js" }, @@ -39,6 +39,7 @@ "@types/lodash": "4.14.178", "ajv": "8.10.0", "aws-cdk-lib": "2.77.0", + "cfonts": "^3.2.0", "constructs": "10.2.25", "lodash": "4.17.21", "process": "0.11.10", diff --git a/static/AWSFIREWALLMANAGER.drawio.xml b/static/AWSFIREWALLMANAGER.drawio.xml index d8e44925..db03b515 100644 --- a/static/AWSFIREWALLMANAGER.drawio.xml +++ b/static/AWSFIREWALLMANAGER.drawio.xml @@ -1 +1,195 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/AWSFIREWALLMANAGER.png b/static/AWSFIREWALLMANAGER.png index 2d04e97d..7d4421e3 100644 Binary files a/static/AWSFIREWALLMANAGER.png and b/static/AWSFIREWALLMANAGER.png differ diff --git a/static/example-deployment.gif b/static/example-deployment.gif index 622ae056..6746e328 100644 Binary files a/static/example-deployment.gif and b/static/example-deployment.gif differ diff --git a/static/example_deployment.png b/static/example_deployment.png deleted file mode 100644 index 481c3f07..00000000 Binary files a/static/example_deployment.png and /dev/null differ diff --git a/tsconfig.json b/tsconfig.json index d2db01f6..687072b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,8 @@ "typeRoots": [ "./node_modules/@types" ], - "resolveJsonModule": true + "resolveJsonModule": true, + "esModuleInterop": true }, "exclude": [ "node_modules", diff --git a/values/CustomResponseBodies.json b/values/custom-response-bodies.json similarity index 100% rename from values/CustomResponseBodies.json rename to values/custom-response-bodies.json diff --git a/values/ip-sets-managed.json b/values/ip-sets-managed.json new file mode 100644 index 00000000..bbe7ba44 --- /dev/null +++ b/values/ip-sets-managed.json @@ -0,0 +1,50 @@ +{ + "General": { + "Prefix": "", + "Stage": "", + "S3LoggingBucketName": "", + "DeployHash": "", + "FireHoseKeyArn": "", + "SecuredDomain": [] + }, + "WebAcl": { + "Name": "test-ipsets", + "Scope": "REGIONAL", + "Description": "IPsets", + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "RemediationEnabled": true, + "ResourcesCleanUp": true, + "IncludeMap": { + "account": [] + }, + "IPSets": [ + { + "Name": "IPsString", + "Addresses": [ + "192.168.178.1/32" + ], + "IPAddressVersion": "IPV4" + } + ], + "PreProcess": { + "CustomRules": [ + { + "Name": "ip-allow", + "Statement": { + "IPSetReferenceStatement": { + "ARN": "IPsString" + } + }, + "Action": { + "Allow": {} + }, + "VisibilityConfig": { + "SampledRequestsEnabled": true, + "CloudWatchMetricsEnabled": true + } + } + ] + }, + "PostProcess": {} + } +} \ No newline at end of file diff --git a/values/RuleActionOverrides.json b/values/rule-action-overrides.json similarity index 100% rename from values/RuleActionOverrides.json rename to values/rule-action-overrides.json