diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..1b5f27f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: bug +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Please complete the following information about the solution:** + +- [ ] Version: [e.g. v1.0.0] + +To get the version of the solution, you can look at the description of the created CloudFormation stack. For example, "_(SO0108) - AWS Network Firewall Deployment Automations for AWS Transit Gateway. Version **v1.0.0**_". + +- [ ] Region: [e.g. us-east-1] +- [ ] Was the solution modified from the version published on this repository? +- [ ] If the answer to the previous question was yes, are the changes available on GitHub? +- [ ] Have you checked your [service quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) for the sevices this solution uses? +- [ ] Were there any errors in the CloudWatch Logs? + +**Screenshots** +If applicable, add screenshots to help explain your problem (please **DO NOT include sensitive information**). + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..8c46516 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature request +about: Suggest an idea for this solution +title: "" +labels: enhancement +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the feature you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..db6ceed --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +_Issue #, if available:_ + +_Description of changes:_ + +By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2093049 --- /dev/null +++ b/.gitignore @@ -0,0 +1,108 @@ + +*node_modules* + +# C extensions +*.so +*.pyc +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +#cdk +*cdk.out* +*.d.ts +*.js + +#ignore these in the deployment folder +*regional-s3-assets* +*staging* +*global-s3-assets* +.DS_Store +*.zip +deployment/open-source +deployment/examples +deployment/dist +source/deploy +deployment/vpc_rules + + +.env +.idea +.vscode +source/scratch/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..8dacba3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2021-02-24 +### Added +- New solution AWS Network Firewall Deployment Automations for AWS Transit Gateway, initial version diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md old mode 100644 new mode 100755 index 5b627cf..3b64466 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ ## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md old mode 100644 new mode 100755 index c4b6a1c..67f2886 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ information to effectively respond to your bug report or contribution. We welcome you to use the GitHub issue tracker to report bugs or suggest features. -When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already +When filing an issue, please check [existing open](https://github.com/awslabs/network-firewall-automation/issues), or [recently closed](https://github.com/awslabs/network-firewall-automation/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: * A reproducible test case or series of steps @@ -23,7 +23,7 @@ reported the issue. Please try to include as much information as you can. Detail ## Contributing via Pull Requests Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: -1. You are working against the latest source on the *main* branch. +1. You are working against the latest source on the *master* branch. 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. @@ -31,17 +31,18 @@ To send us a pull request, please: 1. Fork the repository. 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. -3. Ensure local tests pass. -4. Commit to your fork using clear commit messages. -5. Send us a pull request, answering any default questions in the pull request interface. -6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. +3. Ensure all build processes execute successfully (see README.md for additional guidance). +4. Ensure all unit, integration, and/or snapshot tests pass, as applicable. +5. Commit to your fork using clear commit messages. +6. Send us a pull request, answering any default questions in the pull request interface. +7. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). ## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/network-firewall-automation/labels/help%20wanted) issues is a great place to start. ## Code of Conduct @@ -51,9 +52,11 @@ opensource-codeofconduct@amazon.com with any additional questions or comments. ## Security issue notifications -If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. +If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public GitHub issue. ## Licensing -See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the [LICENSE](https://github.com/awslabs/network-firewall-automation/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. + +We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/LICENSE b/LICENSE.txt old mode 100644 new mode 100755 similarity index 99% rename from LICENSE rename to LICENSE.txt index 67db858..19dc35b --- a/LICENSE +++ b/LICENSE.txt @@ -172,4 +172,4 @@ of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. + of your accepting any such warranty or additional liability. \ No newline at end of file diff --git a/NOTICE b/NOTICE deleted file mode 100644 index 616fc58..0000000 --- a/NOTICE +++ /dev/null @@ -1 +0,0 @@ -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100755 index 0000000..68807d3 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,20 @@ +AWS Network Firewall Automation +Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Licensed under the Apache License Version 2.0 (the "License"). You may not use this file except +in compliance with the License. A copy of the License is located at http://www.apache.org/licenses/ +or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the +specific language governing permissions and limitations under the License. + +********************** +THIRD PARTY COMPONENTS +********************** +This software includes third party software subject to the following copyrights: +jest undert the MIT License +axios under the MIT License +moment under the MIT License +uuid under the MIT License. +AWS SDK under the Apache License Version 2.0 +aws-cdk under Apache License 2.0 + +AWS SDK under the Apache License Version 2.0 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 847260c..eea0150 --- a/README.md +++ b/README.md @@ -1,17 +1,177 @@ -## My Project +**[AWS Network Firewall Deployment Automations for AWS Transit Gateway](https://aws.amazon.com/solutions/implementations/aws-network-firewall-deployment-automations-for-aws-transit-gateway)** | **[🚧 Feature request](https://github.com/awslabs/aws-network-firewall-deployment-automations-for-aws-transit-gateway/issues/new?assignees=&labels=feature-request%2C+enhancement&template=feature_request.md&title=)** | **[🐛 Bug Report](https://github.com/awslabs/aws-network-firewall-deployment-automations-for-aws-transit-gateway/issues/new?assignees=&labels=bug%2C+triage&template=bug_report.md&title=)** -TODO: Fill this README out! +Note: If you want to use the solution without building from source, navigate to Solution Landing Page -Be sure to: +## Table of contents -* Change the title in this README -* Edit your repository description on GitHub +- [Solution Overview](#solution-overview) +- [Architecture Diagram](#architecture-diagram) +- [AWS CDK Constructs](#aws-solutions-constructs) +- [Customizing the Solution](#customizing-the-solution) + - [Prerequisites for Customization](#prerequisites-for-customization) + - [Build](#build) + - [Unit Test](#unit-test) + - [Deploy](#deploy) +- [File Structure](#file-structure) +- [License](#license) -## Security + +# Solution Overview +[//]: # Solution for AWS Network Firewall Deployment Automations for AWS Transit Gateway. + + +# Architecture Diagram +[//]: # ![Architecture Diagram](./source/architecture.png) + + +## Prerequisites for Customization +[//]: # Node.js>12 + + +## Build +[//]: # Build the CDK code +``` +cd source/ +npm run build +``` + +Build the Network Firewall Solution CodeBuild source code +``` +cd source/networkfirewallAutomation +tsc +``` + +Build the templates for custom deployments + +``` +cd deployments/ +chmod +x ./build-s3-dist.sh +./build-s3-dist.sh [SOLUTION_DIST_BUCKET] network-firewall-automation [VERSION_ID] +``` + + +## Unit Test +[//]: # Run the unit tests + +``` +cd source/ +chmod +x ./run-all-tests.sh +``` + + +## Deploy +[//]: Follow the steps for deploying your custom version of the solution. +* Create an S3 bucket with the bucket appended with the region in which the deployment is to be made. example, if the deployment is to be made in us-east-1 create a bucket name as [BUCKET_NAME]-us-east-1. +* Create the distribution files using the script provided in the build section above. +* Create the S3 Key in the bucket network-firewall-automation/[VERSION_ID]/ +* Create the S3 Key in the bucket network-firewall-automation/latest/ +* Copy the file ./deployment/regional-s3-assets/network-firewall-automation.zip to the location s3://[BUCKET_NAME]-[REGION]/network-firewall-automation/[VERSION_ID]/ +* Copy the file ./deployment/regional-s3-assets/network-firewall-configuration.zip to the location s3://[BUCKET_NAME]-[REGION]/network-firewall-automation/latest/ + +Once the above steps are completed, use the file ./deployment/global-s3-assets/aws-network-firewall-deployment-automations-for-aws-transit-gateway.template to create a stack in CloudFormation. + + + +# File structure + +aws-network-firewall-deployment-automations-for-aws-transit-gateway consists of: + +- CDK constructs to generate necessary resources +- Microservices used in the solution + +[//]: # File Structure + +
+|-deployment/
+  |build-s3-dist.sh/                     [ Build script for create the distribution for the solution.]
+|-source/
+  |-bin/
+    |-network-firewall-auto-solution.ts  [ entry point for CDK app ]
+  |-test/                  [ unit tests for CDK constructs ] 
+    |-network-firewall-automation-solution.test.ts [CDK construct for the solution.]
+    |-__snapshots__
+      |-network-firewall-automation-solution.test.ts.snap [CDK construct template snapshot of unit testing.]
+  |-lib/
+    |-network-firewall-automation-solution-stack.ts [ CDK construct for the solution. ]
+  |-networkFirewallAutomation
+    |-__tests__
+      |-firewall-test-configuration
+        |-firewalls
+          |-firewall-invalid.json
+          |-firewall-nopolicy.json
+          |-firewall-example.json
+        |-firewallPolicies
+          |-firewall-invalid-policy.json
+          |-firewall-policy-2.json
+          |-firewall-policy.example.json
+        |-ruleGroups
+          |-stateless-pass-action.example.json
+          |-stateless-fwd-to-stateful.example.json
+          |-stateful-domainblock.example.json
+          |-drop.rules
+          |-suricata-rule-reference.json
+      |-network-firewall-service.spec.ts
+      |-ec2-manager.spec.ts
+      |-firewall-config-validation.spec.ts
+      |-network-firewall-manager.spec.ts
+      |-send-metrics.spec.ts
+    |-config
+      |-examples
+        |-firewalls
+          |-firewall.example.json
+        |-firewallPolicies
+          |-firewall-policy.example.json
+        |-ruleGroups
+          |-stateless-pass-action.example.json
+          |-stateless-fwd-to-stateful.example.json
+          |-stateful-domainblock.example.json
+          |-drop.rules
+          |-suricata-rule-reference.json
+      |-firewallPolicies
+        |-firewall-policy-1.json
+      |-firewalls
+        |-firewall-1.json
+    |-lib
+      |-ec2-manager.ts
+      |-network-firewall-manager.ts
+      |-common
+        |-configReader
+          |-config-reader.ts
+        |-logger.ts
+        |-stringUtils.ts
+        |-firewall-config-validation.ts
+        |-send-metrics.ts
+      |-service
+        |-awsClientConfig.ts
+        |-ec2-service.ts
+        |-network-firewall-service.ts
+      |-build.ts
+      |-index.ts
+      |-config_files            [ tsconfig, jest.config.js, package.json etc. ]
+  |-config_files                [ tsconfig, cdk.json, package.json etc. ]
+  |-run-all-tests.sh
+|-buildspec.yml
+|-architecture.yml
+|-CHANGELOG.md
+|-CODE_OF_CONDUCT.md
+|-LICENSE.txt
+|-CONTRIBUTING.md
+|-NOTICE.txt
+
+ + +*** + +Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Licensed under the Apache License Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at + + http://www.apache.org/licenses/ + +or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and limitations under the License. + +See [LICENSE](https://github.com/awslabs/aws-network-firewall-solution-for-aws-transit-gateway/blob/master/LICENSE.txt) -See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. -## License -This project is licensed under the Apache-2.0 License. diff --git a/deployment/build-s3-dist.sh b/deployment/build-s3-dist.sh new file mode 100755 index 0000000..55cdd43 --- /dev/null +++ b/deployment/build-s3-dist.sh @@ -0,0 +1,142 @@ +#!/bin/bash +# +# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance +# with the License. A copy of the License is located at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES +# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions +# and limitations under the License. +# + +# Important: CDK global version number +cdk_version=1.77.0 + +# Check to see if the required parameters have been provided: +if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then + echo "Please provide the base source bucket name, trademark approved solution name and version where the artifact code will eventually reside." + echo "For example: ./build-s3-dist.sh solutions trademarked-solution-name v1.0.0" + exit 1 +fi + +[ "$DEBUG" == 'true' ] && set -x +set -e + +# Environment variables +export DIST_VERSION=$3 +export DIST_OUTPUT_BUCKET=$1 +export SOLUTION_ID=SO0108 +export SOLUTION_NAME=$2 +export SOLUTION_TRADEMARKEDNAME=$2 + + +# Get reference for all important folders +template_dir="$PWD" +staging_dist_dir="$template_dir/staging" +template_dist_dir="$template_dir/global-s3-assets" +build_dist_dir="$template_dir/regional-s3-assets" +source_dir="$template_dir/../source" + +echo "------------------------------------------------------------------------------" +echo "[Init] Remove any old dist files from previous runs" +echo "------------------------------------------------------------------------------" + +echo "rm -rf $template_dist_dir" +rm -rf $template_dist_dir +echo "mkdir -p $template_dist_dir" +mkdir -p $template_dist_dir +echo "rm -rf $build_dist_dir" +rm -rf $build_dist_dir +echo "mkdir -p $build_dist_dir" +mkdir -p $build_dist_dir +echo "rm -rf $staging_dist_dir" +rm -rf $staging_dist_dir +echo "mkdir -p $staging_dist_dir" +mkdir -p $staging_dist_dir +echo "rm -rf $template_dir/vpc_rules" +rm -rf $template_dir/vpc_rules + +echo "------------------------------------------------------------------------------" +echo "[Synth] CDK Project" +echo "------------------------------------------------------------------------------" + +# Install the global aws-cdk package +echo "cd $source_dir" +cd $source_dir +echo "npm install -g aws-cdk@$cdk_version" +npm install -g aws-cdk@$cdk_version + +# Run 'cdk synth' to generate raw solution outputs +cd "$source_dir" +echo "cdk synth --output=$staging_dist_dir" +npm run build && cdk synth --output=$staging_dist_dir + +# Remove unnecessary output files +echo "cd $staging_dist_dir" +cd $staging_dist_dir +echo "rm tree.json manifest.json cdk.out" +rm tree.json manifest.json cdk.out + +echo "------------------------------------------------------------------------------" +echo "[Packing] Template artifacts" +echo "------------------------------------------------------------------------------" + +# Move outputs from staging to template_dist_dir +echo "Move outputs from staging to template_dist_dir" +echo "cp $template_dir/*.template $template_dist_dir/" +cp $staging_dist_dir/*.template.json $template_dist_dir/ +rm *.template.json + +# Rename all *.template.json files to *.template +echo "Rename all *.template.json to *.template" +echo "copy templates and rename" +for f in $template_dist_dir/*.template.json; do + mv -- "$f" "${f%.template.json}.template" +done + +echo "------------------------------------------------------------------------------" +echo "[Packing] Source code artifacts" +echo "------------------------------------------------------------------------------" + +# General cleanup of node_modules and package-lock.json files +echo "find $staging_dist_dir -iname "node_modules" -type d -exec rm -rf "{}" \; 2> /dev/null" +find $staging_dist_dir -iname "node_modules" -type d -exec rm -rf "{}" \; 2> /dev/null +echo "find $staging_dist_dir -iname "package-lock.json" -type f -exec rm -f "{}" \; 2> /dev/null" +find $staging_dist_dir -iname "package-lock.json" -type f -exec rm -f "{}" \; 2> /dev/null + +echo "------------------------------------------------------------------------------" +echo "Package Network Firewall Automation node project for Code Build/Deploy stage " +echo "------------------------------------------------------------------------------" +cd $source_dir/networkFirewallAutomation/ +npm install +npm run build +npm run zip +if [ "$?" = "1" ]; then + echo "(npm run zip) ERROR: there is likely output above." 1>&2 + exit 1 +fi +echo "Copy package zip to dist directory" +echo "cp ./dist/network-firewall-automation.zip $build_dist_dir/network-firewall-automation.zip" +cp ./dist/network-firewall-automation.zip $build_dist_dir/network-firewall-automation.zip + +# build regional rule groups zip files for each region +echo "Copying network firewall configurations to deployment folder" +cd $template_dir +cp -pr $source_dir/networkFirewallAutomation/config/* ./ +echo -e "\n Creating a zip file with network firewall configurations" +echo -e "\n Building network firewall configuration" +zip -Xr "$build_dist_dir"/network-firewall-configuration.zip ./firewalls ./ruleGroups ./firewallPolicies ./examples + +echo "------------------------------------------------------------------------------" +echo "[Cleanup] Remove temporary files" +echo "------------------------------------------------------------------------------" + +# Delete the temporary /staging folder +echo "rm -rf $staging_dist_dir" +rm -rf $staging_dist_dir +rm -rf ./ruleGroups +rm -rf ./firewallPolicies +rm -rf ./firewalls diff --git a/source/architecture.png b/source/architecture.png new file mode 100644 index 0000000..76158f9 Binary files /dev/null and b/source/architecture.png differ diff --git a/source/bin/network-firewall-auto-solution.ts b/source/bin/network-firewall-auto-solution.ts new file mode 100755 index 0000000..68067fa --- /dev/null +++ b/source/bin/network-firewall-auto-solution.ts @@ -0,0 +1,40 @@ +#!/usr/bin/env node +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +import * as cdk from '@aws-cdk/core'; +import { + NetworkFirewallAutomationStack, + NetworkFirewallAutomationStackProps +} from '../lib/network-firewall-automation-solution-stack'; + +const SOLUTION_VERSION = process.env['DIST_VERSION']; +const SOLUTION_NAME = process.env['SOLUTION_NAME']; +const SOLUTION_ID = process.env['SOLUTION_ID'] || 'SO0108'; +const SOLUTION_BUCKET = process.env['DIST_OUTPUT_BUCKET']; +const SOLUTION_TMN = process.env['SOLUTION_TRADEMARKEDNAME']; +const SOLUTION_PROVIDER = 'AWS Solution Development'; + +const app = new cdk.App(); + +let NetworkFirewallAutomationStackProperties: NetworkFirewallAutomationStackProps = { + solutionId: SOLUTION_ID, + solutionTradeMarkName: SOLUTION_TMN, + solutionProvider: SOLUTION_PROVIDER, + solutionBucket: SOLUTION_BUCKET, + solutionName: SOLUTION_NAME, + solutionVersion: SOLUTION_VERSION, + description: '(' + SOLUTION_ID + ') - The AWS CloudFormation template' + + ' for deployment of the ' + SOLUTION_NAME + ', Version: ' + SOLUTION_VERSION, +} + +new NetworkFirewallAutomationStack(app, 'aws-network-firewall-deployment-automations-for-aws-transit-gateway', NetworkFirewallAutomationStackProperties); diff --git a/source/cdk.json b/source/cdk.json new file mode 100755 index 0000000..9a31f98 --- /dev/null +++ b/source/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node bin/network-firewall-auto-solution.ts" +} diff --git a/source/lib/network-firewall-automation-solution-stack.ts b/source/lib/network-firewall-automation-solution-stack.ts new file mode 100755 index 0000000..7e5c5f3 --- /dev/null +++ b/source/lib/network-firewall-automation-solution-stack.ts @@ -0,0 +1,1222 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import * as cdk from '@aws-cdk/core'; +import { RemovalPolicy } from '@aws-cdk/core'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as logs from '@aws-cdk/aws-logs'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as codecommit from '@aws-cdk/aws-codecommit'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as codepipeline_action from '@aws-cdk/aws-codepipeline-actions'; +import { + BuildEnvironmentVariableType, + BuildSpec, + LinuxBuildImage, + PipelineProject +} from '@aws-cdk/aws-codebuild'; + + +export interface NetworkFirewallAutomationStackProps extends cdk.StackProps { + solutionId: string; + solutionTradeMarkName: string | undefined; + solutionProvider: string | undefined; + solutionBucket: string | undefined; + solutionName: string | undefined; + solutionVersion: string | undefined; +} + +export class NetworkFirewallAutomationStack extends cdk.Stack { + + constructor(scope: cdk.Construct, id: string, props: NetworkFirewallAutomationStackProps) { + super(scope, id, props); + + /** + * Parameters - Values to pass to your template at runtime + */ + + const cidrBlock = new cdk.CfnParameter(this, 'cidrBlock', { + type: 'String', + default: '192.168.1.0/26', + description: 'CIDR Block for VPC. Must be /26 or larger CIDR block.', + allowedPattern: '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}[\/]([0-9]?[0-6]?|[1][7-9])$' + }) + + const logRetentionPeriod = new cdk.CfnParameter(this, "LogRetentionPeriod", { + type: "Number", + description: "Log retention period in days.", + allowedValues: ["1", "3", "5", "7", "14", "30", "60", "90", "120", "150", "180", "365", "400", "545", "731", "1827", "3653"], + default: 90 + }); + + const existingTransitGatewayId = new cdk.CfnParameter(this, "ExistingTransitGateway", { + description: 'Existing AWS Transit Gateway id.', + type: 'String', + default: "" + }) + + const transitGatewayRTIdForAssociation = new cdk.CfnParameter(this, "TransitGatewayRouteTableIdForAssociation", { + description: 'Existing AWS Transit Gateway route table id. Example:' + + ' Firewall Route Table. Format: tgw-rtb-0a1b2c3d', + type: 'String', + default: "" + }) + + const transitGatewayRTIdForDefaultRoute = new cdk.CfnParameter(this, "TransitGatewayRTIdForDefaultRoute", { + description: 'Existing AWS Transit Gateway route table id.' + + ' Example: Spoke VPC Route Table. Format: tgw-rtb-4e5f6g7h', + type: 'String', + default: "" + }) + + const logType = new cdk.CfnParameter(this, "logType", { + type: "String", + description: 'The type of log to send. Alert logs report traffic that' + + ' matches a StatefulRule with an action setting that sends an alert' + + ' log message. Flow logs are standard network traffic flow logs.', + allowedValues: ['ALERT', 'FLOW', 'EnableBoth'], + default: 'FLOW', + }) + + const logDestinationType = new cdk.CfnParameter(this, "logDestinationType", { + type: "String", + description: 'The type of storage destination to send these logs to.' + + ' You can send logs to an Amazon S3 bucket ' + + 'or a CloudWatch log group.', + allowedValues: ['S3', 'CloudWatchLogs', 'ConfigureManually'], + default: 'CloudWatchLogs', + }) + + /** + * Metadata - Objects that provide additional information about the + * template. + */ + + this.templateOptions.metadata = { + "AWS::CloudFormation::Interface": { + ParameterGroups: [ + { + Label: { default: "VPC Configuration" }, + Parameters: [cidrBlock.logicalId] + }, + { + Label: { default: "Transit Gateway Configuration" }, + Parameters: [ + existingTransitGatewayId.logicalId, + transitGatewayRTIdForAssociation.logicalId, + transitGatewayRTIdForDefaultRoute.logicalId + ] + }, + { + Label: { default: "Firewall Logging Configuration" }, + Parameters: [ + logDestinationType.logicalId, + logType.logicalId, + logRetentionPeriod.logicalId + ] + } + ], + ParameterLabels: { + [cidrBlock.logicalId]: { + default: "Provide the CIDR block for the Inspection VPC", + }, + [existingTransitGatewayId.logicalId]: { + default: "Provide the existing AWS Transit Gateway ID you wish to" + + " attach to the Inspection VPC", + }, + [transitGatewayRTIdForAssociation.logicalId]: { + default: "Provide AWS Transit Gateway Route Table to be" + + " associated with the Inspection VPC TGW Attachment.", + }, + [transitGatewayRTIdForDefaultRoute.logicalId]: { + default: "Provide the AWS Transit Gateway Route Table to receive 0.0.0.0/0 route to the Inspection VPC TGW Attachment.", + }, + [logType.logicalId]: { + default: "Select the type of log to send to the defined log" + + " destination.", + }, + [logDestinationType.logicalId]: { + default: "Select the type of log destination for the Network" + + " Firewall", + }, + [logRetentionPeriod.logicalId]: { + default: "Select the log retention period for Network Firewall" + + " Logs.", + } + }, + }, + }; + + /** + * Mappings - define fixed values + */ + const mappings = new cdk.CfnMapping(this, 'SolutionMapping') + mappings.setValue('Version', 'Latest', 'latest') + mappings.setValue('Route', 'QuadZero', '0.0.0.0/0') + mappings.setValue('Log', 'Level', 'info') + mappings.setValue('CodeCommitRepo', 'Name', 'network-firewall-config-repo-') + mappings.setValue('Metrics', 'URL', 'https://metrics.awssolutionsbuilder.com/generic') + mappings.setValue('Solution', 'Identifier', 'SO0108') + mappings.setValue('TransitGatewayAttachment', 'ApplianceMode', 'enable') + + const send = new cdk.CfnMapping(this, 'Send') + send.setValue('AnonymousUsage', 'Data', 'Yes') + send.setValue('ParameterKey', 'UniqueId', `/Solutions/${props.solutionName}/UUID`) + + + /** + * Conditions - control whether certain resources are created or whether + * certain resource properties are assigned a value during stack + * creation or update. + */ + + const isLoggingInS3 = new cdk.CfnCondition(this, + "LoggingInS3", + { + expression: cdk.Fn.conditionEquals(logDestinationType.valueAsString, 'S3') + }) + + const isLoggingInCloudWatch = new cdk.CfnCondition(this, + "LoggingInCloudWatch", + { + expression: cdk.Fn.conditionEquals(logDestinationType.valueAsString, 'CloudWatchLogs') + }) + + const isNotLoggingConfigureManually = new cdk.CfnCondition(this, + "NotLoggingConfigureManually", + { + expression: cdk.Fn.conditionNot(cdk.Fn.conditionEquals(logDestinationType.valueAsString, 'ConfigureManually')) + }) + + /** + * condition to determine if transit gateway id is provided or not if + * provided use it to create transit gateway attachment else skip + */ + + const createTransitGatewayAttachment = new cdk.CfnCondition(this, + "CreateTransitGatewayAttachment", + { + expression: cdk.Fn.conditionNot(cdk.Fn.conditionEquals(existingTransitGatewayId.valueAsString, '')) + }) + + /** + * condition to determine if transit gateway route table id is provided or + * not. if provided use it to create route table association else skip + */ + const createTransitGatewayRTAssociation = new cdk.CfnCondition(this, + "CreateTransitGatewayRTAssociation", + { + expression: cdk.Fn.conditionAnd( + cdk.Fn.conditionNot( + cdk.Fn.conditionEquals( + transitGatewayRTIdForAssociation.valueAsString, '')), createTransitGatewayAttachment) + }) + + /** + * condition to determine if transit gateway route table id is provided or + * not. if provided use it to create route table propagation else skip + */ + const createDefaultRouteFirewallRT = new cdk.CfnCondition(this, + "CreateDefaultRouteFirewallRT", + { + expression: cdk.Fn.conditionAnd( + cdk.Fn.conditionNot( + cdk.Fn.conditionEquals( + transitGatewayRTIdForDefaultRoute.valueAsString, '')), createTransitGatewayAttachment) + }) + + /** + * Resources - Specifies the stack resources and their properties + */ + + this.templateOptions.templateFormatVersion = '2010-09-09'; + + // Create a new VPC + + const vpc = new ec2.CfnVPC(this, 'VPC', { + cidrBlock: cidrBlock.valueAsString, + }); + + //KMS Key for the VPC Flow logs and Firewall Logs + const KMSKeyForNetworkFirewallLogDestinations = new kms.Key(this, "KMSKeyForNetworkFirewallLogDestinations", { + description: "This key will be used for encrypting the vpc flow logs and firewall logs.", + enableKeyRotation: true + }) + + //Permissions for network firewall service to be able use this key for publishing logs to S3. + KMSKeyForNetworkFirewallLogDestinations.addToResourcePolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + resources: ["*"], + principals: [new iam.ServicePrincipal("delivery.logs.amazonaws.com")], + actions: ["kms:GenerateDataKey*"] + })) + //Permissions for network firewall service to be able use this key for publishing logs to cloudwatch. + KMSKeyForNetworkFirewallLogDestinations.addToResourcePolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + resources: ["*"], + actions: [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ], + principals: [ + new iam.ServicePrincipal(`logs.${cdk.Aws.REGION}.amazonaws.com`) + ] + })) + + // Create a new log group for Firewall logging + const cloudWatchLogGroup = new logs.CfnLogGroup(this, 'CloudWatchLogGroup', { + retentionInDays: logRetentionPeriod.valueAsNumber, + kmsKeyId: KMSKeyForNetworkFirewallLogDestinations.keyArn + }) + + cloudWatchLogGroup.cfnOptions.condition = isLoggingInCloudWatch; + + const logsBucket = new s3.Bucket(this, 'Logs', { + encryption: s3.BucketEncryption.KMS, + encryptionKey: KMSKeyForNetworkFirewallLogDestinations, + publicReadAccess: false, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [{ + expiration: cdk.Duration.days(logRetentionPeriod.valueAsNumber) + }] + }); + + const cfnLogsBucket = logsBucket.node.defaultChild as s3.CfnBucket; + cfnLogsBucket.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W35', + reason: 'Logs bucket does not require logging configuration' + }, { + id: 'W51', + reason: 'Logs bucket is private and does not require a bucket policy' + }] + } + }; + cfnLogsBucket.cfnOptions.condition = isLoggingInS3; + + //Solution Logging Changes stop. + + + vpc.applyRemovalPolicy(RemovalPolicy.RETAIN) + vpc.tags.setTag('Name', `${cdk.Aws.STACK_NAME}-Inspection-VPC`) + vpc.tags.setTag('created-by', `${props.solutionName}`) + + const cidrCount = 4 + const cidrBits = '4' + const availabilityZoneA = { + "Fn::Select": [ + "0", + { + "Fn::GetAZs": "" + } + ] + } + const availabilityZoneB = { + "Fn::Select": [ + "1", + { + "Fn::GetAZs": "" + } + ] + } + + // Create Firewall Subnet 1 + const NetworkFirewallSubnet1 = new ec2.CfnSubnet(this, "NetworkFirewallSubnet1", { + vpcId: vpc.ref, + cidrBlock: cdk.Fn.select( + 0, + cdk.Fn.cidr( + vpc.attrCidrBlock, + cidrCount, + cidrBits + ) + ) + }) + NetworkFirewallSubnet1.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-FirewallSubnet1`) + NetworkFirewallSubnet1.applyRemovalPolicy(RemovalPolicy.RETAIN) + NetworkFirewallSubnet1.addPropertyOverride('AvailabilityZone', availabilityZoneA) + + + // Create Firewall Subnet 2 + const NetworkFirewallSubnet2 = new ec2.CfnSubnet(this, "NetworkFirewallSubnet2", { + vpcId: vpc.ref, + cidrBlock: cdk.Fn.select( + 1, + cdk.Fn.cidr( + vpc.attrCidrBlock, + cidrCount, + cidrBits + ) + ) + }) + + NetworkFirewallSubnet2.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-FirewallSubnet2`) + NetworkFirewallSubnet2.applyRemovalPolicy(RemovalPolicy.RETAIN) + NetworkFirewallSubnet2.addPropertyOverride('AvailabilityZone', availabilityZoneB) + + //Subnet Route Tables. + const firewallSubnetRouteTable = new ec2.CfnRouteTable(this, "FirewallSubnetRouteTable", { + vpcId: vpc.ref + }) + firewallSubnetRouteTable.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-FirewallSubnetRouteTable`) + firewallSubnetRouteTable.applyRemovalPolicy(RemovalPolicy.RETAIN) + + //Subnet Route Table Associations. + const NetworkFirewallSubnet1RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, "NetworkFirewallSubnet1RouteTableAssociation", { + subnetId: NetworkFirewallSubnet1.ref, + routeTableId: firewallSubnetRouteTable.ref + }) + NetworkFirewallSubnet1RouteTableAssociation.applyRemovalPolicy(RemovalPolicy.RETAIN) + + const NetworkFirewallSubnet2RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, "NetworkFirewallSubnet2RouteTableAssociation", { + subnetId: NetworkFirewallSubnet2.ref, + routeTableId: firewallSubnetRouteTable.ref + }) + NetworkFirewallSubnet2RouteTableAssociation.applyRemovalPolicy(RemovalPolicy.RETAIN) + + // Create Transit Gateway Subnet 1 + const vpcTGWSubnet1 = new ec2.CfnSubnet(this, "VPCTGWSubnet1", { + vpcId: vpc.ref, + cidrBlock: cdk.Fn.select( + 2, + cdk.Fn.cidr( + vpc.attrCidrBlock, + cidrCount, + cidrBits + ) + ) + }) + vpcTGWSubnet1.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-VPCTGWSubnet1`) + vpcTGWSubnet1.applyRemovalPolicy(RemovalPolicy.RETAIN) + vpcTGWSubnet1.addPropertyOverride('AvailabilityZone', availabilityZoneA) + + // Create Transit Gateway Subnet 2 + const vpcTGWSubnet2 = new ec2.CfnSubnet(this, "VPCTGWSubnet2", { + vpcId: vpc.ref, + cidrBlock: cdk.Fn.select( + 3, + cdk.Fn.cidr( + vpc.attrCidrBlock, + cidrCount, + cidrBits + ) + ) + }) + vpcTGWSubnet2.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-VPCTGWSubnet2`) + vpcTGWSubnet2.applyRemovalPolicy(RemovalPolicy.RETAIN) + vpcTGWSubnet2.addPropertyOverride('AvailabilityZone', availabilityZoneB) + + //Route Tables for VPC Transit Gateway subnets. + const vpcTGWRouteTable1 = new ec2.CfnRouteTable(this, "VPCTGWRouteTable1", { + vpcId: vpc.ref + }) + vpcTGWRouteTable1.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-TGWSubnetRouteTable1`) + vpcTGWRouteTable1.applyRemovalPolicy(RemovalPolicy.RETAIN) + + const vpcTGWRouteTable2 = new ec2.CfnRouteTable(this, "VPCTGWRouteTable2", { + vpcId: vpc.ref + }) + vpcTGWRouteTable2.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-TGWSubnetRouteTable2`) + vpcTGWRouteTable2.applyRemovalPolicy(RemovalPolicy.RETAIN) + + //Subnet Route Table Associations for Transit Gateway Subnets + const vpcTGWSubnet1RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, "VPCTGWSubnet1RouteTableAssociation", { + subnetId: vpcTGWSubnet1.ref, + routeTableId: vpcTGWRouteTable1.ref + }) + vpcTGWSubnet1RouteTableAssociation.applyRemovalPolicy(RemovalPolicy.RETAIN) + + const vpcTGWSubnet2RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, "VPCTGWSubnet2RouteTableAssociation", { + subnetId: vpcTGWSubnet2.ref, + routeTableId: vpcTGWRouteTable2.ref, + }) + vpcTGWSubnet2RouteTableAssociation.applyRemovalPolicy(RemovalPolicy.RETAIN) + + //VPC Flow Log + const logGroup = new logs.CfnLogGroup(this, "LogGroupFlowLogs", { + retentionInDays: logRetentionPeriod.valueAsNumber, + logGroupName: cdk.Aws.STACK_NAME, + kmsKeyId: KMSKeyForNetworkFirewallLogDestinations.keyArn + }) + + const flowLogRole = new iam.Role(this, "RoleFlowLogs", { + assumedBy: new iam.ServicePrincipal("vpc-flow-logs.amazonaws.com") + }); + + const policyStatement = new iam.PolicyStatement({ + actions: [ + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:CreateLogGroup", + "logs:DescribeLogGroups"], + resources: [logGroup.attrArn] + }); + policyStatement.effect = iam.Effect.ALLOW; + flowLogRole.addToPolicy(policyStatement); + + + new ec2.CfnFlowLog(this, "FlowLog", { + deliverLogsPermissionArn: flowLogRole.roleArn, + logGroupName: logGroup.logGroupName, + resourceId: vpc.ref, + resourceType: "VPC", + trafficType: "ALL" + }); + + //Start: associate for an existing transit gateway if user provides one. + + + //Transit gateway attachment. + const vpcTGWAttachment = new ec2.CfnTransitGatewayAttachment(this, 'VPC_TGW_ATTACHMENT', { + transitGatewayId: existingTransitGatewayId.valueAsString, + vpcId: vpc.ref, + subnetIds: [ + vpcTGWSubnet1.ref, + vpcTGWSubnet2.ref + ] + }) + vpcTGWAttachment.cfnOptions.condition = createTransitGatewayAttachment + vpcTGWAttachment.tags.setTag('Name', `${cdk.Aws.STACK_NAME}-Inspection-VPC-Attachment`) + vpcTGWAttachment.applyRemovalPolicy(RemovalPolicy.RETAIN) + vpcTGWAttachment.addDeletionOverride("UpdateReplacePolicy") + + //add the transit gateway id provided by the user to the firewall route + // table created for transit gateway interaction. + const defaultTransitGatewayRoute = new ec2.CfnRoute(this, 'TGWRoute', { + routeTableId: firewallSubnetRouteTable.ref, + destinationCidrBlock: mappings.findInMap('Route', 'QuadZero'), + transitGatewayId: existingTransitGatewayId.valueAsString + }) + defaultTransitGatewayRoute.cfnOptions.condition = createTransitGatewayAttachment + defaultTransitGatewayRoute.addDependsOn(vpcTGWAttachment) + + + //Transit Gateway association with the TGW route table id provided by the user. + const tgwRouteTableAssociation = new ec2.CfnTransitGatewayRouteTableAssociation(this, 'VPCTGWRouteTableAssociation', { + transitGatewayAttachmentId: vpcTGWAttachment.ref, + transitGatewayRouteTableId: transitGatewayRTIdForAssociation.valueAsString + }) + + //createTransitGatewayRTAssociation + tgwRouteTableAssociation.cfnOptions.condition = createTransitGatewayRTAssociation + tgwRouteTableAssociation.addOverride("DeletionPolicy", "Retain") + tgwRouteTableAssociation.addDeletionOverride("UpdateReplacePolicy") + + // Add default route to Instection VPC-TGW Attachment in the Spoke VPC + // Route Transit Gateway Route Table + const defaultRouteSpokeVPCTGWRouteTable = new ec2.CfnTransitGatewayRoute(this, 'DefaultRouteSpokeVPCTGWRouteTable', { + transitGatewayRouteTableId: transitGatewayRTIdForDefaultRoute.valueAsString, + destinationCidrBlock: mappings.findInMap('Route', 'QuadZero'), + transitGatewayAttachmentId: vpcTGWAttachment.ref + }) + defaultRouteSpokeVPCTGWRouteTable.cfnOptions.condition = createDefaultRouteFirewallRT + defaultRouteSpokeVPCTGWRouteTable.addOverride("DeletionPolicy", "Retain") + + //End: Transit gateway changes. + + //CodeCommit Repo and Code Pipeline with default policy created. + const codeCommitRepo = new codecommit.Repository(this, 'NetworkFirewallCodeRepository', { + repositoryName: mappings.findInMap("CodeCommitRepo", "Name") + cdk.Aws.STACK_NAME, + description: 'This repository is created by the AWS Network Firewall' + + ' solution for AWS Transit Gateway, to store and trigger changes to' + + ' the network firewall rules and configurations.' + }) + + const codeCommitRepo_cfn_ref = codeCommitRepo.node.defaultChild as codecommit.CfnRepository + codeCommitRepo_cfn_ref.addOverride("Properties.Code.S3.Bucket", `${props.solutionBucket}-${this.region}`) + codeCommitRepo_cfn_ref.addOverride("Properties.Code.S3.Key", `${props.solutionName}/${mappings.findInMap('Version', 'Latest')}/network-firewall-configuration.zip`) + codeCommitRepo_cfn_ref.addOverride("DeletionPolicy", "Retain") + codeCommitRepo_cfn_ref.addOverride("UpdateReplacePolicy", "Retain") + + const codeBuildStagesSourceCodeBucket = new s3.Bucket(this, 'CodeBuildStagesSourceCodeBucket', { + publicReadAccess: false, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL + }); + + const sourceOutputArtifact = new codepipeline.Artifact('SourceArtifact') + const buildOutputArtifact = new codepipeline.Artifact('BuildArtifact') + + const subnetIds = NetworkFirewallSubnet1.ref + ',' + NetworkFirewallSubnet2.ref + const codeBuildEnvVariables = { + ['LOG_LEVEL']: + { + value: mappings.findInMap('Log', 'Level'), + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['VPC_ID']: + { + value: vpc.ref, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['SUBNET_IDS']: + { + value: subnetIds, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['LOG_TYPE']: + { + value: logType.valueAsString, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['LOG_DESTINATION_TYPE']: + { + value: logDestinationType.valueAsString, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['S3_LOG_BUCKET_NAME']: + { + value: cdk.Fn.conditionIf('LoggingInS3', logsBucket.bucketName, 'NotConfigured'), + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['CLOUDWATCH_LOG_GROUP_NAME']: + { + value: cdk.Fn.conditionIf('LoggingInCloudWatch', cloudWatchLogGroup.ref, 'NotConfigured'), + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['VPC_TGW_ATTACHMENT_AZ_1']: + { + value: cdk.Fn.getAtt( + 'NetworkFirewallSubnet1', + 'AvailabilityZone').toString(), + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['VPC_TGW_ATTACHMENT_AZ_2']: + { + value: cdk.Fn.getAtt( + 'NetworkFirewallSubnet2', + 'AvailabilityZone').toString(), + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_1']: + { + value: vpcTGWRouteTable1.ref, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_2']: + { + value: vpcTGWRouteTable2.ref, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['CODE_BUILD_SOURCE_CODE_S3_KEY']: { + value: `${props.solutionName}/${props.solutionVersion}`, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['STACK_ID']: { + value: `${cdk.Aws.STACK_ID}`, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['SSM_PARAM_FOR_UUID']: { + value: send.findInMap('ParameterKey', 'UniqueId'), + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['SEND_ANONYMOUS_METRICS']: { + value: `${send.findInMap('AnonymousUsage', 'Data')}`, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['SOLUTION_ID']: { + value: `${mappings.findInMap('Solution', 'Identifier')}`, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['METRICS_URL']: { + value: `${mappings.findInMap('Metrics', 'URL')}`, + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['TRANSIT_GATEWAY_ATTACHMENT_ID']: { + value: cdk.Fn.conditionIf(createTransitGatewayAttachment.logicalId, vpcTGWAttachment.ref, ''), + type: BuildEnvironmentVariableType.PLAINTEXT + }, + ['TRANSIT_GATEWAY_ATTACHMENT_APPLIANCE_MODE']: { + value: mappings.findInMap('TransitGatewayAttachment', 'ApplianceMode'), + type: BuildEnvironmentVariableType.PLAINTEXT + } + } + + // Code build project, code build role will be created by the construct. + const buildProject = new PipelineProject(this, 'BuildProject', { + buildSpec: BuildSpec.fromObject({ + version: '0.2', + phases: { + install: { + 'runtime-versions': { + nodejs: '12' + }, + commands: [`export current=$(pwd)`, `export sourceCodeKey=$CODE_BUILD_SOURCE_CODE_S3_KEY`] + }, + pre_build: { + commands: [ + `cd $current`, + `pwd; ls -ltr`, + `echo 'Download Network Firewall Solution Package'`, + `aws s3 cp s3://${codeBuildStagesSourceCodeBucket.bucketName}/$sourceCodeKey/network-firewall-automation.zip $current || true`, + `if [ -f $current/network-firewall-automation.zip ];then exit 0;else echo \"Copy file to s3 bucket\"; aws s3 cp s3://${props.solutionBucket}-${cdk.Aws.REGION}/$sourceCodeKey/network-firewall-automation.zip s3://${codeBuildStagesSourceCodeBucket.bucketName}/$sourceCodeKey/network-firewall-automation.zip; aws s3 cp s3://${codeBuildStagesSourceCodeBucket.bucketName}/$sourceCodeKey/network-firewall-automation.zip $current; fi;`, + `unzip -o $current/network-firewall-automation.zip -d $current`, + `pwd; ls -ltr`, + ] + }, + build: { + commands: [ + `echo "Validating the firewall config"`, + `node build.js` + ] + } + }, + artifacts: { + files: "**/*" + } + }), + environment: { + buildImage: LinuxBuildImage.STANDARD_4_0 + }, + environmentVariables: codeBuildEnvVariables + }) + + const buildStageIAMPolicy = new iam.Policy(this, 'buildStageIAMPolicy', { + statements: [ + new iam.PolicyStatement({ + actions: [ + "network-firewall:CreateFirewallPolicy", + "network-firewall:CreateRuleGroup" + ], + resources: [ + cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:stateful-rulegroup/*"), + cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:firewall-policy/*"), + cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:stateless-rulegroup/*") + ], + effect: iam.Effect.ALLOW + }), + new iam.PolicyStatement({ + actions: ["s3:GetObject"], + resources: [cdk.Fn.sub("arn:${AWS::Partition}:s3:::${CodeBucketName}/${KeyName}/*", { + CodeBucketName: `${props.solutionBucket}-${this.region}`, + KeyName: `${props.solutionName}` + }), + `arn:${cdk.Aws.PARTITION}:s3:::${codeBuildStagesSourceCodeBucket.bucketName}/*`] + }), + new iam.PolicyStatement({ + actions: ["s3:PutObject"], + resources: [ + `arn:${cdk.Aws.PARTITION}:s3:::${codeBuildStagesSourceCodeBucket.bucketName}/*` + ], + effect: iam.Effect.ALLOW + }), + new iam.PolicyStatement({ + actions: [ + "ssm:PutParameter", + "ssm:GetParameter", + ], + effect: iam.Effect.ALLOW, + resources: [ + cdk.Fn.sub("arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${ParameterKey}", { + ParameterKey: `${send.findInMap('ParameterKey', 'UniqueId')}` + }) + ] + }), + ] + }) + + buildProject.role?.attachInlinePolicy(buildStageIAMPolicy) + + //IAM Policy and Role to execute deploy stage + + const deployStageFirewallPolicy = new iam.Policy(this, + 'deployStageFirewallPolicy', + { + statements: [ + new iam.PolicyStatement({ + actions: [ + "network-firewall:CreateFirewall", + "network-firewall:UpdateFirewallDeleteProtection", + "network-firewall:DeleteRuleGroup", + "network-firewall:DescribeLoggingConfiguration", + "network-firewall:UpdateFirewallDescription", + "network-firewall:CreateRuleGroup", + "network-firewall:DescribeFirewall", + "network-firewall:DeleteFirewallPolicy", + "network-firewall:UpdateRuleGroup", + "network-firewall:DescribeRuleGroup", + "network-firewall:ListRuleGroups", + "network-firewall:UpdateSubnetChangeProtection", + "network-firewall:UpdateFirewallPolicyChangeProtection", + "network-firewall:AssociateFirewallPolicy", + "network-firewall:DescribeFirewallPolicy", + "network-firewall:UpdateFirewallPolicy", + "network-firewall:DescribeResourcePolicy", + "network-firewall:CreateFirewallPolicy", + "network-firewall:UpdateLoggingConfiguration", + "network-firewall:TagResource" + ], + resources: [ + cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:stateful-rulegroup/*"), + cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:firewall-policy/*"), + cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:firewall/*"), + cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:stateless-rulegroup/*") + ] + }), + new iam.PolicyStatement({ + actions: ["s3:GetObject"], + resources: [cdk.Fn.sub("arn:${AWS::Partition}:s3:::${CodeBucketName}/${KeyName}/*", { + CodeBucketName: `${props.solutionBucket}-${this.region}`, + KeyName: `${props.solutionName}` + }), + `arn:${cdk.Aws.PARTITION}:s3:::${codeBuildStagesSourceCodeBucket.bucketName}/*`] + }), + new iam.PolicyStatement({ + actions: [ + "ec2:DescribeVpcs", + "ec2:DescribeSubnets", + "ec2:DescribeRouteTables" + ], + resources: ["*"] + }), + new iam.PolicyStatement({ + actions: [ + "ec2:CreateRoute", + "ec2:DeleteRoute", + ], + effect: iam.Effect.ALLOW, + resources: [ + `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:route-table/${vpcTGWRouteTable1.ref}`, + `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:route-table/${vpcTGWRouteTable2.ref}` + ] + }), + new iam.PolicyStatement({ + actions: ["iam:CreateServiceLinkedRole"], + resources: [cdk.Fn.sub("arn:aws:iam::${AWS::AccountId}:role/aws-service-role/network-firewall.amazonaws.com/AWSServiceRoleForNetworkFirewall")] + }) + ] + }) + + const deployStageFirewallPolicyResource = deployStageFirewallPolicy.node.findChild('Resource') as iam.CfnPolicy; + + deployStageFirewallPolicyResource.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [ + { + id: 'W12', + reason: 'Resource * is required for describe APIs' + }] + } + }; + + //add modify transit gateway attachement permission only if the transit gateway attachment is provided. + const deployStageModifyTransitGatewayAttachmentPolicy = new iam.Policy(this, 'deployStageModifyTransitGatewayAttachmentPolicy', { + statements: [ + new iam.PolicyStatement({ + actions: [ + "ec2:ModifyTransitGatewayVpcAttachment" + ], + effect: iam.Effect.ALLOW, + resources: [ + `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:transit-gateway-attachment/${vpcTGWAttachment.ref}`, + ] + }) + ] + }) + const resourcePolicyModifyTGWAttachment = deployStageModifyTransitGatewayAttachmentPolicy.node.findChild('Resource') as iam.CfnPolicy; + resourcePolicyModifyTGWAttachment.cfnOptions.condition = createTransitGatewayAttachment + + const deployStageFirewallLoggingPolicy = new iam.Policy(this, + 'deployStageFirewallLoggingPolicy', + { + statements: [ + new iam.PolicyStatement({ + actions: [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries" + ], + resources: ["*"] // Per IAM service must use All Resources + }) + ] + }) + + const deployStageFirewallLoggingResource = deployStageFirewallLoggingPolicy.node.findChild('Resource') as iam.CfnPolicy; + + deployStageFirewallLoggingResource.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [ + { + id: 'W12', + reason: 'Resource * is required for these actions.' + }] + } + }; + + // skip creating the 'deployStageFirewallLoggingPolicy' IAM policy if + // logging destination type is set to configure manually + deployStageFirewallLoggingResource.cfnOptions.condition = isNotLoggingConfigureManually + + const deployStageFirewallLoggingS3Policy = new iam.Policy(this, + 'deployStageFirewallLoggingS3Policy', + { + statements: [ + new iam.PolicyStatement({ + actions: [ + "s3:PutBucketPolicy", + "s3:GetBucketPolicy" + ], + resources: [logsBucket.bucketArn] + }) + ] + }) + + const deployStageFirewallLoggingS3PolicyResource = deployStageFirewallLoggingS3Policy.node.findChild('Resource') as iam.CfnPolicy; + + // create the 'deployStageFirewallLoggingS3Policy' IAM policy only if + // logging destination type is set to S3 + deployStageFirewallLoggingS3PolicyResource.cfnOptions.condition = isLoggingInS3 + + const deployStageFirewallLoggingCWPolicy = new iam.Policy(this, + 'deployStageFirewallLoggingCWPolicy', + { + statements: [ + new iam.PolicyStatement({ + actions: [ + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies" + ], + resources: ["*"] // Per IAM service must use All Resources + }), + new iam.PolicyStatement({ + actions: [ + "logs:DescribeLogGroups" + ], + resources: [ + cdk.Fn.sub("arn:${AWS::Partition}:logs:*:${AWS::AccountId}:log-group:*") + ] + }) + ] + }) + + const deployStageFirewallLoggingCWPolicyResource = deployStageFirewallLoggingCWPolicy.node.findChild('Resource') as iam.CfnPolicy; + + deployStageFirewallLoggingCWPolicyResource.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [ + { + id: 'W12', + reason: 'Resource * is required for describe APIs' + }] + } + }; + + // create the 'deployStageFirewallLoggingCWPolicy' IAM policy if + // logging destination type is set to CloudWatch Logs + deployStageFirewallLoggingCWPolicyResource.cfnOptions.condition = isLoggingInCloudWatch + + // Code deploy build action project, role will be created by the construct. + + const deployProject = new PipelineProject(this, 'DeployProject', { + buildSpec: BuildSpec.fromObject({ + version: '0.2', + phases: { + install: { + 'runtime-versions': { + nodejs: '12' + }, + commands: [`export current=$(pwd)`, `export sourceCodeKey=$CODE_BUILD_SOURCE_CODE_S3_KEY`] + }, + pre_build: { + commands: [ + `cd $current`, + `pwd; ls -ltr`, + `echo 'Download Network Firewall Solution Package'`, + `aws s3 cp s3://${codeBuildStagesSourceCodeBucket.bucketName}/$sourceCodeKey/network-firewall-automation.zip $current`, + `unzip -o $current/network-firewall-automation.zip -d $current`, + `pwd; ls -ltr`, + ] + }, + build: { + commands: [ + `echo "Initiating Network Firewall Automation"`, + `node index.js` + ] + }, + post_build: { + commands: [] + } + }, + artifacts: { + files: "**/*" + } + }), + environment: { + buildImage: LinuxBuildImage.STANDARD_4_0 + }, + environmentVariables: codeBuildEnvVariables + }) + + // attach inline IAM policies with the default CodeBuild role. + deployProject.role?.attachInlinePolicy(deployStageFirewallPolicy) + deployProject.role?.attachInlinePolicy(deployStageFirewallLoggingPolicy) + deployProject.role?.attachInlinePolicy(deployStageFirewallLoggingS3Policy) + deployProject.role?.attachInlinePolicy(deployStageFirewallLoggingCWPolicy) + deployProject.role?.attachInlinePolicy(deployStageModifyTransitGatewayAttachmentPolicy) + + + const codePipeline = new codepipeline.Pipeline(this, `NetworkFirewallCodePipeline`, { + stages: [ + { + stageName: 'Source', + actions: [ + new codepipeline_action.CodeCommitSourceAction({ + actionName: 'Source', + repository: codeCommitRepo, + output: sourceOutputArtifact, + }) + ] + }, + { + stageName: 'Validation', + actions: [ + new codepipeline_action.CodeBuildAction({ + actionName: 'CodeBuild', + input: sourceOutputArtifact, + project: buildProject, + outputs: [buildOutputArtifact] + }) + ] + }, + { + stageName: 'Deployment', + actions: [ + new codepipeline_action.CodeBuildAction({ + actionName: 'CodeDeploy', + input: buildOutputArtifact, + project: deployProject, + }) + ] + }] + }) + + //Adding bucket encryption + const kmsKeyCfn_ref = codePipeline.artifactBucket.encryptionKey?.node.defaultChild as kms.CfnKey + kmsKeyCfn_ref.addPropertyOverride('EnableKeyRotation', true) + + const stack = cdk.Stack.of(this); + + const codePipelineArtifactBucketKmsKeyAlias = stack.node.findChild("NetworkFirewallCodePipeline").node.findChild("ArtifactsBucketEncryptionKeyAlias").node.defaultChild as kms.CfnAlias + codePipelineArtifactBucketKmsKeyAlias.addPropertyOverride("AliasName", { + "Fn::Join": [ + "", + [ + "alias/", + { + "Ref": "AWS::StackName" + }, + "-artifactBucket-EncryptionKeyAlias" + ] + ] + }) + + const codeBuildStagesSourceCodeBucket_cfn_ref = codeBuildStagesSourceCodeBucket.node.defaultChild as s3.CfnBucket + codeBuildStagesSourceCodeBucket_cfn_ref.bucketEncryption = { + serverSideEncryptionConfiguration: [ + { + serverSideEncryptionByDefault: { + kmsMasterKeyId: codePipeline.artifactBucket.encryptionKey?.keyArn, + sseAlgorithm: "aws:kms" + } + } + ] + } + + codeBuildStagesSourceCodeBucket_cfn_ref.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W35', + reason: 'Source Code bucket bucket does not require logging configuration' + }, { + id: 'W51', + reason: 'Source Code bucket is private and does not require a bucket policy' + }] + } + }; + + //S3 Bucket policy for the pipeline artifacts bucket + const bucketPolicy = new s3.BucketPolicy(this, 'CodePipelineArtifactS3BucketPolicy', { + bucket: codePipeline.artifactBucket, + removalPolicy: RemovalPolicy.RETAIN + }) + + bucketPolicy.document.addStatements( + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + 's3:DeleteBucket' + ], + principals: [new iam.ServicePrincipal('cloudformation.amazonaws.com')], + resources: [ + codePipeline.artifactBucket.bucketArn + ] + }), + new iam.PolicyStatement({ + effect: iam.Effect.DENY, + actions: [ + 's3:GetObject' + ], + principals: [ + new iam.AnyPrincipal() + ], + resources: [ + `${codePipeline.artifactBucket.bucketArn}/*`, + `${codePipeline.artifactBucket.bucketArn}` + ], + conditions: { + Bool: { + "aws:SecureTransport": false + } + } + })); + + const bucketPolicyForlogsBucket = new s3.BucketPolicy(this, 'CloudWatchLogsForNetworkFirewallBucketPolicy', { + bucket: logsBucket, + removalPolicy: RemovalPolicy.RETAIN + }) + + bucketPolicyForlogsBucket.document.addStatements( + new iam.PolicyStatement({ + effect: iam.Effect.DENY, + actions: [ + 's3:GetObject' + ], + principals: [ + new iam.AnyPrincipal() + ], + resources: [ + `${logsBucket.bucketArn}/*`, + `${logsBucket.bucketArn}` + ], + conditions: { + Bool: { + "aws:SecureTransport": false + } + } + })); + + const bucketPolicyForlogsBucket_cfn_ref = bucketPolicyForlogsBucket.node.defaultChild as s3.CfnBucketPolicy + bucketPolicyForlogsBucket_cfn_ref.cfnOptions.condition = isLoggingInS3 + + const bucketPolicyForSourceCodeBucket = new s3.BucketPolicy(this, 'CodeBuildStageSourceCodeBucketPolicy', { + bucket: codeBuildStagesSourceCodeBucket, + removalPolicy: RemovalPolicy.RETAIN + }); + + bucketPolicyForSourceCodeBucket.document.addStatements( + new iam.PolicyStatement({ + effect: iam.Effect.DENY, + actions: [ + 's3:GetObject' + ], + principals: [ + new iam.AnyPrincipal() + ], + resources: [ + `${codeBuildStagesSourceCodeBucket.bucketArn}`, + `${codeBuildStagesSourceCodeBucket.bucketArn}/*` + ], + conditions: { + Bool: { + "aws:SecureTransport": false + } + } + })); + + //disable W35 for the artifact bucket as it only store the artifact files. + const w35Rule = { + rules_to_suppress: [{ + id: 'W35', + reason: "This S3 bucket is used as the destination for 'NetworkFirewallCodePipelineArtifactsBucket'" + }] + } + const s3ArtifactBucket_cfn_ref = codePipeline.artifactBucket.node.defaultChild as s3.CfnBucket + s3ArtifactBucket_cfn_ref.cfnOptions.metadata = { + cfn_nag: w35Rule + } + + /** + * Outputs - describes the values that are returned whenever you view + * your stack's properties. + */ + new cdk.CfnOutput(this, 'Inspection VPC ID', { + value: vpc.ref, + description: 'Inspection VPC ID to create Network Firewall.', + }) + + new cdk.CfnOutput(this, 'Firewall Subnet 1 ID', { + value: NetworkFirewallSubnet1.ref, + description: 'Subnet 1 associated with Network Firewall.', + }) + + new cdk.CfnOutput(this, 'Firewall Subnet 2 ID', { + value: NetworkFirewallSubnet2.ref, + description: 'Subnet 2 associated with Network Firewall.', + }) + + new cdk.CfnOutput(this, 'Transit Gateway Subnet 1 ID', { + value: vpcTGWSubnet1.ref, + description: 'Subnet 1 associated with Transit Gateway.', + }) + + new cdk.CfnOutput(this, 'Transit Gateway Subnet 2 ID', { + value: vpcTGWSubnet2.ref, + description: 'Subnet 1 associated with Transit Gateway.', + }) + + new cdk.CfnOutput(this, 'Network Firewall Availability Zone 1', { + value: cdk.Fn.getAtt( + 'NetworkFirewallSubnet1', + 'AvailabilityZone').toString(), + description: 'Availability Zone configured for Network Firewall subnet 1', + }) + + new cdk.CfnOutput(this, 'Network Firewall Availability Zone 2', { + value: cdk.Fn.getAtt( + 'NetworkFirewallSubnet2', + 'AvailabilityZone').toString(), + description: 'Availability Zone configured for Network Firewall subnet 2', + }) + + new cdk.CfnOutput(this, 'Artifact Bucket for CodePipeline', { + value: codePipeline.artifactBucket.bucketName, + description: 'Artifact bucket name configured for the CodePipeline.', + }) + + new cdk.CfnOutput(this, 'Code Build source code bucket', { + value: codeBuildStagesSourceCodeBucket.bucketName, + description: 'Code Build source code bucket', + }) + + new cdk.CfnOutput(this, 'S3 Bucket for Firewall Logs', { + value: cdk.Fn.conditionIf('LoggingInS3', logsBucket.bucketName, 'NotConfigured').toString(), + description: 'S3 Bucket used as the log destination for Firewall' + + ' Logs.', + }) + + new cdk.CfnOutput(this, 'CloudWatch Log Group for Firewall Logs', { + value: cdk.Fn.conditionIf('LoggingInCloudWatch', cloudWatchLogGroup.ref, 'NotConfigured').toString(), + description: 'CloudWatch Log Group used as the log destination for Firewall' + + ' Logs.', + }) + + } +} diff --git a/source/networkFirewallAutomation/__tests__/ec2-manager.spec.ts b/source/networkFirewallAutomation/__tests__/ec2-manager.spec.ts new file mode 100644 index 0000000..aeeca0b --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/ec2-manager.spec.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { Ec2Manager } from '../lib/ec2-manager'; + +const ec2EnvProps = [ + { + "routeTableId":"rtb-0e99886b16ecb5710", + "availabilityZone": 'us-east-1a' + }, + { + "routeTableId":"rtb-0e99886b16ecb5710", + "availabilityZone": 'us-east-1b' + }] + +jest.mock("aws-sdk", () => { + return { + __esModule: true, + EC2: jest.fn().mockReturnValue({ + + }) + } +}, { virtual: true }); + +jest.mock("../lib/service/ec2-service", () => { + return { + __esModule: true, + Ec2Service: jest.fn().mockReturnValue({ + describeRouteTables: jest.fn().mockImplementation(() => { + return [{"Associations":[{"Main":false,"RouteTableAssociationId":"rtbassoc-041509f1a595fa5dd","RouteTableId":"rtb-0e99886b16ecb5710","SubnetId":"subnet-028bf1f940038d771","AssociationState":{"State":"associated"}},{"Main":false,"RouteTableAssociationId":"rtbassoc-0c83e3ec6163f1999","RouteTableId":"rtb-0e99886b16ecb5710","SubnetId":"subnet-0884864b53eaf5171","AssociationState":{"State":"associated"}}],"PropagatingVgws":[],"RouteTableId":"rtb-0e99886b16ecb5710","Routes":[{"DestinationCidrBlock":"192.168.1.0/26","GatewayId":"local","Origin":"CreateRouteTable","State":"active"}],"Tags":[{"Key":"Name","Value":"FirewallSubnetRouteTable"}],"VpcId":"vpc-0ea9f7f530319814a","OwnerId":"1234"}] + }), + createRoute: jest.fn().mockImplementation(() => { + return { + 'Return': true + } + }) + }) + } +}, { virtual: true }); + +test('test the method routeTableOperations - 2 VPCE', async () => { + const syncStates = { + "us-east-1a": { + "Attachment": { + "SubnetId": "subnet-1", + "EndpointId": "vpce-1", + "Status": "READY" + }, + "Config": { + "arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1": {'SyncStatus': "IN_SYNC"}, + "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1": {'SyncStatus': "IN_SYNC"}, + "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1": {'SyncStatus': "IN_SYNC"}, + "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2": {'SyncStatus': "IN_SYNC"} + } + }, + "us-east-1b": { + "Attachment": { + "SubnetId": "subnet-2", + "EndpointId": "vpce-2", + "Status": "READY" + }, + "Config": { + "arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1": {'SyncStatus': "IN_SYNC"}, + "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1": {'SyncStatus': "IN_SYNC"}, + "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1": {'SyncStatus': "IN_SYNC"}, + "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2": {'SyncStatus': "IN_SYNC"} + } + } + + } + + const ec2Mgr = new Ec2Manager(ec2EnvProps, syncStates) + const response = await ec2Mgr.routeTableOperations() + console.log(response) + expect(response[0].VpcEndpointId).toStrictEqual("vpce-1") + expect(response[0].RouteTableId).toStrictEqual("rtb-0e99886b16ecb5710") + expect(response[0].DefaultRouteCreated).toStrictEqual(true) + expect(response[1].VpcEndpointId).toStrictEqual("vpce-2") + expect(response[0].RouteTableId).toStrictEqual("rtb-0e99886b16ecb5710") + expect(response[1].DefaultRouteCreated).toStrictEqual(true) + +}) diff --git a/source/networkFirewallAutomation/__tests__/firewall-config-validation.spec.ts b/source/networkFirewallAutomation/__tests__/firewall-config-validation.spec.ts new file mode 100644 index 0000000..13bad4b --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-config-validation.spec.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { FirewallConfigValidation } from "../lib/common/firewall-config-validation" + +jest.mock("aws-sdk", () => { + return { + __esModule: true, + NetworkFirewall: jest.fn().mockReturnValue({ + createRuleGroup: jest.fn().mockImplementation(() => { + //console.log(`Inside rule group mock ${JSON.stringify(data)}` ) + }), + createFirewallPolicy: jest.fn().mockImplementation(() => { + //console.log(`Inside firewall policy mock ${JSON.stringify(data)}` ) + }), + }) + } +}) + +test('test firewall config validation.', async () => { + const firewallConfigValidation = new FirewallConfigValidation(); + try { + await firewallConfigValidation.execute("/__tests__/firewall-test-configuration/firewalls/") + } catch (error) { + expect(firewallConfigValidation.getInvalidFiles()).toStrictEqual([ + { + "path": "__tests__/firewall-test-configuration/ruleGroups/stateless-fwd-to-stateful.invalid.json", + "referencedInFile": "__tests__/firewall-test-configuration/firewallPolicies/firewall-invalid-policy.json", + "error": "The file in the attribute path is not available in the configuration." + }, + { + "path": "__tests__/firewall-test-configuration/firewallPolicies/firewall-notavailable.json", + "referencedInFile": "__tests__/firewall-test-configuration/firewallPolicies/firewall-notavailable.json", + "error": "The file in the attribute path is not available in the configuration." + } + ]) + } + +}) \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewallPolicies/firewall-invalid-policy.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewallPolicies/firewall-invalid-policy.json new file mode 100644 index 0000000..929842a --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewallPolicies/firewall-invalid-policy.json @@ -0,0 +1,26 @@ +{ + "FirewallPolicyName": "Firewall-Policy-2", + "FirewallPolicy": { + "StatelessDefaultActions": [ + "aws:drop" + ], + "StatelessFragmentDefaultActions": [ + "aws:drop" + ], + "StatelessRuleGroupReferences": [ + { + "Priority": 30, + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateless-fwd-to-stateful.invalid.json" + }, + { + "Priority": 20, + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateless-pass-action.example.json" + } + ], + "StatefulRuleGroupReferences": [ + { + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateful-domainblock.example.json" + } + ] + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewallPolicies/firewall-policy-2.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewallPolicies/firewall-policy-2.json new file mode 100644 index 0000000..1efe322 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewallPolicies/firewall-policy-2.json @@ -0,0 +1,26 @@ +{ + "FirewallPolicyName": "Firewall-Policy-2", + "FirewallPolicy": { + "StatelessDefaultActions": [ + "aws:drop" + ], + "StatelessFragmentDefaultActions": [ + "aws:drop" + ], + "StatelessRuleGroupReferences": [ + { + "Priority": 30, + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateless-fwd-to-stateful.example.json" + }, + { + "Priority": 20, + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateless-pass-action.example.json" + } + ], + "StatefulRuleGroupReferences": [ + { + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateful-domainblock.example.json" + } + ] + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewallPolicies/firewall-policy.example.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewallPolicies/firewall-policy.example.json new file mode 100644 index 0000000..bb5ad6b --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewallPolicies/firewall-policy.example.json @@ -0,0 +1,29 @@ +{ + "FirewallPolicyName": "Firewall-Policy-1", + "FirewallPolicy": { + "StatelessDefaultActions": [ + "aws:drop" + ], + "StatelessFragmentDefaultActions": [ + "aws:drop" + ], + "StatelessRuleGroupReferences": [ + { + "Priority": 30, + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateless-fwd-to-stateful.example.json" + }, + { + "Priority": 20, + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateless-pass-action.example.json" + } + ], + "StatefulRuleGroupReferences": [ + { + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateful-domainblock.example.json" + }, + { + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/suricata-rule-reference.json" + } + ] + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewalls/firewall-invalid.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewalls/firewall-invalid.json new file mode 100644 index 0000000..a9ea930 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewalls/firewall-invalid.json @@ -0,0 +1,8 @@ +{ + "FirewallName": "VpcFirewall-1", + "FirewallPolicyArn": "__tests__/firewall-test-configuration/firewallPolicies/firewall-invalid-policy.json", + "Description": "Network Firewall created by AWS Solutions", + "DeleteProtection": true, + "FirewallPolicyChangeProtection": true, + "SubnetChangeProtection": true + } \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewalls/firewall-nopolicy.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewalls/firewall-nopolicy.json new file mode 100644 index 0000000..ca7272e --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewalls/firewall-nopolicy.json @@ -0,0 +1,8 @@ +{ + "FirewallName": "VpcFirewall-1", + "FirewallPolicyArn": "__tests__/firewall-test-configuration/firewallPolicies/firewall-notavailable.json", + "Description": "Network Firewall created by AWS Solutions", + "DeleteProtection": true, + "FirewallPolicyChangeProtection": true, + "SubnetChangeProtection": true + } \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewalls/firewall.example.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewalls/firewall.example.json new file mode 100644 index 0000000..8bc8538 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/firewalls/firewall.example.json @@ -0,0 +1,8 @@ +{ + "FirewallName": "VpcFirewall-1", + "FirewallPolicyArn": "__tests__/firewall-test-configuration/firewallPolicies/firewall-policy.example.json", + "Description": "Network Firewall created by AWS Solutions", + "DeleteProtection": true, + "FirewallPolicyChangeProtection": true, + "SubnetChangeProtection": true +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/drop.rules b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/drop.rules new file mode 100644 index 0000000..e37904c --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/drop.rules @@ -0,0 +1,79 @@ +# +# $Id: emerging-drop.rules $ +# Emerging Threats Spamhaus DROP List rules. +# +# Rules to block Spamhaus DROP listed networks (www.spamhaus.org) +# +# More information available at www.emergingthreats.net +# +# Please submit any feedback or ideas to emerging@emergingthreats.net or the emerging-sigs mailing list +# +#************************************************************* +# +# Copyright (c) 2003-2020, Emerging Threats +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other materials provided with the distribution. +# * Neither the name of the nor the names of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# + +# VERSION 2793 + + +# Generated 2021-01-10 00:05:02 EDT + +alert ip [2.59.200.0/22,5.134.128.0/19,5.180.4.0/22,5.181.84.0/22,5.183.60.0/22,5.188.10.0/23,24.137.16.0/20,24.170.208.0/20,24.233.0.0/19,24.236.0.0/19,27.126.160.0/20,27.146.0.0/16,31.14.65.0/24,31.14.66.0/23,31.40.156.0/22,31.40.164.0/22,36.0.8.0/21,36.37.48.0/20,36.116.0.0/16,36.119.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 1"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400000; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [42.0.32.0/19,42.1.128.0/17,42.96.0.0/18,42.128.0.0/12,42.160.0.0/12,42.194.128.0/17,42.208.0.0/12,43.229.52.0/22,43.236.0.0/16,43.250.116.0/22,43.252.80.0/22,45.4.128.0/22,45.4.136.0/22,45.6.48.0/22,45.9.148.0/22,45.9.156.0/22,45.10.16.0/22,45.11.184.0/22,45.11.188.0/22,45.41.0.0/18] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 2"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400001; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [45.65.120.0/22,45.65.188.0/22,45.80.28.0/22,45.80.248.0/23,45.80.250.0/23,45.86.20.0/22,45.95.40.0/22,45.114.240.0/22,45.117.52.0/22,45.117.232.0/22,45.119.40.0/22,45.121.204.0/22,45.130.100.0/22,45.135.193.0/24,45.159.56.0/22,45.220.64.0/18,46.102.177.0/24,46.102.178.0/23,46.102.180.0/24,46.102.182.0/23] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 3"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400002; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [58.14.0.0/15,58.145.176.0/21,59.153.60.0/22,60.233.0.0/16,61.11.224.0/19,61.45.251.0/24,64.92.224.0/20,64.250.144.0/20,65.97.48.0/20,67.213.112.0/20,68.66.48.0/20,69.8.64.0/20,69.8.96.0/20,72.1.224.0/20,74.114.148.0/22,76.191.0.0/20,77.36.62.0/24,77.81.84.0/23,77.81.86.0/24,77.81.89.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 4"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400003; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [85.209.4.0/22,86.55.40.0/23,86.55.42.0/23,86.62.28.0/22,86.104.0.0/23,86.104.2.0/24,86.104.212.0/23,86.104.222.0/23,86.104.224.0/23,86.105.2.0/24,86.105.6.0/24,86.105.176.0/24,86.105.178.0/24,86.105.184.0/23,86.105.186.0/24,86.105.229.0/24,86.105.230.0/24,86.105.242.0/23,86.106.10.0/24,86.106.13.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 5"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400004; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [86.106.110.0/23,86.106.114.0/23,86.106.116.0/23,86.106.118.0/24,86.106.138.0/23,86.106.140.0/23,86.106.174.0/23,86.107.72.0/24,86.107.193.0/24,86.107.194.0/23,88.218.40.0/22,88.218.148.0/22,89.32.43.0/24,89.32.170.0/24,89.32.202.0/24,89.33.46.0/23,89.33.116.0/24,89.33.134.0/24,89.33.198.0/23,89.33.200.0/23] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 6"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400005; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [89.34.74.0/24,89.34.102.0/24,89.34.104.0/23,89.35.54.0/24,89.35.89.0/24,89.35.90.0/24,89.36.38.0/23,89.36.136.0/24,89.36.138.0/23,89.36.141.0/24,89.37.92.0/23,89.37.94.0/24,89.37.96.0/24,89.37.129.0/24,89.37.130.0/23,89.37.132.0/23,89.37.134.0/24,89.38.240.0/24,89.39.69.0/24,89.39.212.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 7"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400006; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [89.40.209.0/24,89.41.27.0/24,89.41.28.0/23,89.41.49.0/24,89.41.50.0/23,89.41.189.0/24,89.41.190.0/23,89.42.10.0/24,89.42.152.0/23,89.42.154.0/24,89.45.82.0/24,89.46.47.0/24,91.132.164.0/22,91.197.196.0/22,91.200.12.0/22,91.200.133.0/24,91.200.248.0/22,91.218.236.0/22,91.220.163.0/24,91.229.52.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 8"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400007; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [93.114.51.0/24,93.114.52.0/23,93.114.54.0/24,93.114.58.0/23,93.115.59.0/24,93.119.118.0/23,93.119.120.0/23,93.119.124.0/23,93.120.34.0/24,93.120.46.0/24,94.131.228.0/22,94.154.32.0/22,96.45.144.0/20,98.143.192.0/20,101.42.0.0/16,101.134.0.0/15,101.192.0.0/14,101.203.128.0/19,101.248.0.0/15,102.196.96.0/19] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 9"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400008; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [103.14.208.0/22,103.16.76.0/24,103.23.8.0/22,103.23.124.0/22,103.24.232.0/22,103.30.12.0/22,103.32.0.0/16,103.32.132.0/22,103.34.0.0/16,103.36.64.0/22,103.59.92.0/22,103.73.172.0/22,103.75.36.0/22,103.76.96.0/22,103.76.128.0/22,103.77.32.0/22,103.99.0.0/22,103.100.168.0/22,103.134.144.0/23,103.135.144.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 10"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400009; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [103.197.240.0/22,103.199.88.0/22,103.199.184.0/22,103.205.84.0/22,103.207.160.0/22,103.210.244.0/22,103.215.80.0/22,103.225.72.0/22,103.225.128.0/22,103.226.192.0/22,103.228.60.0/22,103.229.36.0/22,103.230.144.0/22,103.232.136.0/22,103.232.172.0/22,103.236.32.0/22,103.239.28.0/22,103.239.56.0/22,103.243.8.0/22,103.243.124.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 11"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400010; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [104.239.0.0/17,104.243.192.0/20,104.247.96.0/19,104.250.192.0/19,104.250.224.0/19,104.251.192.0/20,106.95.0.0/16,107.182.112.0/20,107.182.240.0/20,107.190.160.0/20,110.41.0.0/16,111.223.192.0/19,113.212.128.0/19,116.144.0.0/15,116.146.0.0/15,117.58.0.0/17,119.58.0.0/16,119.232.0.0/16,120.48.0.0/15,121.46.124.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 12"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400011; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [124.157.0.0/18,124.242.0.0/16,125.31.192.0/18,125.58.0.0/18,125.169.0.0/16,128.24.0.0/16,128.85.0.0/16,130.21.0.0/16,130.148.0.0/16,130.196.0.0/16,130.222.0.0/16,131.108.16.0/22,131.143.0.0/16,131.200.0.0/16,132.255.132.0/22,134.18.0.0/16,134.22.0.0/16,134.23.0.0/16,134.33.0.0/16,134.127.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 13"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400012; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [137.72.0.0/16,137.76.0.0/16,137.105.0.0/16,137.114.0.0/16,137.218.0.0/16,138.31.0.0/16,138.36.92.0/22,138.36.136.0/22,138.52.0.0/16,138.59.4.0/22,138.59.204.0/22,138.94.144.0/22,138.94.216.0/22,138.97.156.0/22,138.122.192.0/22,138.125.0.0/16,138.185.116.0/22,138.186.208.0/22,138.216.0.0/16,138.219.172.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 14"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400013; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [140.82.96.0/20,140.167.0.0/16,141.98.68.0/23,141.98.70.0/23,141.136.22.0/24,141.178.0.0/16,141.206.128.0/20,141.253.0.0/16,142.102.0.0/16,143.0.236.0/22,143.49.0.0/16,143.135.0.0/16,143.136.0.0/16,143.253.0.0/16,145.231.0.0/16,146.3.0.0/16,146.51.0.0/16,146.106.0.0/16,146.183.0.0/16,146.202.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 15"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400014; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [148.148.0.0/16,148.154.0.0/16,148.178.0.0/16,148.185.0.0/16,148.248.0.0/16,149.118.0.0/16,149.207.0.0/16,150.10.0.0/16,150.22.128.0/17,150.25.0.0/16,150.40.0.0/16,150.121.0.0/16,150.129.212.0/22,150.129.228.0/22,150.141.0.0/16,150.242.120.0/22,150.242.144.0/22,151.212.0.0/16,152.89.228.0/23,152.89.230.0/23] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 16"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400015; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [155.11.0.0/16,155.40.0.0/16,155.66.0.0/16,155.71.0.0/16,155.73.0.0/16,155.108.0.0/16,155.159.0.0/16,155.235.0.0/16,155.249.0.0/16,156.96.0.0/16,157.115.0.0/16,157.162.0.0/16,157.186.0.0/16,157.195.0.0/16,158.54.0.0/16,158.249.0.0/16,159.80.0.0/16,159.85.0.0/16,159.151.0.0/16,159.174.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 17"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400016; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [160.116.0.0/16,160.117.0.0/16,160.121.0.0/16,160.122.0.0/16,160.180.0.0/16,160.184.0.0/16,160.188.0.0/16,160.200.0.0/16,160.235.0.0/16,160.240.0.0/16,160.255.0.0/16,161.0.0.0/19,161.0.68.0/22,161.1.0.0/16,162.208.124.0/22,162.212.188.0/22,162.222.128.0/21,162.249.20.0/22,163.47.19.0/24,163.50.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 18"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400017; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [163.216.0.0/19,163.250.0.0/16,163.254.0.0/16,164.6.0.0/16,164.79.0.0/16,164.88.0.0/16,164.137.0.0/16,164.155.0.0/16,165.3.0.0/16,165.25.0.0/16,165.52.0.0/14,165.102.0.0/16,165.205.0.0/16,165.209.0.0/16,165.231.0.0/16,166.93.0.0/16,166.117.0.0/16,167.74.0.0/18,167.82.144.0/20,167.97.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 19"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400018; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [167.224.0.0/19,167.224.32.0/20,167.224.48.0/21,167.249.200.0/22,168.0.212.0/22,168.64.0.0/16,168.76.0.0/16,168.80.0.0/15,168.90.96.0/22,168.129.0.0/16,168.151.0.0/22,168.151.4.0/23,168.151.6.0/24,168.151.32.0/21,168.151.43.0/24,168.151.44.0/22,168.151.48.0/22,168.151.52.0/23,168.151.54.0/24,168.151.56.0/21] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 20"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400019; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [168.151.128.0/20,168.151.145.0/24,168.151.146.0/23,168.151.148.0/22,168.151.152.0/22,168.151.157.0/24,168.151.158.0/23,168.151.160.0/20,168.151.176.0/21,168.151.184.0/22,168.151.192.0/20,168.151.208.0/21,168.151.216.0/22,168.151.220.0/23,168.151.232.0/21,168.151.240.0/21,168.151.248.0/22,168.151.254.0/24,168.181.52.0/22,168.195.76.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 21"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400020; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [168.211.0.0/16,168.227.128.0/22,168.227.140.0/22,169.239.152.0/22,170.67.0.0/16,170.83.232.0/22,170.113.0.0/16,170.120.0.0/16,170.179.0.0/16,170.244.40.0/22,170.244.240.0/22,170.247.220.0/22,171.25.212.0/22,171.26.0.0/16,172.98.0.0/18,174.136.192.0/18,175.103.64.0/18,176.56.192.0/19,176.96.88.0/21,176.102.120.0/21] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 22"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400021; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [176.223.116.0/23,176.223.118.0/24,176.223.160.0/23,177.234.136.0/21,178.212.184.0/21,178.213.176.0/22,179.63.0.0/17,180.178.192.0/18,180.236.0.0/14,181.177.64.0/18,185.0.96.0/19,185.21.8.0/22,185.30.168.0/22,185.39.8.0/22,185.55.4.0/22,185.55.140.0/22,185.60.201.0/24,185.60.202.0/23,185.63.35.0/24,185.64.23.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 23"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400022; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [185.116.172.0/23,185.116.175.0/24,185.120.8.0/22,185.122.128.0/22,185.123.144.0/20,185.123.248.0/21,185.124.0.0/22,185.124.56.0/21,185.126.136.0/22,185.126.148.0/22,185.126.160.0/21,185.126.224.0/22,185.126.236.0/22,185.126.248.0/22,185.127.44.0/22,185.127.56.0/22,185.127.68.0/22,185.127.76.0/22,185.127.92.0/22,185.129.8.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 24"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400023; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [185.144.180.0/22,185.147.140.0/22,185.156.88.0/21,185.156.92.0/22,185.161.148.0/22,185.165.24.0/22,185.180.192.0/22,185.184.192.0/22,185.185.48.0/22,185.193.90.0/24,185.193.143.0/24,185.194.100.0/22,185.203.64.0/22,185.215.132.0/22,185.227.200.0/22,185.230.44.0/22,185.234.64.0/22,185.237.104.0/22,185.237.220.0/22,185.237.226.0/23] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 25"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400024; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [188.172.160.0/19,188.208.48.0/22,188.208.52.0/22,188.208.109.0/24,188.208.220.0/22,188.209.120.0/21,188.212.254.0/24,188.213.23.0/24,188.213.206.0/23,188.213.214.0/23,188.213.248.0/22,188.213.252.0/22,188.214.94.0/24,188.214.95.0/24,188.214.140.0/24,188.214.155.0/24,188.214.193.0/24,188.241.211.0/24,188.247.230.0/24,190.123.208.0/20] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 26"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400025; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [192.31.212.0/23,192.40.29.0/24,192.43.160.0/24,192.43.175.0/24,192.43.176.0/21,192.43.184.0/24,192.54.110.0/24,192.67.16.0/24,192.96.146.0/24,192.101.44.0/24,192.101.181.0/24,192.101.200.0/21,192.101.240.0/21,192.101.248.0/23,192.133.3.0/24,192.152.194.0/24,192.154.11.0/24,192.160.44.0/24,192.161.80.0/20,192.190.49.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 27"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400026; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [192.219.120.0/21,192.219.128.0/18,192.219.192.0/20,192.219.208.0/21,192.226.16.0/20,192.229.32.0/19,192.231.66.0/24,192.234.189.0/24,192.245.101.0/24,192.251.231.0/24,192.252.16.0/20,193.25.48.0/20,193.30.254.0/23,193.32.66.0/23,193.46.172.0/22,193.139.0.0/16,193.151.160.0/22,193.201.232.0/22,193.228.91.0/24,193.243.0.0/17] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 28"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400027; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [196.1.109.0/24,196.10.64.0/19,196.15.64.0/18,196.16.0.0/14,196.42.128.0/17,196.61.192.0/20,196.62.0.0/16,196.192.192.0/18,196.193.0.0/16,196.194.0.0/15,196.199.0.0/16,196.207.64.0/18,196.246.0.0/16,197.154.0.0/16,197.231.208.0/22,198.13.0.0/20,198.14.0.0/20,198.20.16.0/20,198.45.32.0/20,198.45.64.0/19] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 29"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400028; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [198.96.224.0/20,198.99.117.0/24,198.102.222.0/24,198.148.212.0/24,198.151.16.0/20,198.151.64.0/18,198.151.152.0/22,198.160.205.0/24,198.169.201.0/24,198.177.175.0/24,198.177.176.0/22,198.177.180.0/24,198.177.214.0/24,198.178.64.0/19,198.179.22.0/24,198.181.96.0/20,198.183.32.0/19,198.184.193.0/24,198.184.208.0/24,198.186.25.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 30"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400029; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [198.200.8.0/23,198.202.237.0/24,198.204.0.0/21,198.206.140.0/24,198.212.132.0/24,199.5.152.0/23,199.5.229.0/24,199.26.137.0/24,199.26.207.0/24,199.26.251.0/24,199.33.222.0/24,199.34.128.0/18,199.60.102.0/24,199.71.192.0/20,199.73.64.0/20,199.84.16.0/20,199.84.55.0/24,199.84.56.0/22,199.84.60.0/24,199.84.64.0/19] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 31"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400030; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [199.184.82.0/24,199.185.144.0/20,199.185.192.0/20,199.196.192.0/19,199.198.160.0/20,199.198.176.0/21,199.198.184.0/23,199.198.188.0/22,199.200.64.0/19,199.212.96.0/20,199.223.0.0/20,199.230.64.0/19,199.230.96.0/21,199.233.85.0/24,199.233.96.0/24,199.241.64.0/19,199.244.56.0/21,199.245.138.0/24,199.246.137.0/24,199.246.213.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 32"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400031; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [199.254.32.0/20,200.0.60.0/23,200.22.0.0/16,200.71.124.0/22,200.189.44.0/22,200.234.128.0/18,201.148.168.0/22,201.169.0.0/16,202.0.192.0/18,202.20.32.0/19,202.21.64.0/19,202.27.96.0/23,202.27.98.0/24,202.27.99.0/24,202.27.100.0/22,202.27.120.0/22,202.27.161.0/24,202.27.162.0/23,202.27.164.0/22,202.27.168.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 33"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400032; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [202.148.176.0/20,202.183.0.0/19,202.189.80.0/20,203.2.200.0/22,203.9.0.0/19,203.31.88.0/23,203.34.70.0/23,203.86.252.0/22,203.169.0.0/22,203.191.64.0/18,203.195.0.0/18,204.14.80.0/22,204.19.38.0/23,204.44.32.0/20,204.44.208.0/20,204.44.224.0/20,204.52.96.0/19,204.52.255.0/24,204.57.16.0/20,204.75.147.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 34"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400033; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [204.106.128.0/18,204.106.192.0/19,204.107.208.0/24,204.126.244.0/23,204.128.32.0/20,204.128.151.0/24,204.128.180.0/24,204.130.16.0/20,204.130.167.0/24,204.147.64.0/21,204.147.96.0/20,204.147.240.0/20,204.156.192.0/20,204.194.64.0/21,204.225.159.0/24,204.225.210.0/24,204.232.0.0/18,204.238.137.0/24,204.238.170.0/24,204.238.183.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 35"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400034; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [205.148.192.0/18,205.151.128.0/19,205.159.45.0/24,205.159.174.0/24,205.159.180.0/24,205.166.77.0/24,205.166.84.0/24,205.166.130.0/24,205.166.168.0/24,205.166.211.0/24,205.172.244.0/22,205.175.160.0/19,205.189.71.0/24,205.189.72.0/23,205.203.0.0/19,205.203.224.0/19,205.207.134.0/24,205.210.107.0/24,205.210.139.0/24,205.210.171.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 36"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400035; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [205.236.189.0/24,205.237.88.0/21,206.41.128.0/20,206.41.160.0/19,206.51.29.0/24,206.124.104.0/21,206.125.16.0/20,206.130.188.0/24,206.143.128.0/17,206.183.128.0/19,206.195.224.0/19,206.197.28.0/24,206.197.29.0/24,206.197.77.0/24,206.197.165.0/24,206.209.48.0/20,206.209.80.0/20,206.223.17.0/24,206.224.160.0/19,206.226.0.0/19] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 37"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400036; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [207.90.0.0/18,207.110.64.0/18,207.110.96.0/19,207.110.128.0/18,207.183.64.0/19,207.183.96.0/20,207.183.128.0/19,207.183.192.0/19,207.201.64.0/18,207.228.192.0/20,207.244.0.0/18,208.73.208.0/22,208.90.32.0/21,208.93.4.0/22,209.17.192.0/19,209.66.0.0/18,209.66.128.0/19,209.95.64.0/19,209.95.192.0/19,209.99.128.0/18] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 38"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400037; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [209.145.0.0/19,209.148.16.0/20,209.161.64.0/19,209.161.96.0/20,209.182.64.0/19,209.242.192.0/19,212.162.152.0/22,213.173.36.0/22,213.247.0.0/19,216.179.128.0/17,220.154.0.0/16,221.132.192.0/18,223.0.0.0/15,223.169.0.0/16,223.173.0.0/16,223.254.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 39"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400038; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/stateful-domainblock.example.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/stateful-domainblock.example.json new file mode 100644 index 0000000..ded9f03 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/stateful-domainblock.example.json @@ -0,0 +1,15 @@ +{ + "RuleGroupName": "StatefulRulesExample1", + "RuleGroup": { + "RulesSource": { + "RulesSourceList": { + "Targets": [ "test.example.com" ], + "TargetTypes": [ "HTTP_HOST", "TLS_SNI" ], + "GeneratedRulesType": "DENYLIST" + } + } + }, + "Type": "STATEFUL", + "Description": "Stateful Rule3", + "Capacity": 100 +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/stateless-fwd-to-stateful.example.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/stateless-fwd-to-stateful.example.json new file mode 100644 index 0000000..70a9a42 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/stateless-fwd-to-stateful.example.json @@ -0,0 +1,41 @@ +{ + "RuleGroupName": "StatelessExample2", + "RuleGroup": { + "RulesSource": { + "StatelessRulesAndCustomActions": { + "StatelessRules": [ + { + "RuleDefinition": { + "MatchAttributes": { + "Sources": [ + { + "AddressDefinition": "192.0.2.0/8" + } + ], + "Destinations": [ + { + "AddressDefinition": "124.1.1.5/32" + }, + { + "AddressDefinition": "198.51.100.0/16" + } + ], + "Protocols": [ + 6, + 17 + ] + }, + "Actions": [ + "aws:forward_to_sfe" + ] + }, + "Priority": 100 + } + ] + } + } + }, + "Type": "STATELESS", + "Description": "Stateless Rule with Forward to Stateful3", + "Capacity": 220 +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/stateless-pass-action.example.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/stateless-pass-action.example.json new file mode 100644 index 0000000..c97b849 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/stateless-pass-action.example.json @@ -0,0 +1,68 @@ +{ + "RuleGroupName": "StatelessExample1", + "RuleGroup": { + "RulesSource": { + "StatelessRulesAndCustomActions": { + "StatelessRules": [ + { + "RuleDefinition": { + "MatchAttributes": { + "Sources": [ + { + "AddressDefinition": "192.0.2.0/8" + } + ], + "Destinations": [ + { + "AddressDefinition": "198.51.100.0/16" + } + ], + "SourcePorts": [ + { + "FromPort": 53, + "ToPort": 53 + }, + { + "FromPort": 1001, + "ToPort": 1053 + } + ], + "DestinationPorts": [ + { + "FromPort": 53, + "ToPort": 53 + }, + { + "FromPort": 1001, + "ToPort": 1053 + } + ], + "Protocols": [ + 6 + ], + "TCPFlags": [ + { + "Flags": [ + "SYN" + ], + "Masks": [ + "SYN", + "ACK" + ] + } + ] + }, + "Actions": [ + "aws:pass" + ] + }, + "Priority": 19 + } + ] + } + } + }, + "Type": "STATELESS", + "Description": "Stateless Rule with pass action", + "Capacity": 199 +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/suricata-rule-reference.json b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/suricata-rule-reference.json new file mode 100644 index 0000000..1f99720 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/firewall-test-configuration/ruleGroups/suricata-rule-reference.json @@ -0,0 +1,8 @@ + +{ + "RuleGroupName": "suricata-icmp-rules2", + "Rules": "__tests__/firewall-test-configuration/ruleGroups/drop.rules", + "Type": "STATEFUL", + "Description": "Suricata rule group", + "Capacity": 100 + } \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/network-firewall-manager.spec.ts b/source/networkFirewallAutomation/__tests__/network-firewall-manager.spec.ts new file mode 100644 index 0000000..da0c3f5 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/network-firewall-manager.spec.ts @@ -0,0 +1,327 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { NetworkFirewallManager } from '../lib/network-firewall-manager'; +import { ConfigReader } from '../lib/common/configReader/config-reader'; + + +jest.mock("aws-sdk", () => { + return { + __esModule: true, + NetworkFirewall: jest.fn().mockReturnValue({ + + }) + } +}, { virtual: true }); + +jest.mock("../lib/service/network-firewall-service", () => { + return { + __esModule: true, + NetworkFirewallService: jest.fn().mockReturnValue({ + describeRuleGroup: jest.fn().mockImplementation((data) => { + const StatelessExample2Describe = { "UpdateToken": "c7007261-d236-4997-8eab-7e15445c84a2", "RuleGroupResponse": { "RuleGroupArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2", "RuleGroupName": "StatelessExample2", "RuleGroupId": "206bd83b-3b59-4000-9ff3-3fe369f34719", "Description": "Stateless Rule with Forward to Stateful3", "Type": "STATELESS", "Capacity": 220, "RuleGroupStatus": "ACTIVE", "Tags": [] } } + const StatelessExample1Describe = { "UpdateToken": "9b5bc310-99d4-45c9-a16e-bdb58f883a48", "RuleGroup": { "RulesSource": { "StatelessRulesAndCustomActions": { "StatelessRules": [{ "RuleDefinition": { "MatchAttributes": { "Sources": [{ "AddressDefinition": "192.0.2.0/8" }], "Destinations": [{ "AddressDefinition": "198.51.100.0/16" }], "SourcePorts": [{ "FromPort": 53, "ToPort": 53 }, { "FromPort": 1001, "ToPort": 1053 }], "DestinationPorts": [{ "FromPort": 53, "ToPort": 53 }, { "FromPort": 1001, "ToPort": 1053 }], "Protocols": [6], "TCPFlags": [{ "Flags": ["SYN"], "Masks": ["SYN", "ACK"] }] }, "Actions": ["aws:drop"] }, "Priority": 19 }], "CustomActions": [{ "ActionName": "CustomAction", "ActionDefinition": { "PublishMetricAction": { "Dimensions": [{ "Value": "test" }] } } }] } } }, "RuleGroupResponse": { "RuleGroupArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1", "RuleGroupName": "StatelessExample1", "RuleGroupId": "7246cfe2-00c7-4ef9-8d47-2b80bf8840e5", "Description": "Stateless Rule with Custom Action2", "Type": "STATELESS", "Capacity": 199, "RuleGroupStatus": "ACTIVE", "Tags": [] } } + const StatefulRulesExample1Describe = { "UpdateToken": "dd7696c5-e2cd-4882-a560-21e28570fc0f", "RuleGroup": { "RulesSource": { "RulesSourceList": { "Targets": ["test.example.com"], "TargetTypes": ["HTTP_HOST", "TLS_SNI"], "GeneratedRulesType": "DENYLIST" } } }, "RuleGroupResponse": { "RuleGroupArn": "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1", "RuleGroupName": "StatefulRulesExample1", "RuleGroupId": "2560e622-5d9e-4c5c-9680-958bcb5c231b", "Description": "Stateful Rule2", "Type": "STATEFUL", "Capacity": 100, "RuleGroupStatus": "ACTIVE", "Tags": [] } } + const suricataRuleGroup = { + UpdateToken: '72e4e89b-acec-4184-b033-2dab8dd2a35f', + RuleGroupResponse: { + RuleGroupArn: 'arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/suricata-icmp-rules2', + RuleGroupName: 'suricata-icmp-rules2', + RuleGroupId: 'f593c04a-079c-423f-8558-b02a8c0edb0e', + Type: 'STATEFUL', + Capacity: 300, + RuleGroupStatus: 'ACTIVE' + } + } + + if (data === 'StatelessExample2') { + return StatelessExample2Describe + } else if (data === 'StatelessExample1') { + return StatelessExample1Describe + } else if (data === 'StatefulRulesExample1') { + return StatefulRulesExample1Describe; + } else if(data === 'suricata-icmp-rules2') { + return suricataRuleGroup; + } + return '' + }), + updateRuleGroup: jest.fn().mockImplementation((data) => { + const StatelessExample2Update = { "UpdateToken": "7fa52fd2-6b3a-41c5-8356-359d17a01ac0", "RuleGroup": { "RulesSource": { "StatelessRulesAndCustomActions": { "StatelessRules": [{ "RuleDefinition": { "MatchAttributes": { "Sources": [{ "AddressDefinition": "192.0.2.0/8" }], "Destinations": [{ "AddressDefinition": "124.1.1.5/32" }, { "AddressDefinition": "198.51.100.0/16" }], "Protocols": [6, 17] }, "Actions": ["aws:forward_to_sfe"] }, "Priority": 100 }] } } }, "RuleGroupResponse": { "RuleGroupArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2", "RuleGroupName": "StatelessExample2", "RuleGroupId": "206bd83b-3b59-4000-9ff3-3fe369f34719", "Description": "Stateless Rule with Forward to Stateful2", "Type": "STATELESS", "Capacity": 220, "RuleGroupStatus": "ACTIVE", "Tags": [] } } + const StatelessExample1Update = { "UpdateToken": "327d0dca-e671-46bc-9ed7-83cf51773868", "RuleGroupResponse": { "RuleGroupArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1", "RuleGroupName": "StatelessExample1", "RuleGroupId": "7246cfe2-00c7-4ef9-8d47-2b80bf8840e5", "Description": "Stateless Rule with Custom Action3", "Type": "STATELESS", "Capacity": 199, "RuleGroupStatus": "ACTIVE", "Tags": [] } } + const StatefulRulesExample1Update = { "UpdateToken": "cc4687e1-f370-4e10-abfc-12984e1d62e7", "RuleGroupResponse": { "RuleGroupArn": "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1", "RuleGroupName": "StatefulRulesExample1", "RuleGroupId": "2560e622-5d9e-4c5c-9680-958bcb5c231b", "Description": "Stateful Rule3", "Type": "STATEFUL", "Capacity": 100, "RuleGroupStatus": "ACTIVE", "Tags": [] } } + if (data["RuleGroupArn"] === 'arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2') { + return StatelessExample2Update + } else if (data["RuleGroupArn"] === 'arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1') { + return StatelessExample1Update + } else if (data["RuleGroupArn"] === 'arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1') { + return StatefulRulesExample1Update; + } + return '' + }), + createRuleGroup: jest.fn().mockImplementation(() => { + //console.log(`Inside createRuleGroup mock ${JSON.stringify(data)}`); + }), + listRuleGroupsForPolicy: jest.fn().mockImplementation(() => { + return '' + }), + describeFirewall: jest.fn().mockImplementation(() => { + //console.log(`Inside describeFirewall mock ${JSON.stringify(data)}`); + return { Firewall: { "FirewallName": "VpcFirewall-1", "FirewallPolicyArn": "arn:aws:network-firewall:us-east-1:1234::firewall/*", "Description": "NetworkFirewallcreatedbyAWSSolutions", "VpcId": "vpc-1", "SubnetMappings": [{ "SubnetId": "subnet-1" }, { "SubnetId": "subnet-2" },], "DeleteProtection": true, "SubnetChangeProtection": true, "FirewallPolicyChangeProtection": true, "FirewallId": "string", "Tags": [{ "Key": "string", "Value": "string" },] }, FirewallStatus: { "Status": "READY", "ConfigurationSyncStateSummary": "IN_SYNC", "SyncStates": { "us-east-1a": { "Attachment": { "SubnetId": "subnet-1", "EndpointId": "vpce-1", "Status": "READY" }, "Config": { "arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2": {'SyncStatus': "IN_SYNC"} } }, "us-east-1b": { "Attachment": { "SubnetId": "subnet-2", "EndpointId": "vpce-2", "Status": "READY" }, "Config": { "arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2": {'SyncStatus': "IN_SYNC"} } } } } } + }), + describeFirewallPolicy: jest.fn().mockImplementation((data) => { + if (data && data === "Firewall-Policy-2") { + return Promise.resolve({ + UpdateToken: 'aaa', + FirewallPolicyResponse: { + FirewallPolicyName: 'Firewall-Policy-2', + FirewallPolicyArn: 'arn:aws', + FirewallPolicyId: 100 + } + }) + } + return Promise.resolve() + }), + createFirewallPolicy: jest.fn().mockImplementation(() => { + //console.log(`Inside describeFirewallPolicy mock ${JSON.stringify(data)}`); + return { "FirewallPolicyResponse": { "FirewallPolicyName": "Firewall-Policy-1", "Description": "FirewallPolicy1", "FirewallPolicyArn": "arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1", "FirewallPolicyStatus": "ACTIVE", "Tags": [{ "Key": "string", "Value": "string" }] }, "FirewallPolicy": { "StatelessDefaultActions": ["aws:drop"], "StatelessFragmentDefaultActions": ["aws:drop"], "StatelessRuleGroupReferences": [{ "Priority": 30, "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2" }, { "Priority": 20, "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1" }], "StatefulRuleGroupReferences": [{ "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1" }] } } + }), + createFirewall: jest.fn().mockImplementation(() => { + //console.log(`Inside describeFirewallPolicy mock ${JSON.stringify(data)}`); + return { "FirewallResponse": { "Firewall": { "FirewallName": "VpcFirewall-1", "FirewallPolicyArn": "arn:aws:network-firewall:us-east-1:1234::firewall/*", "Description": "NetworkFirewallcreatedbyAWSSolutions", "VpcId": "vpc-1", "SubnetMappings": [{ "SubnetId": "subnet-1" }, { "SubnetId": "subnet-2" }], "DeleteProtection": true, "SubnetChangeProtection": true, "FirewallPolicyChangeProtection": true, "FirewallId": "string", "Tags": [{ "Key": "string", "Value": "string" }] }, "FirewallStatus": { "Status": "READY", "ConfigurationSyncStateSummary": "IN_SYNC", "SyncStates": { "us-east-1a": { "Attachment": { "SubnetId": "subnet-1", "EndpointId": "vpce-1", "Status": "READY" }, "Config": { "arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2": {'SyncStatus': "IN_SYNC"} } }, "us-east-1b": { "Attachment": { "SubnetId": "subnet-2", "EndpointId": "vpce-2", "Status": "READY" }, "Config": { "arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1": {'SyncStatus': "IN_SYNC"}, "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2": {'SyncStatus': "IN_SYNC"} } } } } } } + }), + updateLoggingConfiguration: jest.fn().mockImplementation(() => { + return {} + }), + updateFirewallPolicy: jest.fn().mockImplementation(() => { + return { + + } + }), + associateFirewallPolicy: jest.fn().mockImplementation((data) => { + expect(data["FirewallPolicyArn"]).toBe("arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1"); + }), + updateFirewallDeleteProtection: jest.fn().mockImplementation((data) => { + expect(data["DeleteProtection"]).toBeTruthy(); + }), + updateFirewallPolicyChangeProtection: jest.fn().mockImplementation((data) => { + expect(data["FirewallPolicyChangeProtection"]).toBeTruthy() + }), + updateSubnetChangeProtection: jest.fn().mockImplementation((data) => { + expect(data["SubnetChangeProtection"]).toBeTruthy(); + }), + updateFirewallDescription: jest.fn().mockImplementation((data) => { + expect(data["Description"]).toBe("Network Firewall created by AWS Solutions") + }) + }) + } +}, { virtual: true }); + +test('test the method ruleGroupExist.', async () => { + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + const managerInstance = new NetworkFirewallManager( + { vpcId: '', subnetIds: '', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: '' }, firewallObject, new ConfigReader()); + + //load the firewall policy + const policyObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewallPolicies/firewall-policy.example.json') + + const response = await managerInstance.ruleGroupOperations(policyObject); + + expect(response).toStrictEqual({ "FirewallPolicyName": "Firewall-Policy-1", "FirewallPolicy": { "StatelessDefaultActions": ["aws:drop"], "StatelessFragmentDefaultActions": ["aws:drop"], "StatelessRuleGroupReferences": [{ "Priority": 30, "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2" }, { "Priority": 20, "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1" }], "StatefulRuleGroupReferences": [{ "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1" }, { "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/suricata-icmp-rules2" }] } }) +}) + +test('test the method ruleGroupExist error scenario.', async () => { + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + const managerInstance = new NetworkFirewallManager( + { vpcId: '', subnetIds: '', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: '' }, firewallObject, new ConfigReader()); + + const policyObject = { + FirewallPolicyName: 'Firewall-Policy-1', + FirewallPolicy: { + StatelessDefaultActions: ['aws:drop'], + StatelessFragmentDefaultActions: ['aws:drop'], + StatelessRuleGroupReferences: [{ + "Priority": 30, + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateless-fwd-to-stateful.example.json" + }, + { + "Priority": 20, + "ResourceArn": "__tests__/firewall-test-configuration/ruleGroups/stateless-pass-action.example.json" + }], + StatefulRuleGroupReferences: [{ + "ResourceArn": "error" + }] + } + } + + await expect(managerInstance.ruleGroupOperations(policyObject)).rejects.toThrowError("Error: ENOENT: no such file or directory, open 'error'") + +}) + +test('test the method firewallExist.', async () => { + + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + const managerInstance = new NetworkFirewallManager( + { vpcId: 'vpc-1', subnetIds: 'subnet-1, subnet-2', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + + const response = await managerInstance.firewallOperations(); + expect(response).toStrictEqual({ + 'us-east-1a': { + Attachment: { SubnetId: 'subnet-1', EndpointId: 'vpce-1', Status: 'READY' }, + Config: { + 'arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1': {'SyncStatus': "IN_SYNC"}, + 'arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1': {'SyncStatus': "IN_SYNC"}, + 'arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1': {'SyncStatus': "IN_SYNC"}, + 'arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2': {'SyncStatus': "IN_SYNC"} + } + }, + 'us-east-1b': { + Attachment: { SubnetId: 'subnet-2', EndpointId: 'vpce-2', Status: 'READY' }, + Config: { + 'arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1': {'SyncStatus': "IN_SYNC"}, + 'arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1': {'SyncStatus': "IN_SYNC"}, + 'arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1': {'SyncStatus': "IN_SYNC"}, + 'arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2': {'SyncStatus': "IN_SYNC"} + } + } + }) +}) + + +test('firewall policy already exists', async () => { + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + const managerInstance = new NetworkFirewallManager( + { vpcId: 'vpc-1', subnetIds: 'subnet-1, subnet-2', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + + const response = await managerInstance.firewallPolicyOperations("__tests__/firewall-test-configuration/firewallPolicies/firewall-policy-2.json") + expect(response).toBe("arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1") +}) + + +test('test the logging configuration object creation from environment variables', async () => { + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + let managerInstance = new NetworkFirewallManager( + { vpcId: '', subnetIds: '', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + + let loggingConfiguration = await managerInstance.createLoggingConfigurations() + + expect(loggingConfiguration.length).toBe(1) + expect(loggingConfiguration[0].LogType).toBe("ALERT") + expect(loggingConfiguration[0].LogDestinationType).toBe("S3") + expect(JSON.stringify(loggingConfiguration[0].LogDestination)).toStrictEqual("{\"bucketName\":\"test-bucket\",\"prefix\":\"alerts\"}") + + + managerInstance = new NetworkFirewallManager( + { vpcId: '', subnetIds: '', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'FLOW', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + + loggingConfiguration = await managerInstance.createLoggingConfigurations() + + expect(loggingConfiguration.length).toBe(1) + expect(loggingConfiguration[0].LogType).toBe("FLOW") + expect(loggingConfiguration[0].LogDestinationType).toBe("S3") + expect(JSON.stringify(loggingConfiguration[0].LogDestination)).toStrictEqual("{\"bucketName\":\"test-bucket\",\"prefix\":\"flow\"}") + + managerInstance = new NetworkFirewallManager( + { vpcId: '', subnetIds: '', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'EnableBoth', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + + loggingConfiguration = await managerInstance.createLoggingConfigurations() + + expect(loggingConfiguration.length).toBe(2) + expect(loggingConfiguration[0].LogType).toBe("ALERT") + expect(loggingConfiguration[0].LogDestinationType).toBe("S3") + expect(JSON.stringify(loggingConfiguration[0].LogDestination)).toStrictEqual("{\"bucketName\":\"test-bucket\",\"prefix\":\"alerts\"}") + expect(loggingConfiguration[1].LogType).toBe("FLOW") + expect(loggingConfiguration[1].LogDestinationType).toBe("S3") + expect(JSON.stringify(loggingConfiguration[1].LogDestination)).toStrictEqual("{\"bucketName\":\"test-bucket\",\"prefix\":\"flow\"}") + + managerInstance = new NetworkFirewallManager( + { vpcId: '', subnetIds: '', logDestinationType: 'CloudWatchLogs', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'EnableBoth', logDestination: 'log-group-name' }, firewallObject, new ConfigReader()); + + loggingConfiguration = await managerInstance.createLoggingConfigurations() + + expect(loggingConfiguration.length).toBe(2) + expect(loggingConfiguration[0].LogType).toBe("ALERT") + expect(loggingConfiguration[0].LogDestinationType).toBe("CloudWatchLogs") + expect(JSON.stringify(loggingConfiguration[0].LogDestination)).toStrictEqual("{\"logGroup\":\"log-group-name\"}") + + expect(loggingConfiguration[1].LogType).toBe("FLOW") + expect(loggingConfiguration[1].LogDestinationType).toBe("CloudWatchLogs") + expect(JSON.stringify(loggingConfiguration[1].LogDestination)).toStrictEqual("{\"logGroup\":\"log-group-name\"}") +}); + +test('subnet mappings function should return an array', () => { + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + const managerInstance = new NetworkFirewallManager( + { vpcId: 'vpc-1', subnetIds: 'subnet-1, subnet-2', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + expect(managerInstance.getSubnetMapping()).toStrictEqual([ { SubnetId: 'subnet-1' }, { SubnetId: ' subnet-2' } ]) +}) +test('subnet mappings function should return an array --error scenario', () => { + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + const managerInstance = new NetworkFirewallManager( + { vpcId: 'vpc-1', subnetIds: '', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + try { + managerInstance.getSubnetMapping() + } catch(error) { + expect(error["message"]).toBe("Subnet IDs must be in the environment variables") + } +}) + +test('vpc id should be return from environment variable', () => { + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + const managerInstance = new NetworkFirewallManager( + { vpcId: 'vpc-1', subnetIds: 'subnet-1, subnet-2', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + expect(managerInstance.getVpcId()).toBe("vpc-1") +}) + +test('vpc id should be return from environment variable --error scenario', () => { + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + const managerInstance = new NetworkFirewallManager( + { vpcId: '', subnetIds: 'subnet-1, subnet-2', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + try { + managerInstance.getVpcId() + } catch (error) { + expect(error["message"]).toBe("VPC ID must be in the environment variables") + } +}) + +test('Update firewall properties', async () => { + const fileHandler = new ConfigReader(); + let firewallObject = fileHandler.convertFileToObject('__tests__/firewall-test-configuration/firewalls/firewall.example.json') + const managerInstance = new NetworkFirewallManager( + { vpcId: '', subnetIds: 'subnet-1, subnet-2', logDestinationType: 'S3', logRetentionPeriod: "90", stackId : 'f449b250-b969-11e0-a185-5081d0136786', logType: 'ALERT', logDestination: 'test-bucket' }, firewallObject, new ConfigReader()); + + await managerInstance.updateFirewall({ + Firewall: { + FirewallId: '12345', + FirewallPolicyArn: 'arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-2', + SubnetMappings: [], + VpcId: '', + DeleteProtection: false, + Description: '', + FirewallName: 'VpcFirewall-1', + FirewallArn: '', + FirewallPolicyChangeProtection: false, + SubnetChangeProtection: false + } + }, 'arn:aws:network-firewall:us-east-1:1234:firewall-policy/Firewall-Policy-1') + +}) + diff --git a/source/networkFirewallAutomation/__tests__/network-firewall-service.spec.ts b/source/networkFirewallAutomation/__tests__/network-firewall-service.spec.ts new file mode 100644 index 0000000..8bd18e7 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/network-firewall-service.spec.ts @@ -0,0 +1,740 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { NetworkFirewallService } from '../lib/service/network-firewall-service'; + +jest.mock("aws-sdk", () => { + return { + __esModule: true, + NetworkFirewall: jest.fn().mockReturnValue({ + deleteRuleGroup: jest.fn().mockImplementation((data) => { + expect(data['RuleGroupArn']).toBeDefined(); + return { + promise: jest.fn().mockImplementation(() => { + return Promise.resolve( + { + ResourceArn: '', + ResourceName: 'rg1', + Description: '', + UpdateToken: '', + RulesSource: {} + }) + }) + } + }), + describeRuleGroup: jest.fn().mockImplementation((ruleGroup) => { + if (ruleGroup["RuleGroupName"] === "ThrottlingException") { + throw { + "message": "ThrottlingException" + } + } + if (ruleGroup["RuleGroupName"] === "ResourceNotFoundException") { + throw {"code": "ResourceNotFoundException"}; + } + if (ruleGroup["RuleGroupName"] === "Error") { + return Promise.reject({ + message: "Error" + }) + } + if (ruleGroup["RuleGroupArn"] === "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2") { + return { + promise: jest.fn().mockReturnValue({ + UpdateToken: "aaaa", + RuleGroupResponse: { + RuleGroupArn: "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2", + RuleGroupName: "StatelessExample2", + RuleGroupId: 111 + } + }) + } + } + return { + promise: jest.fn().mockReturnValue({ + RuleGroup: { + RuleVariables: { + IPSets: [{ + "foo": { + Definition: [''], + Reference: 'AWS_ARN' + } + }], + PortSets: [{ + "foo": { + Definition: [''] + } + }] + }, + RulesSource: { + RulesString: '', + RulesSourceList: [{ + Targets: [''], + TargetType: [''], + GeneratedRulesType: '' + }], + StatefulRules: [{ + Action: '', + Header: { + Protocol: '', + Source: '', + SourcePort: '', + Direction: '', + Destination: '', + DestinationPort: '' + }, + RuleOptions: [{ + Keyword: '', + Settings: [''] + }] + }], + StatelessRulesAndCustomActions: { + StatelessRules: [{ + RuleDefinition: { + MatchAttributes: { + Sources: [''], + Destinations: [''], + SourcePorts: [{ + FromPort: 0, + ToPort: 999 + }], + DestinationPorts: [{ + FromPort: 0, + ToPort: 999 + }], + Protocols: [0, 1, 2, 3], + TCPFlags: [{ + Flags: [''], + Masks: [''] + }] + }, + Actions: [''] + }, + Priority: 9999 + }], + CustomAction: { + PublishMetrics: { + Dimensions: [{ + Value: '' + }] + } + } + } + } + }, + RuleGroupResponse: { + RuleGroupArn: '', + RuleGroupName: '', + RuleGroupId: '', + Description: '', + Type: '', + Capacity: 9999, + RuleGroupStatus: 'ACTIVE|DELETING|string', + Tags: [{ + Key: '', + Value: '' + }] + }, + UpdateToken: 'aaa' + }) + } + }), + describeFirewallPolicy: jest.fn().mockImplementation(() => { + return { + promise: jest.fn().mockReturnValue({ + UpdateToken: 'aaaa', + FirewallPolicyResponse: { + FirewallPolicyName: 'test-firewall-policy', + FirewallPolicyArn: '', + FirewallPolicyId: '', + Description: '', + FirewallPolicyStatus: 'ACTIVE', + Tags: [{ + Key: '', + Value: '' + }] + }, + FirewallPolicy: { + StatelessRuleGroupReferences: [{ + ResourceArn: 'arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2', + Priority: 999 + }], + StatelessDefaultActions: [''], + StatelessFragmentDefaultActions: [''], + StatelessCustomActions: [{ + ActionName: '', + CustomAction: { + PublishMetrics: { + Dimensions: [{ + Value: '' + }] + } + } + }], + StatefulRuleGroupReferences: [{ + ResourceArn: '' + }] + + } + }) + } + }), + updateRuleGroup: jest.fn().mockImplementation((data) =>{ + if (data['UpdateToken'] === 'invalid token') { + return { + promise: jest.fn().mockReturnValue({ + message: 'Update token is invalid.' + }) + } + } + if (data["UpdateToken"] === "error") { + return { + promise: jest.fn().mockReturnValue( + Promise.reject()) + } + } + + return { + promise: jest.fn().mockReturnValue({ + UpdateToken: '', + RuleGroupResponse: { + RuleGroupArn: '', + RuleGroupName: '', + RuleGroupId: '', + Description: '', + Type: '"STATELESS"|"STATEFUL"|string', + Capacity: 999, + RuleGroupStatus: '"ACTIVE"|"DELETING"|string', + Tags: [{ + Key: '', + Value: '' + }] + } + })} + }), + updateFirewallPolicy: jest.fn().mockImplementation((data) => { + if (data && data["UpdateToken"] === "test invalid token scenario") { + throw { + message: "Update token is invalid." + } + } + if (data && data["UpdateToken"] === "error") { + throw { + "message": "error" + } + } + + return { + promise: jest.fn().mockReturnValue({ + UpdateToken: 'aaa', + FirewallPolicyResponse: { + FirewallPolicyName: '', + FirewallPolicyArn: '', + FirewallPolicyId: '', + Description: '', + FirewallPolicyStatus: '"ACTIVE"|"DELETING"|string', + Tags: [{ + Key: '', + Value: '' + }] + } + }) + } + }), + listFirewalls: jest.fn().mockReturnValue({ + promise: jest.fn().mockReturnValue({}) + }), + createFirewall: jest.fn().mockImplementation((data) => { + + if (data["Description"] === "Error") { + throw Error("ResourceNotFoundException") + } + return { + promise: jest.fn().mockReturnValue({}) + + } + }), + createFirewallPolicy: jest.fn().mockReturnValue({ + promise: jest.fn().mockReturnValue({ + + }) + }), + createRuleGroup: jest.fn().mockReturnValue({ + promise: jest.fn().mockReturnValue({ + + }) + }), + describeFirewall: jest.fn().mockImplementation((data) => { + if (data["FirewallName"] === "error") { + throw Error("ResourceNotFoundException") + } + expect(data["FirewallName"]).toBeDefined(); + return { + promise: jest.fn().mockReturnValue({ + + }) + } + }), + describeLoggingConfiguration: jest.fn().mockReturnValue({ + promise: jest.fn().mockReturnValue({ + LoggingConfiguration: { + LogDestinationConfigs: [{ + LogType: 'ALERT', + LogDestinationType: 'CloudWatchLogs', + LogDestination: { + 'logGroup': "network-firewall-automation-solution", + 'prefix': 'alerts' + } + }] + } + }) + }), + updateLoggingConfiguration: jest.fn().mockImplementation((config)=> { + if(config["LoggingConfiguration"]["LogDestinationConfigs"][0] === undefined) { + + return { + promise: jest.fn().mockReturnValue({ + LoggingConfiguration: { + LogDestinationConfigs: [] + } + }) + } + } + if (config["LoggingConfiguration"]["LogDestinationConfigs"][0]["LogDestinationType"] === "CloudWatchLogs") { + return { + promise: jest.fn().mockReturnValue({ + LoggingConfiguration: { + LogDestinationConfigs: [] + } + }) + } + } + + + return { + promise: jest.fn().mockReturnValue({ + LoggingConfiguration: { + LogDestinationConfigs: [config["LoggingConfiguration"]["LogDestinationConfigs"][0]] + } + }) + } + }), + associateFirewallPolicy: jest.fn().mockImplementation((data) => { + + if (data && data["FirewallName"] === "error") { + throw { + "message": "error" + } + } + return {promise: jest.fn().mockReturnValue({ + + })} + }), + updateSubnetChangeProtection: jest.fn().mockImplementation((data) => { + if (data && data["FirewallName"] === "error") { + throw { + "message": "error" + } + } + return {promise: jest.fn().mockReturnValue({ + + })} + }), + updateFirewallDescription: jest.fn().mockImplementation((data) => { + if (data && data["FirewallName"] === "error") { + throw { + "message": "error" + } + } + return {promise: jest.fn().mockReturnValue({ + + })} + }), + updateFirewallPolicyChangeProtection: jest.fn().mockImplementation((data) => { + if (data && data["FirewallName"] === "error") { + throw { + "message": "error" + } + } + return {promise: jest.fn().mockReturnValue({ + + })} + }), + updateFirewallDeleteProtection: jest.fn().mockImplementation((data) => { + if (data && data["FirewallName"] === "error") { + throw { + "message": "error" + } + } + return {promise: jest.fn().mockReturnValue({ + + })} + }) + }) + } +}, { virtual: true }); + + + +test('test describe firewall policy', async () => { + + const service = new NetworkFirewallService(); + await expect(service.describeFirewallPolicy( +'test-network-firewall' + )).resolves.toBeDefined() +}) + + +test('test describe rule group', async () => { + const service = new NetworkFirewallService() + await expect(service.describeRuleGroup('test-stateless-rg1', 'STATEFUL')).resolves.toBeDefined(); +}) + +test('test describe rule group throttling error response', async () => { + const service = new NetworkFirewallService() + await expect(service.describeRuleGroup('ThrottlingException', 'STATEFUL')).rejects.toStrictEqual({"message":"ThrottlingException"}); +}) +test('test describe rule group resource not found exception response', async () => { + const service = new NetworkFirewallService() + await expect(service.describeRuleGroup('ResourceNotFoundException', 'STATEFUL')).resolves.toBeUndefined(); +}) + +test('create firewall ', async () => { + const service = new NetworkFirewallService() + await expect(service.createFirewall({ + "FirewallName": "VpcFirewall-1", + "FirewallPolicyArn": "__tests__/firewall-test-configuration/firewallPolicies/firewall-policy.example.json", + "Description": "Network Firewall created by AWS Solutions", + "DeleteProtection": true, + "FirewallPolicyChangeProtection": true, + "SubnetChangeProtection": true, + "SubnetMappings": [], + "VpcId": '', + "Tags": [{ + "Key": "SampleKey", + "Value": "SampleValue" + }] + })).resolves.toBeDefined() +}) +test('create firewall handle error response from the sdk. ', async () => { + const service = new NetworkFirewallService() + await expect(service.createFirewall({ + "FirewallName": "VpcFirewall-1", + "FirewallPolicyArn": "__tests__/firewall-test-configuration/firewallPolicies/firewall-policy.example.json", + "Description": "Error", + "DeleteProtection": true, + "FirewallPolicyChangeProtection": true, + "SubnetChangeProtection": true, + "SubnetMappings": [], + "VpcId": '', + "Tags": [{ + "Key": "SampleKey", + "Value": "SampleValue" + }] + })).rejects.toThrowError("ResourceNotFoundException") +}) + +test('create Firewall policy', async () => { + const service = new NetworkFirewallService() + await expect(service.createFirewallPolicy({ + "FirewallPolicyName": "Firewall-Policy-1", + "FirewallPolicy": { + "StatelessDefaultActions": [ + "aws:drop" + ], + "StatelessFragmentDefaultActions": [ + "aws:drop" + ], + "StatelessRuleGroupReferences": [ + { + "Priority": 30, + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2" + }, + { + "Priority": 20, + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1" + } + ], + "StatefulRuleGroupReferences": [ + { + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1" + } + ] + } + })).resolves.toBeDefined(); + +}) + +test('create rule group', async () => { + + const service = new NetworkFirewallService() + await expect(service.createRuleGroup({ + "RuleGroupName": "StatefulRulesExample1", + "RuleGroup": { + "RulesSource": { + "RulesSourceList": { + "Targets": ["test.example.com"], + "TargetTypes": ["HTTP_HOST", "TLS_SNI"], + "GeneratedRulesType": "DENYLIST" + } + } + }, + "Type": "STATEFUL", + "Description": "Stateful Rule3", + "Capacity": 100 + })).resolves.toBeDefined(); + +}) + +test(' describe firewall', async () => { + const service = new NetworkFirewallService() + await expect(service.describeFirewall('firewall-name')).resolves.toBeDefined(); +}) + +test(' describe firewall handle sdk error', async () => { + const service = new NetworkFirewallService() + await expect(service.describeFirewall('error')) + .rejects.toThrowError("ResourceNotFoundException") +}) + +test('Update firewall policy ', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateFirewallPolicy({ + UpdateToken: '', + FirewallPolicyArn: '', + FirewallPolicyName: 'test', + FirewallPolicy: { + "StatelessDefaultActions": [ + "aws:drop" + ], + "StatelessFragmentDefaultActions": [ + "aws:drop" + ], + "StatelessRuleGroupReferences": [ + { + "Priority": 30, + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2" + }, + { + "Priority": 20, + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1" + } + ], + "StatefulRuleGroupReferences": [ + { + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1" + } + ] + } + })).resolves.toBeDefined() +}) + +test('Update firewall policy handle invalid token scenario.', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateFirewallPolicy({ + UpdateToken: 'test invalid token scenario', + FirewallPolicyArn: '', + FirewallPolicyName: 'test', + FirewallPolicy: { + "StatelessDefaultActions": [ + "aws:drop" + ], + "StatelessFragmentDefaultActions": [ + "aws:drop" + ], + "StatelessRuleGroupReferences": [ + { + "Priority": 30, + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2" + }, + { + "Priority": 20, + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1" + } + ], + "StatefulRuleGroupReferences": [ + { + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1" + } + ] + } + })).resolves.toBeDefined() +}) +test('Update firewall policy handle error.', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateFirewallPolicy({ + UpdateToken: 'error', + FirewallPolicyArn: '', + FirewallPolicyName: 'test', + FirewallPolicy: { + "StatelessDefaultActions": [ + "aws:drop" + ], + "StatelessFragmentDefaultActions": [ + "aws:drop" + ], + "StatelessRuleGroupReferences": [ + { + "Priority": 30, + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample2" + }, + { + "Priority": 20, + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateless-rulegroup/StatelessExample1" + } + ], + "StatefulRuleGroupReferences": [ + { + "ResourceArn": "arn:aws:network-firewall:us-east-1:1234:stateful-rulegroup/StatefulRulesExample1" + } + ] + } + })).rejects.toBeDefined() +}) + + +test('Update rule groups', async () => { + + const service = new NetworkFirewallService() + await expect(service.updateRuleGroup({ + UpdateToken: '', + RuleGroupName: 'test' + })).resolves.toBeDefined() + +}) +test('Update rule groups handle invalid token error', async () => { + + const service = new NetworkFirewallService() + await expect(service.updateRuleGroup({ + UpdateToken: 'invalid token', + RuleGroupName: 'test' + })).resolves.toBeDefined() + +}) +test('Update rule groups handle error', async () => { + + const service = new NetworkFirewallService() + await expect(service.updateRuleGroup({ + UpdateToken: 'error', + RuleGroupName: 'test' + })).rejects.toThrowError() + +}) + +test('Update logging configuration', async () => { + const service = new NetworkFirewallService() + const response = await service.updateLoggingConfiguration('firewallName', { + LogDestinationConfigs: [{ + LogType: 'ALERT', + LogDestination: { + 'bucketName': "network-firewall-automation-solution", + 'prefix': "alerts" + }, + LogDestinationType: 'S3' + }] + }) + expect(response).toStrictEqual({"LoggingConfiguration":{"LogDestinationConfigs":[{"LogType":"ALERT","LogDestination":{"bucketName":"network-firewall-automation-solution","prefix":"alerts"},"LogDestinationType":"S3"}]}}) +}) + +test('List rule groups for firewall Policy', async () => { + const service = new NetworkFirewallService() + await expect(service.listRuleGroupsForPolicy('FirewallName')).resolves.toBeDefined() +}) + +test('delete rule Group', async () => { + const service = new NetworkFirewallService() + await expect(service.deleteRuleGroup('')).resolves.toBeUndefined() +}) + +test('associate firewall policy', async () => { + + const service = new NetworkFirewallService(); + await expect(service.associateFirewallPolicy({ + FirewallPolicyArn: '', + FirewallName: '' + })).resolves.toBeDefined(); + +}) + +test('associate firewall policy error response', async () => { + + const service = new NetworkFirewallService(); + await expect(service.associateFirewallPolicy({ + FirewallPolicyArn: '', + FirewallName: 'error' + })).rejects.toBeDefined(); + +}) + +test('update firewall description.', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateFirewallDescription({ + Description: '', + FirewallName: '' + })).resolves.toBeDefined(); + +}) + +test('associate firewall description error response', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateFirewallDescription({ + Description: '', + FirewallName: 'error' + })).rejects.toBeDefined(); +}) + +test('update firewall deletion protection.', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateFirewallDeleteProtection({ + DeleteProtection: false, + FirewallName: '' + })).resolves.toBeDefined(); +}) + +test('associate firewall deletion protection error response', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateFirewallDeleteProtection({ + DeleteProtection: false, + FirewallName: 'error' + })).rejects.toBeDefined(); +}) + +test('update firewall policy change protection.', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateFirewallPolicyChangeProtection({ + FirewallPolicyChangeProtection: false, + FirewallName: '' + })).resolves.toBeDefined(); +}) + +test('update firewall policy change protection error response.', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateFirewallPolicyChangeProtection({ + FirewallPolicyChangeProtection: false, + FirewallName: 'error' + })).rejects.toBeDefined(); +}) + +test('update subnet change protection.', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateSubnetChangeProtection({ + SubnetChangeProtection: false, + FirewallName: '' + })).resolves.toBeDefined(); +}) + +test('update subnet change protection error response.', async () => { + const service = new NetworkFirewallService(); + await expect(service.updateSubnetChangeProtection({ + SubnetChangeProtection: false, + FirewallName: 'error' + })).rejects.toBeDefined(); +}) \ No newline at end of file diff --git a/source/networkFirewallAutomation/__tests__/send-metrics.spec.ts b/source/networkFirewallAutomation/__tests__/send-metrics.spec.ts new file mode 100644 index 0000000..f4a38f4 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/send-metrics.spec.ts @@ -0,0 +1,74 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +import { MetricsManager } from "../lib/common/send-metrics" + +jest.mock("aws-sdk", () => { + return { + __esModule: true, + SSM: jest.fn().mockReturnValue({ + getParameter: jest.fn().mockImplementation((data) => { + expect(data).toStrictEqual({ Name: 'network-firewall-solution-uuid-asds' }) + if ('network-firewall-solution-uuid-asds' === data["Name"]) { + return { + promise: jest.fn().mockReturnValue({ + Parameter: { + Value: '5d358dfa-bc71-4a48-a00c-0931e8ec1456' + } + }) + } + } else { + return { + promise: jest.fn().mockReturnValue({ + + }) + } + } + }) + }) + } +}, { virtual: true }); + +jest.mock("uuid", () => { + return { + __esModule: true, + v4: jest.fn().mockImplementation(() => { + return '5d358dfa-bc71-4a48-a00c-0931e8ec1456' + }) + } +}, { virtual: true }); + +jest.mock("axios", () => { + return { + __esModule: true, + post: jest.fn().mockImplementation(() => { + return { + promise: jest.fn().mockReturnValue({ + + }) + } + }) + } +}, { virtual: true }); + +test('test sending the metrics when the uuid is already in the parameter store.', async () => { + process.env.STACK_ID = 'asds' + process.env.SEND_ANONYMOUS_METRICS = 'Yes' + await MetricsManager.sendMetrics({ + numberOfFirewalls: 1, + numberOfPolicies: 1, + numberOfStatefulRuleGroups: 1, + numberOfStatelessRuleGroups: 1, + numberOfSuricataRules: 0 + }) +}) + diff --git a/source/networkFirewallAutomation/__tests__/stringManipulation.spec.ts b/source/networkFirewallAutomation/__tests__/stringManipulation.spec.ts new file mode 100644 index 0000000..743b503 --- /dev/null +++ b/source/networkFirewallAutomation/__tests__/stringManipulation.spec.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { StringUtils, Name } from '../lib/common/stringUtils'; + +const stackId = 'f449b250-b969-11e0-a185-5081d0136786' + +test('test resource name less than 128 chars', async () => { + const resourceName = 'Firewall-1' + const stringMod = new StringUtils(stackId) + const customName = stringMod.getUniqueResourceName(resourceName) + console.log(customName) + expect(customName.length < Name.maxCharacters) +}) + +test('test resource name more than 128 chars', async () => { + const resourceName = 'Firewall-1-f449b250-b969-11e0-a185-5081d0136786-f449b250-b969-11e0-a185-5081d0136786-f449b250-b969-11e0-a185-9-11e0-a185-5081d0136786-f449b250-b969-11e0-a185' + const stringMod = new StringUtils(stackId) + const customName = stringMod.getUniqueResourceName(resourceName) + console.log(customName) + expect(customName.length == Name.maxCharacters) +}) diff --git a/source/networkFirewallAutomation/build.ts b/source/networkFirewallAutomation/build.ts new file mode 100644 index 0000000..59a0817 --- /dev/null +++ b/source/networkFirewallAutomation/build.ts @@ -0,0 +1,27 @@ + +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +import { FirewallConfigValidation } from "./lib/common/firewall-config-validation" +import { Logger, LOG_LEVEL } from "./lib/common/logger"; + +async function main() { + try { + const firewallConfigValidation = new FirewallConfigValidation() + await firewallConfigValidation.execute(); + } catch(error) { + Logger.log(LOG_LEVEL.ERROR, `Error in firewall config validation`, error) + process.exit(1) + } +} + +main(); \ No newline at end of file diff --git a/source/networkFirewallAutomation/config/examples/firewallPolicies/firewall-policy.example.json b/source/networkFirewallAutomation/config/examples/firewallPolicies/firewall-policy.example.json new file mode 100644 index 0000000..abf1fd3 --- /dev/null +++ b/source/networkFirewallAutomation/config/examples/firewallPolicies/firewall-policy.example.json @@ -0,0 +1,30 @@ +{ + "FirewallPolicyName": "Firewall-Policy-1", + "Description": "Firewall Policy 1", + "FirewallPolicy": { + "StatelessDefaultActions": [ + "aws:drop" + ], + "StatelessFragmentDefaultActions": [ + "aws:drop" + ], + "StatelessRuleGroupReferences": [ + { + "Priority": 30, + "ResourceArn": "./ruleGroups/stateless-fwd-to-stateful.example.json" + }, + { + "Priority": 20, + "ResourceArn": "./ruleGroups/stateless-pass-action.example.json" + } + ], + "StatefulRuleGroupReferences": [ + { + "ResourceArn": "./ruleGroups/stateful-domainblock.example.json" + }, + { + "ResourceArn": "./ruleGroups/suricata-rule-reference.json" + } + ] + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/config/examples/firewalls/firewall.example.json b/source/networkFirewallAutomation/config/examples/firewalls/firewall.example.json new file mode 100644 index 0000000..a831ca5 --- /dev/null +++ b/source/networkFirewallAutomation/config/examples/firewalls/firewall.example.json @@ -0,0 +1,12 @@ +{ + "FirewallName": "VpcFirewall-1", + "FirewallPolicyArn": "./firewallPolicies/firewall-policy.example.json", + "Description": "Network Firewall created by AWS Solutions", + "DeleteProtection": true, + "FirewallPolicyChangeProtection": true, + "SubnetChangeProtection": true, + "Tags": [{ + "Key": "SampleKey", + "Value": "SampleValue" + }] +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/config/examples/ruleGroups/drop.rules b/source/networkFirewallAutomation/config/examples/ruleGroups/drop.rules new file mode 100644 index 0000000..e37904c --- /dev/null +++ b/source/networkFirewallAutomation/config/examples/ruleGroups/drop.rules @@ -0,0 +1,79 @@ +# +# $Id: emerging-drop.rules $ +# Emerging Threats Spamhaus DROP List rules. +# +# Rules to block Spamhaus DROP listed networks (www.spamhaus.org) +# +# More information available at www.emergingthreats.net +# +# Please submit any feedback or ideas to emerging@emergingthreats.net or the emerging-sigs mailing list +# +#************************************************************* +# +# Copyright (c) 2003-2020, Emerging Threats +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other materials provided with the distribution. +# * Neither the name of the nor the names of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# + +# VERSION 2793 + + +# Generated 2021-01-10 00:05:02 EDT + +alert ip [2.59.200.0/22,5.134.128.0/19,5.180.4.0/22,5.181.84.0/22,5.183.60.0/22,5.188.10.0/23,24.137.16.0/20,24.170.208.0/20,24.233.0.0/19,24.236.0.0/19,27.126.160.0/20,27.146.0.0/16,31.14.65.0/24,31.14.66.0/23,31.40.156.0/22,31.40.164.0/22,36.0.8.0/21,36.37.48.0/20,36.116.0.0/16,36.119.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 1"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400000; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [42.0.32.0/19,42.1.128.0/17,42.96.0.0/18,42.128.0.0/12,42.160.0.0/12,42.194.128.0/17,42.208.0.0/12,43.229.52.0/22,43.236.0.0/16,43.250.116.0/22,43.252.80.0/22,45.4.128.0/22,45.4.136.0/22,45.6.48.0/22,45.9.148.0/22,45.9.156.0/22,45.10.16.0/22,45.11.184.0/22,45.11.188.0/22,45.41.0.0/18] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 2"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400001; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [45.65.120.0/22,45.65.188.0/22,45.80.28.0/22,45.80.248.0/23,45.80.250.0/23,45.86.20.0/22,45.95.40.0/22,45.114.240.0/22,45.117.52.0/22,45.117.232.0/22,45.119.40.0/22,45.121.204.0/22,45.130.100.0/22,45.135.193.0/24,45.159.56.0/22,45.220.64.0/18,46.102.177.0/24,46.102.178.0/23,46.102.180.0/24,46.102.182.0/23] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 3"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400002; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [58.14.0.0/15,58.145.176.0/21,59.153.60.0/22,60.233.0.0/16,61.11.224.0/19,61.45.251.0/24,64.92.224.0/20,64.250.144.0/20,65.97.48.0/20,67.213.112.0/20,68.66.48.0/20,69.8.64.0/20,69.8.96.0/20,72.1.224.0/20,74.114.148.0/22,76.191.0.0/20,77.36.62.0/24,77.81.84.0/23,77.81.86.0/24,77.81.89.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 4"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400003; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [85.209.4.0/22,86.55.40.0/23,86.55.42.0/23,86.62.28.0/22,86.104.0.0/23,86.104.2.0/24,86.104.212.0/23,86.104.222.0/23,86.104.224.0/23,86.105.2.0/24,86.105.6.0/24,86.105.176.0/24,86.105.178.0/24,86.105.184.0/23,86.105.186.0/24,86.105.229.0/24,86.105.230.0/24,86.105.242.0/23,86.106.10.0/24,86.106.13.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 5"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400004; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [86.106.110.0/23,86.106.114.0/23,86.106.116.0/23,86.106.118.0/24,86.106.138.0/23,86.106.140.0/23,86.106.174.0/23,86.107.72.0/24,86.107.193.0/24,86.107.194.0/23,88.218.40.0/22,88.218.148.0/22,89.32.43.0/24,89.32.170.0/24,89.32.202.0/24,89.33.46.0/23,89.33.116.0/24,89.33.134.0/24,89.33.198.0/23,89.33.200.0/23] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 6"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400005; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [89.34.74.0/24,89.34.102.0/24,89.34.104.0/23,89.35.54.0/24,89.35.89.0/24,89.35.90.0/24,89.36.38.0/23,89.36.136.0/24,89.36.138.0/23,89.36.141.0/24,89.37.92.0/23,89.37.94.0/24,89.37.96.0/24,89.37.129.0/24,89.37.130.0/23,89.37.132.0/23,89.37.134.0/24,89.38.240.0/24,89.39.69.0/24,89.39.212.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 7"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400006; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [89.40.209.0/24,89.41.27.0/24,89.41.28.0/23,89.41.49.0/24,89.41.50.0/23,89.41.189.0/24,89.41.190.0/23,89.42.10.0/24,89.42.152.0/23,89.42.154.0/24,89.45.82.0/24,89.46.47.0/24,91.132.164.0/22,91.197.196.0/22,91.200.12.0/22,91.200.133.0/24,91.200.248.0/22,91.218.236.0/22,91.220.163.0/24,91.229.52.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 8"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400007; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [93.114.51.0/24,93.114.52.0/23,93.114.54.0/24,93.114.58.0/23,93.115.59.0/24,93.119.118.0/23,93.119.120.0/23,93.119.124.0/23,93.120.34.0/24,93.120.46.0/24,94.131.228.0/22,94.154.32.0/22,96.45.144.0/20,98.143.192.0/20,101.42.0.0/16,101.134.0.0/15,101.192.0.0/14,101.203.128.0/19,101.248.0.0/15,102.196.96.0/19] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 9"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400008; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [103.14.208.0/22,103.16.76.0/24,103.23.8.0/22,103.23.124.0/22,103.24.232.0/22,103.30.12.0/22,103.32.0.0/16,103.32.132.0/22,103.34.0.0/16,103.36.64.0/22,103.59.92.0/22,103.73.172.0/22,103.75.36.0/22,103.76.96.0/22,103.76.128.0/22,103.77.32.0/22,103.99.0.0/22,103.100.168.0/22,103.134.144.0/23,103.135.144.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 10"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400009; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [103.197.240.0/22,103.199.88.0/22,103.199.184.0/22,103.205.84.0/22,103.207.160.0/22,103.210.244.0/22,103.215.80.0/22,103.225.72.0/22,103.225.128.0/22,103.226.192.0/22,103.228.60.0/22,103.229.36.0/22,103.230.144.0/22,103.232.136.0/22,103.232.172.0/22,103.236.32.0/22,103.239.28.0/22,103.239.56.0/22,103.243.8.0/22,103.243.124.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 11"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400010; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [104.239.0.0/17,104.243.192.0/20,104.247.96.0/19,104.250.192.0/19,104.250.224.0/19,104.251.192.0/20,106.95.0.0/16,107.182.112.0/20,107.182.240.0/20,107.190.160.0/20,110.41.0.0/16,111.223.192.0/19,113.212.128.0/19,116.144.0.0/15,116.146.0.0/15,117.58.0.0/17,119.58.0.0/16,119.232.0.0/16,120.48.0.0/15,121.46.124.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 12"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400011; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [124.157.0.0/18,124.242.0.0/16,125.31.192.0/18,125.58.0.0/18,125.169.0.0/16,128.24.0.0/16,128.85.0.0/16,130.21.0.0/16,130.148.0.0/16,130.196.0.0/16,130.222.0.0/16,131.108.16.0/22,131.143.0.0/16,131.200.0.0/16,132.255.132.0/22,134.18.0.0/16,134.22.0.0/16,134.23.0.0/16,134.33.0.0/16,134.127.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 13"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400012; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [137.72.0.0/16,137.76.0.0/16,137.105.0.0/16,137.114.0.0/16,137.218.0.0/16,138.31.0.0/16,138.36.92.0/22,138.36.136.0/22,138.52.0.0/16,138.59.4.0/22,138.59.204.0/22,138.94.144.0/22,138.94.216.0/22,138.97.156.0/22,138.122.192.0/22,138.125.0.0/16,138.185.116.0/22,138.186.208.0/22,138.216.0.0/16,138.219.172.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 14"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400013; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [140.82.96.0/20,140.167.0.0/16,141.98.68.0/23,141.98.70.0/23,141.136.22.0/24,141.178.0.0/16,141.206.128.0/20,141.253.0.0/16,142.102.0.0/16,143.0.236.0/22,143.49.0.0/16,143.135.0.0/16,143.136.0.0/16,143.253.0.0/16,145.231.0.0/16,146.3.0.0/16,146.51.0.0/16,146.106.0.0/16,146.183.0.0/16,146.202.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 15"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400014; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [148.148.0.0/16,148.154.0.0/16,148.178.0.0/16,148.185.0.0/16,148.248.0.0/16,149.118.0.0/16,149.207.0.0/16,150.10.0.0/16,150.22.128.0/17,150.25.0.0/16,150.40.0.0/16,150.121.0.0/16,150.129.212.0/22,150.129.228.0/22,150.141.0.0/16,150.242.120.0/22,150.242.144.0/22,151.212.0.0/16,152.89.228.0/23,152.89.230.0/23] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 16"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400015; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [155.11.0.0/16,155.40.0.0/16,155.66.0.0/16,155.71.0.0/16,155.73.0.0/16,155.108.0.0/16,155.159.0.0/16,155.235.0.0/16,155.249.0.0/16,156.96.0.0/16,157.115.0.0/16,157.162.0.0/16,157.186.0.0/16,157.195.0.0/16,158.54.0.0/16,158.249.0.0/16,159.80.0.0/16,159.85.0.0/16,159.151.0.0/16,159.174.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 17"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400016; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [160.116.0.0/16,160.117.0.0/16,160.121.0.0/16,160.122.0.0/16,160.180.0.0/16,160.184.0.0/16,160.188.0.0/16,160.200.0.0/16,160.235.0.0/16,160.240.0.0/16,160.255.0.0/16,161.0.0.0/19,161.0.68.0/22,161.1.0.0/16,162.208.124.0/22,162.212.188.0/22,162.222.128.0/21,162.249.20.0/22,163.47.19.0/24,163.50.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 18"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400017; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [163.216.0.0/19,163.250.0.0/16,163.254.0.0/16,164.6.0.0/16,164.79.0.0/16,164.88.0.0/16,164.137.0.0/16,164.155.0.0/16,165.3.0.0/16,165.25.0.0/16,165.52.0.0/14,165.102.0.0/16,165.205.0.0/16,165.209.0.0/16,165.231.0.0/16,166.93.0.0/16,166.117.0.0/16,167.74.0.0/18,167.82.144.0/20,167.97.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 19"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400018; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [167.224.0.0/19,167.224.32.0/20,167.224.48.0/21,167.249.200.0/22,168.0.212.0/22,168.64.0.0/16,168.76.0.0/16,168.80.0.0/15,168.90.96.0/22,168.129.0.0/16,168.151.0.0/22,168.151.4.0/23,168.151.6.0/24,168.151.32.0/21,168.151.43.0/24,168.151.44.0/22,168.151.48.0/22,168.151.52.0/23,168.151.54.0/24,168.151.56.0/21] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 20"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400019; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [168.151.128.0/20,168.151.145.0/24,168.151.146.0/23,168.151.148.0/22,168.151.152.0/22,168.151.157.0/24,168.151.158.0/23,168.151.160.0/20,168.151.176.0/21,168.151.184.0/22,168.151.192.0/20,168.151.208.0/21,168.151.216.0/22,168.151.220.0/23,168.151.232.0/21,168.151.240.0/21,168.151.248.0/22,168.151.254.0/24,168.181.52.0/22,168.195.76.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 21"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400020; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [168.211.0.0/16,168.227.128.0/22,168.227.140.0/22,169.239.152.0/22,170.67.0.0/16,170.83.232.0/22,170.113.0.0/16,170.120.0.0/16,170.179.0.0/16,170.244.40.0/22,170.244.240.0/22,170.247.220.0/22,171.25.212.0/22,171.26.0.0/16,172.98.0.0/18,174.136.192.0/18,175.103.64.0/18,176.56.192.0/19,176.96.88.0/21,176.102.120.0/21] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 22"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400021; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [176.223.116.0/23,176.223.118.0/24,176.223.160.0/23,177.234.136.0/21,178.212.184.0/21,178.213.176.0/22,179.63.0.0/17,180.178.192.0/18,180.236.0.0/14,181.177.64.0/18,185.0.96.0/19,185.21.8.0/22,185.30.168.0/22,185.39.8.0/22,185.55.4.0/22,185.55.140.0/22,185.60.201.0/24,185.60.202.0/23,185.63.35.0/24,185.64.23.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 23"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400022; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [185.116.172.0/23,185.116.175.0/24,185.120.8.0/22,185.122.128.0/22,185.123.144.0/20,185.123.248.0/21,185.124.0.0/22,185.124.56.0/21,185.126.136.0/22,185.126.148.0/22,185.126.160.0/21,185.126.224.0/22,185.126.236.0/22,185.126.248.0/22,185.127.44.0/22,185.127.56.0/22,185.127.68.0/22,185.127.76.0/22,185.127.92.0/22,185.129.8.0/22] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 24"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400023; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [185.144.180.0/22,185.147.140.0/22,185.156.88.0/21,185.156.92.0/22,185.161.148.0/22,185.165.24.0/22,185.180.192.0/22,185.184.192.0/22,185.185.48.0/22,185.193.90.0/24,185.193.143.0/24,185.194.100.0/22,185.203.64.0/22,185.215.132.0/22,185.227.200.0/22,185.230.44.0/22,185.234.64.0/22,185.237.104.0/22,185.237.220.0/22,185.237.226.0/23] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 25"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400024; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [188.172.160.0/19,188.208.48.0/22,188.208.52.0/22,188.208.109.0/24,188.208.220.0/22,188.209.120.0/21,188.212.254.0/24,188.213.23.0/24,188.213.206.0/23,188.213.214.0/23,188.213.248.0/22,188.213.252.0/22,188.214.94.0/24,188.214.95.0/24,188.214.140.0/24,188.214.155.0/24,188.214.193.0/24,188.241.211.0/24,188.247.230.0/24,190.123.208.0/20] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 26"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400025; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [192.31.212.0/23,192.40.29.0/24,192.43.160.0/24,192.43.175.0/24,192.43.176.0/21,192.43.184.0/24,192.54.110.0/24,192.67.16.0/24,192.96.146.0/24,192.101.44.0/24,192.101.181.0/24,192.101.200.0/21,192.101.240.0/21,192.101.248.0/23,192.133.3.0/24,192.152.194.0/24,192.154.11.0/24,192.160.44.0/24,192.161.80.0/20,192.190.49.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 27"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400026; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [192.219.120.0/21,192.219.128.0/18,192.219.192.0/20,192.219.208.0/21,192.226.16.0/20,192.229.32.0/19,192.231.66.0/24,192.234.189.0/24,192.245.101.0/24,192.251.231.0/24,192.252.16.0/20,193.25.48.0/20,193.30.254.0/23,193.32.66.0/23,193.46.172.0/22,193.139.0.0/16,193.151.160.0/22,193.201.232.0/22,193.228.91.0/24,193.243.0.0/17] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 28"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400027; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [196.1.109.0/24,196.10.64.0/19,196.15.64.0/18,196.16.0.0/14,196.42.128.0/17,196.61.192.0/20,196.62.0.0/16,196.192.192.0/18,196.193.0.0/16,196.194.0.0/15,196.199.0.0/16,196.207.64.0/18,196.246.0.0/16,197.154.0.0/16,197.231.208.0/22,198.13.0.0/20,198.14.0.0/20,198.20.16.0/20,198.45.32.0/20,198.45.64.0/19] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 29"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400028; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [198.96.224.0/20,198.99.117.0/24,198.102.222.0/24,198.148.212.0/24,198.151.16.0/20,198.151.64.0/18,198.151.152.0/22,198.160.205.0/24,198.169.201.0/24,198.177.175.0/24,198.177.176.0/22,198.177.180.0/24,198.177.214.0/24,198.178.64.0/19,198.179.22.0/24,198.181.96.0/20,198.183.32.0/19,198.184.193.0/24,198.184.208.0/24,198.186.25.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 30"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400029; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [198.200.8.0/23,198.202.237.0/24,198.204.0.0/21,198.206.140.0/24,198.212.132.0/24,199.5.152.0/23,199.5.229.0/24,199.26.137.0/24,199.26.207.0/24,199.26.251.0/24,199.33.222.0/24,199.34.128.0/18,199.60.102.0/24,199.71.192.0/20,199.73.64.0/20,199.84.16.0/20,199.84.55.0/24,199.84.56.0/22,199.84.60.0/24,199.84.64.0/19] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 31"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400030; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [199.184.82.0/24,199.185.144.0/20,199.185.192.0/20,199.196.192.0/19,199.198.160.0/20,199.198.176.0/21,199.198.184.0/23,199.198.188.0/22,199.200.64.0/19,199.212.96.0/20,199.223.0.0/20,199.230.64.0/19,199.230.96.0/21,199.233.85.0/24,199.233.96.0/24,199.241.64.0/19,199.244.56.0/21,199.245.138.0/24,199.246.137.0/24,199.246.213.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 32"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400031; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [199.254.32.0/20,200.0.60.0/23,200.22.0.0/16,200.71.124.0/22,200.189.44.0/22,200.234.128.0/18,201.148.168.0/22,201.169.0.0/16,202.0.192.0/18,202.20.32.0/19,202.21.64.0/19,202.27.96.0/23,202.27.98.0/24,202.27.99.0/24,202.27.100.0/22,202.27.120.0/22,202.27.161.0/24,202.27.162.0/23,202.27.164.0/22,202.27.168.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 33"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400032; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [202.148.176.0/20,202.183.0.0/19,202.189.80.0/20,203.2.200.0/22,203.9.0.0/19,203.31.88.0/23,203.34.70.0/23,203.86.252.0/22,203.169.0.0/22,203.191.64.0/18,203.195.0.0/18,204.14.80.0/22,204.19.38.0/23,204.44.32.0/20,204.44.208.0/20,204.44.224.0/20,204.52.96.0/19,204.52.255.0/24,204.57.16.0/20,204.75.147.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 34"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400033; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [204.106.128.0/18,204.106.192.0/19,204.107.208.0/24,204.126.244.0/23,204.128.32.0/20,204.128.151.0/24,204.128.180.0/24,204.130.16.0/20,204.130.167.0/24,204.147.64.0/21,204.147.96.0/20,204.147.240.0/20,204.156.192.0/20,204.194.64.0/21,204.225.159.0/24,204.225.210.0/24,204.232.0.0/18,204.238.137.0/24,204.238.170.0/24,204.238.183.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 35"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400034; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [205.148.192.0/18,205.151.128.0/19,205.159.45.0/24,205.159.174.0/24,205.159.180.0/24,205.166.77.0/24,205.166.84.0/24,205.166.130.0/24,205.166.168.0/24,205.166.211.0/24,205.172.244.0/22,205.175.160.0/19,205.189.71.0/24,205.189.72.0/23,205.203.0.0/19,205.203.224.0/19,205.207.134.0/24,205.210.107.0/24,205.210.139.0/24,205.210.171.0/24] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 36"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400035; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [205.236.189.0/24,205.237.88.0/21,206.41.128.0/20,206.41.160.0/19,206.51.29.0/24,206.124.104.0/21,206.125.16.0/20,206.130.188.0/24,206.143.128.0/17,206.183.128.0/19,206.195.224.0/19,206.197.28.0/24,206.197.29.0/24,206.197.77.0/24,206.197.165.0/24,206.209.48.0/20,206.209.80.0/20,206.223.17.0/24,206.224.160.0/19,206.226.0.0/19] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 37"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400036; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [207.90.0.0/18,207.110.64.0/18,207.110.96.0/19,207.110.128.0/18,207.183.64.0/19,207.183.96.0/20,207.183.128.0/19,207.183.192.0/19,207.201.64.0/18,207.228.192.0/20,207.244.0.0/18,208.73.208.0/22,208.90.32.0/21,208.93.4.0/22,209.17.192.0/19,209.66.0.0/18,209.66.128.0/19,209.95.64.0/19,209.95.192.0/19,209.99.128.0/18] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 38"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400037; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) +alert ip [209.145.0.0/19,209.148.16.0/20,209.161.64.0/19,209.161.96.0/20,209.182.64.0/19,209.242.192.0/19,212.162.152.0/22,213.173.36.0/22,213.247.0.0/19,216.179.128.0/17,220.154.0.0/16,221.132.192.0/18,223.0.0.0/15,223.169.0.0/16,223.173.0.0/16,223.254.0.0/16] any -> $HOME_NET any (msg:"ET DROP Spamhaus DROP Listed Traffic Inbound group 39"; reference:url,www.spamhaus.org/drop/drop.lasso; threshold: type limit, track by_src, seconds 3600, count 1; classtype:misc-attack; flowbits:set,ET.Evil; flowbits:set,ET.DROPIP; sid:2400038; rev:2793; metadata:affected_product Any, attack_target Any, deployment Perimeter, tag Dshield, signature_severity Minor, created_at 2010_12_30, updated_at 2021_01_10;) diff --git a/source/networkFirewallAutomation/config/examples/ruleGroups/stateful-domainblock.example.json b/source/networkFirewallAutomation/config/examples/ruleGroups/stateful-domainblock.example.json new file mode 100644 index 0000000..4465c03 --- /dev/null +++ b/source/networkFirewallAutomation/config/examples/ruleGroups/stateful-domainblock.example.json @@ -0,0 +1,31 @@ +{ + "RuleGroupName": "StatefulRulesExample1", + "RuleGroup": { + "RuleVariables": { + "IPSets": { + "HOME_NET": { + "Definition": [ + "10.0.0.0/8", + "172.16.0.0/16" + ] + } + } + }, + "RulesSource": { + "RulesSourceList": { + "TargetTypes": [ + "HTTP_HOST", + "TLS_SNI" + ], + "Targets": [ + "test.example.com", + "test2.example.com" + ], + "GeneratedRulesType": "DENYLIST" + } + } + }, + "Type": "STATEFUL", + "Description": "Stateful Rule", + "Capacity": 100 +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/config/examples/ruleGroups/stateless-fwd-to-stateful.example.json b/source/networkFirewallAutomation/config/examples/ruleGroups/stateless-fwd-to-stateful.example.json new file mode 100644 index 0000000..3169f0e --- /dev/null +++ b/source/networkFirewallAutomation/config/examples/ruleGroups/stateless-fwd-to-stateful.example.json @@ -0,0 +1,41 @@ +{ + "RuleGroupName": "StatelessExample2", + "RuleGroup": { + "RulesSource": { + "StatelessRulesAndCustomActions": { + "StatelessRules": [ + { + "RuleDefinition": { + "MatchAttributes": { + "Sources": [ + { + "AddressDefinition": "192.0.2.0/8" + } + ], + "Destinations": [ + { + "AddressDefinition": "198.51.100.0/16" + }, + { + "AddressDefinition": "198.52.100.0/16" + } + ], + "Protocols": [ + 6, + 17 + ] + }, + "Actions": [ + "aws:forward_to_sfe" + ] + }, + "Priority": 100 + } + ] + } + } + }, + "Type": "STATELESS", + "Description": "Stateless Rule with Forward to Stateful", + "Capacity": 220 +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/config/examples/ruleGroups/stateless-pass-action.example.json b/source/networkFirewallAutomation/config/examples/ruleGroups/stateless-pass-action.example.json new file mode 100644 index 0000000..c97b849 --- /dev/null +++ b/source/networkFirewallAutomation/config/examples/ruleGroups/stateless-pass-action.example.json @@ -0,0 +1,68 @@ +{ + "RuleGroupName": "StatelessExample1", + "RuleGroup": { + "RulesSource": { + "StatelessRulesAndCustomActions": { + "StatelessRules": [ + { + "RuleDefinition": { + "MatchAttributes": { + "Sources": [ + { + "AddressDefinition": "192.0.2.0/8" + } + ], + "Destinations": [ + { + "AddressDefinition": "198.51.100.0/16" + } + ], + "SourcePorts": [ + { + "FromPort": 53, + "ToPort": 53 + }, + { + "FromPort": 1001, + "ToPort": 1053 + } + ], + "DestinationPorts": [ + { + "FromPort": 53, + "ToPort": 53 + }, + { + "FromPort": 1001, + "ToPort": 1053 + } + ], + "Protocols": [ + 6 + ], + "TCPFlags": [ + { + "Flags": [ + "SYN" + ], + "Masks": [ + "SYN", + "ACK" + ] + } + ] + }, + "Actions": [ + "aws:pass" + ] + }, + "Priority": 19 + } + ] + } + } + }, + "Type": "STATELESS", + "Description": "Stateless Rule with pass action", + "Capacity": 199 +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/config/examples/ruleGroups/suricata-rule-reference.json b/source/networkFirewallAutomation/config/examples/ruleGroups/suricata-rule-reference.json new file mode 100644 index 0000000..1175cb4 --- /dev/null +++ b/source/networkFirewallAutomation/config/examples/ruleGroups/suricata-rule-reference.json @@ -0,0 +1,8 @@ + +{ + "RuleGroupName": "suricata-drop-rules", + "Rules": "./ruleGroups/drop.rules", + "Type": "STATEFUL", + "Description": "Suricata rule group", + "Capacity": 100 + } \ No newline at end of file diff --git a/source/networkFirewallAutomation/config/firewallPolicies/firewall-policy-1.json b/source/networkFirewallAutomation/config/firewallPolicies/firewall-policy-1.json new file mode 100644 index 0000000..4a69cbe --- /dev/null +++ b/source/networkFirewallAutomation/config/firewallPolicies/firewall-policy-1.json @@ -0,0 +1,12 @@ +{ + "FirewallPolicyName": "Firewall-Policy-1", + "Description": "Firewall Policy 1", + "FirewallPolicy": { + "StatelessDefaultActions": [ + "aws:forward_to_sfe" + ], + "StatelessFragmentDefaultActions": [ + "aws:pass" + ] + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/config/firewalls/firewall-1.json b/source/networkFirewallAutomation/config/firewalls/firewall-1.json new file mode 100644 index 0000000..782b4f4 --- /dev/null +++ b/source/networkFirewallAutomation/config/firewalls/firewall-1.json @@ -0,0 +1,8 @@ +{ + "FirewallName": "Firewall-1", + "FirewallPolicyArn": "./firewallPolicies/firewall-policy-1.json", + "Description": "Network Firewall 1", + "DeleteProtection": true, + "FirewallPolicyChangeProtection": true, + "SubnetChangeProtection": true +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/index.ts b/source/networkFirewallAutomation/index.ts new file mode 100644 index 0000000..49c47bb --- /dev/null +++ b/source/networkFirewallAutomation/index.ts @@ -0,0 +1,88 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/** + * @description + * AWS Network Firewall Manager Solution + * @author aws-solutions + */ + +import { + EnvironmentProps, + NetworkFirewallManager +} from "./lib/network-firewall-manager" +import { + Ec2EnvironmentProps, + Ec2Manager +} from "./lib/ec2-manager" +import { ConfigReader, ConfigPath } from "./lib/common/configReader/config-reader" +import { Logger, LOG_LEVEL } from "./lib/common/logger" + + +async function firewallManager() { + + // declare environment variables + let envProps: EnvironmentProps = { + vpcId: process.env.VPC_ID, + subnetIds: process.env.SUBNET_IDS, + logDestinationType: process.env.LOG_DESTINATION_TYPE, //S3 or CloudWatchLogs + logDestination: process.env.S3_LOG_BUCKET_NAME !== 'NotConfigured' ? process.env.S3_LOG_BUCKET_NAME : process.env.CLOUDWATCH_LOG_GROUP_NAME, //S3 bucket name or CloudWatchLogs group name + logType: process.env.LOG_TYPE, //ALERT OR FLOW + logRetentionPeriod: process.env.LOG_RETENTION_IN_DAYS, + stackId: process.env.STACK_ID ? process.env.STACK_ID : "" + } + + const transitGatewayAttachmentId = process.env.TRANSIT_GATEWAY_ATTACHMENT_ID ? process.env.TRANSIT_GATEWAY_ATTACHMENT_ID : ""; + const applianceMode = process.env.TRANSIT_GATEWAY_ATTACHMENT_APPLIANCE_MODE ? process.env.TRANSIT_GATEWAY_ATTACHMENT_APPLIANCE_MODE : "enable"; + Ec2Manager.updateTransitGatewayAttachementApplianceMode(transitGatewayAttachmentId, applianceMode); + + + let ec2EnvProps: Ec2EnvironmentProps[] = [ + { + availabilityZone: process.env.VPC_TGW_ATTACHMENT_AZ_1, + routeTableId: process.env.VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_1 + }, + { + availabilityZone: process.env.VPC_TGW_ATTACHMENT_AZ_2, + routeTableId: process.env.VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_2 + }] + + try { + const currentPath = process.cwd() + const directoryPath = currentPath.concat(ConfigPath.firewallDirectory) + + const fileHandler = new ConfigReader() + const firewallFiles = fileHandler.getJSONFileNames(directoryPath) + + for (let filePath of firewallFiles) { + + Logger.log(LOG_LEVEL.INFO, `Processing ${filePath}`) + let firewallObject = fileHandler.convertFileToObject(filePath) + Logger.log(LOG_LEVEL.INFO, firewallObject) + let firewallMgr = new NetworkFirewallManager(envProps, firewallObject, fileHandler) + const syncStates = await firewallMgr.firewallOperations() + Logger.log(LOG_LEVEL.INFO, syncStates) + Logger.log(LOG_LEVEL.INFO, `Creating route to firewall endpoint.`) + if (syncStates) { + const ec2Mgr = new Ec2Manager(ec2EnvProps, syncStates) + await ec2Mgr.routeTableOperations() + } + } + } catch (error) { + Logger.log(LOG_LEVEL.ERROR, `Failed to deploy/update Network Firewall`, error) + process.exit(1) + } +} + +// Initiating Network Firewall Manager Solution +firewallManager() \ No newline at end of file diff --git a/source/networkFirewallAutomation/lib/common/configReader/config-reader.ts b/source/networkFirewallAutomation/lib/common/configReader/config-reader.ts new file mode 100644 index 0000000..488ae6d --- /dev/null +++ b/source/networkFirewallAutomation/lib/common/configReader/config-reader.ts @@ -0,0 +1,63 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +import * as fs from 'fs' +import * as path from 'path' +import { Logger, LOG_LEVEL } from '../logger' + +export enum ConfigPath { + firewallDirectory = '/firewalls' +} +/** + * @description This class reads the json files and return file objects + */ +export class ConfigReader { + + /** + * @description This method will return the json file names in the path. + * @param directoryPath string value of the file system path. + * @returns Array of file names. + */ + getJSONFileNames(directoryPath: string): string[] { + Logger.log(LOG_LEVEL.DEBUG, `Config directory path: ${directoryPath}`) + return fs.readdirSync(directoryPath) + .filter((name: any) => path.extname(name) === '.json') + .map((name: any) => (path.join(directoryPath, name))) + } + + /** + * This method will read the file contents and attempt to convert the file content into JSON object. + * @returns JSON object of the file content. + * @param filePath string value of absolute file path. + */ + convertFileToObject(filePath: string): any { + Logger.log(LOG_LEVEL.DEBUG, `Returning object for file: ${filePath}`) + return JSON.parse(fs.readFileSync(filePath).toString()) + } + + /** + * This method will read the file contents and attempt to convert the file content into a string. Method will return an empty string + * if the file path is incorrect or invalid. + * @returns String representation of the file content. + * @param filePath string value of absolute file path. + */ + copyFileContentToString(filePath: string): any { + Logger.log(LOG_LEVEL.DEBUG, `Returning string content for file: ${filePath}`) + try { + return fs.readFileSync(filePath).toString() + } catch(error) { + Logger.log(LOG_LEVEL.DEBUG, `Error converting the file content to string:`, error) + return ""; + } + } + +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/lib/common/firewall-config-validation.ts b/source/networkFirewallAutomation/lib/common/firewall-config-validation.ts new file mode 100644 index 0000000..15be7fa --- /dev/null +++ b/source/networkFirewallAutomation/lib/common/firewall-config-validation.ts @@ -0,0 +1,214 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { NetworkFirewall } from "aws-sdk"; +import { Logger, LOG_LEVEL } from "./logger"; +import { ConfigReader, ConfigPath } from "./configReader/config-reader"; +import { MetricsManager, NetworkFirewallMetrics } from "./send-metrics"; + +interface InvalidConfigFiles { + path: string; + referencedInFile?: any; + error?: any; +} + +export class FirewallConfigValidation { + + private invalidFiles: InvalidConfigFiles[]; + private service: NetworkFirewall; + private fileHandler: ConfigReader; + + constructor() { + this.invalidFiles = [] + this.service = new NetworkFirewall() + this.fileHandler = new ConfigReader() + } + + getInvalidFiles() { + return this.invalidFiles + } + + /** + * This method will validate all the files in starting with firewall, firewall policy and rule groups, all the invalid + * files will be output to the console and an error is thrown, + * if there no invalid files the validation will exit without any error. + * @param rootDir optional if the value is not provided the path configured in the ConfigPath is taken as directory. + */ + async execute(rootDir?: string) { + const metrics: NetworkFirewallMetrics = { + numberOfFirewalls: 0, + numberOfPolicies: 0, + numberOfStatefulRuleGroups: 0, + numberOfStatelessRuleGroups: 0, + numberOfSuricataRules: 0 + } + Logger.log(LOG_LEVEL.INFO, `Starting firewall config validation`) + try { + const currentPath = process.cwd() + let directoryPath; + if (rootDir) { + directoryPath = currentPath.concat(rootDir) + } else { + directoryPath = currentPath.concat(ConfigPath.firewallDirectory) + } + Logger.log(LOG_LEVEL.INFO, `Config file path ${directoryPath}`) + const firewallFiles = this.fileHandler.getJSONFileNames(directoryPath) + metrics.numberOfFirewalls = firewallFiles.length + + for (let firewallFile of firewallFiles) { + Logger.log(LOG_LEVEL.INFO, `Validating the file paths for the firewall file named: ${firewallFile}`) + let firewall: NetworkFirewall.Types.CreateFirewallRequest = this.fileHandler.convertFileToObject(firewallFile) + + this.validateFirewallFile(firewall) + + let firewallPolicy: NetworkFirewall.Types.CreateFirewallPolicyRequest; + + //verify firewall policy. + try { + firewallPolicy = this.fileHandler.convertFileToObject(firewall.FirewallPolicyArn) + metrics.numberOfPolicies += 1 + await this.validateFirewallPolicyFile(firewallPolicy, firewall.FirewallPolicyArn) + } catch (error) { + Logger.log(LOG_LEVEL.INFO, `Failed to validate the firewall policy`) + this.invalidFiles.push({ + path: firewall.FirewallPolicyArn, + referencedInFile: firewall.FirewallPolicyArn, + error: "The file in the attribute path is not available in the configuration." + }) + break; + } + + //loop through all the stateful rule groups and verify if the files compile to a valid json object. + if (firewallPolicy.FirewallPolicy.StatefulRuleGroupReferences) { + metrics.numberOfStatefulRuleGroups += firewallPolicy.FirewallPolicy.StatefulRuleGroupReferences.length + Logger.log(LOG_LEVEL.DEBUG, `Firewall Policy StatefulRuleGroupReferences`, firewallPolicy.FirewallPolicy.StatefulRuleGroupReferences) + for (let statefulRuleGroup of firewallPolicy.FirewallPolicy.StatefulRuleGroupReferences) { + try { + const ruleGroup: NetworkFirewall.Types.CreateRuleGroupRequest = this.fileHandler.convertFileToObject(statefulRuleGroup.ResourceArn); + if (ruleGroup.Rules) { + metrics.numberOfSuricataRules += 1; + } + await this.validateRuleGroupFile(ruleGroup, statefulRuleGroup.ResourceArn) + } catch (error) { + this.invalidFiles.push({ + path: statefulRuleGroup.ResourceArn, + referencedInFile: firewall.FirewallPolicyArn, + error: "The file in the attribute path is not available in the configuration." + }) + } + } + } + //loop through all the stateless rule groups and verify if the files compile to a valid json object. + if (firewallPolicy.FirewallPolicy.StatelessRuleGroupReferences) { + metrics.numberOfStatelessRuleGroups += firewallPolicy.FirewallPolicy.StatelessRuleGroupReferences.length + Logger.log(LOG_LEVEL.DEBUG, `Firewall Policy StatelessRuleGroupReferences`, firewallPolicy.FirewallPolicy.StatelessRuleGroupReferences) + for (let statelessRuleGroup of firewallPolicy.FirewallPolicy.StatelessRuleGroupReferences) { + try { + const ruleGroup = this.fileHandler.convertFileToObject(statelessRuleGroup.ResourceArn) + await this.validateRuleGroupFile(ruleGroup, statelessRuleGroup.ResourceArn) + } catch (error) { + this.invalidFiles.push({ + path: statelessRuleGroup.ResourceArn, + referencedInFile: firewall.FirewallPolicyArn, + error: "The file in the attribute path is not available in the configuration." + }) + } + } + } + } + + } catch (error) { + Logger.log(LOG_LEVEL.ERROR, error) + throw new Error("Validation failed."); + } finally { + Logger.log(LOG_LEVEL.INFO, `Number of invalid files: ${this.invalidFiles.length}`) + Logger.log(LOG_LEVEL.INFO, `-----------INVALID FILES START-----------`) + this.getInvalidFiles().forEach((invalidFile) => { + Logger.log(LOG_LEVEL.ERROR, invalidFile) + }) + Logger.log(LOG_LEVEL.INFO, `-----------INVALID FILES END--------------`) + if (this.invalidFiles.length > 0) { + const error = "Validation failed." + Logger.log(LOG_LEVEL.ERROR, error) + throw error + } + Logger.log(LOG_LEVEL.DEBUG, `Send metrics`, metrics) + MetricsManager.sendMetrics(metrics) + } + } + + async validateFirewallPolicyFile(firewallPolicy: NetworkFirewall.Types.CreateFirewallPolicyRequest, path: string) { + firewallPolicy.DryRun = true; + let response; + try { + response = await this.service.createFirewallPolicy(firewallPolicy).promise() + } catch (error) { + const errorCode: string = error["code"] + Logger.log(LOG_LEVEL.DEBUG, `Error response from the create firewall policy dry run API`, error) + if (errorCode === "MultipleValidationErrors" || errorCode === "UnexpectedParameter") { + this.invalidFiles.push({ + path: path, + error: error["message"] + }) + } + } + Logger.log(LOG_LEVEL.DEBUG, `Response from the create firewall policy dry run API`, response) + } + async validateRuleGroupFile(ruleGroup: NetworkFirewall.Types.CreateRuleGroupRequest, path: string) { + //add code to check if this rule source is provided or rules file is being provided + if (ruleGroup.Rules && ruleGroup.RuleGroup) { + Logger.log(LOG_LEVEL.DEBUG, `Rule Group file has both Rules and RuleGroup fields.`, ruleGroup) + this.invalidFiles.push({ + path: path, + error: "Both RuleGroup and Rules have data, You must provide either the rule group setting or a Rules setting, but not both. " + }) + return; + } else if (ruleGroup.Rules) { + const ruleString = this.fileHandler.copyFileContentToString(ruleGroup.Rules) + if (!ruleString) { + ruleGroup.Rules = ruleString + this.invalidFiles.push({ + path: path, + error: "Rules attribute has invalid file path. " + ruleGroup.Rules + }) + } + Logger.log(LOG_LEVEL.DEBUG, `Rule Group file has both Rules and RuleGroup fields.`, ruleGroup.Rules) + } + + ruleGroup.DryRun = true; + let response; + try { + response = await this.service.createRuleGroup(ruleGroup).promise(); + } catch(error) { + Logger.log(LOG_LEVEL.DEBUG, `Error response from the create rule group dry run API`, error) + const errorCode: string = error["code"] + if (errorCode === "MultipleValidationErrors" || errorCode === "UnexpectedParameter") { + this.invalidFiles.push({ + path: path, + error: error["message"] + }) + } + } + Logger.log(LOG_LEVEL.DEBUG, `Response from the create rule group dry run API`, response) + } + + validateFirewallFile(firewall: NetworkFirewall.Types.CreateFirewallRequest) { + if (!firewall.FirewallName || !firewall.FirewallPolicyArn) { + this.invalidFiles.push({ + path: firewall.FirewallName, + referencedInFile: firewall.FirewallName, + error: "FirewallName and FirewallPolicyArn are required in the firewall." + }) + } + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/lib/common/logger.ts b/source/networkFirewallAutomation/lib/common/logger.ts new file mode 100644 index 0000000..f1c69f9 --- /dev/null +++ b/source/networkFirewallAutomation/lib/common/logger.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +export enum LOG_LEVEL { + "ERROR", + "WARN", + "INFO", + "DEBUG" +} + +export class Logger { + + private static readonly CONFIGURED_LOG_LEVEL = process.env.LOG_LEVEL && Object.values(LOG_LEVEL).indexOf(process.env.LOG_LEVEL.toUpperCase()) != -1 ? Object.values(LOG_LEVEL).indexOf(process.env.LOG_LEVEL.toUpperCase()) : LOG_LEVEL.ERROR; + + constructor() { } + + static log(log_level: LOG_LEVEL, message: any, object?: any) { + if (log_level <= this.CONFIGURED_LOG_LEVEL) { + let currentDateTime = new Date() + let formatted_date = `${currentDateTime.getFullYear()}-${(currentDateTime.getMinutes()-1)}-${currentDateTime.getDate()} ${currentDateTime.getHours()}:${currentDateTime.getMinutes()}:${currentDateTime.getSeconds()}` + let log_message = `${formatted_date} : ${JSON.stringify(message, null, 2)}` + if (object) { + log_message = `${formatted_date} : ${JSON.stringify(message, null, 2)} : ${JSON.stringify(object, null, 2)}` + } + console.log(log_message) + } + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/lib/common/send-metrics.ts b/source/networkFirewallAutomation/lib/common/send-metrics.ts new file mode 100644 index 0000000..527e357 --- /dev/null +++ b/source/networkFirewallAutomation/lib/common/send-metrics.ts @@ -0,0 +1,80 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +import { v4 as uuidv4 } from "uuid" +import { SSM } from "aws-sdk" +import axios from "axios" +import { Logger, LOG_LEVEL } from "./logger" + +export interface NetworkFirewallMetrics { + numberOfFirewalls: number, + numberOfStatefulRuleGroups: number, + numberOfStatelessRuleGroups: number, + numberOfPolicies: number, + numberOfSuricataRules: number, + logType?: string + logDestinationType?: string +} + +export class MetricsManager { + + private constructor() { } + + static async sendMetrics(data: NetworkFirewallMetrics) { + const ssmParameterForUUID = process.env.SSM_PARAM_FOR_UUID ? process.env.SSM_PARAM_FOR_UUID : "network-firewall-solution-uuid" + const stackId = process.env.STACK_ID ? process.env.STACK_ID.slice(process.env.STACK_ID.length - 36) : "" + const sendAnonymousMetrics = process.env.SEND_ANONYMOUS_METRICS ? process.env.SEND_ANONYMOUS_METRICS : "No" + let uuid = "" + const ssmUUIDKey = `${ssmParameterForUUID}-${stackId}` + try { + if (sendAnonymousMetrics.toUpperCase() === "YES") { + let ssmInstance = new SSM(); + let ssmGetParamResponse; + try { + ssmGetParamResponse = await ssmInstance.getParameter({ + Name: ssmUUIDKey, + }).promise(); + uuid = ssmGetParamResponse.Parameter?.Value ? ssmGetParamResponse.Parameter?.Value : uuidv4(); + } catch (error) { + if (error["code"] = "ParameterNotFound") { + uuid = uuidv4(); + await ssmInstance.putParameter({ + Name: ssmUUIDKey, + Value: uuid, + Type: "String" + }).promise(); + } + } + Logger.log(LOG_LEVEL.DEBUG, "uuid: ", uuid) + const metricsUrl: string = process.env.METRICS_URL ? process.env.METRICS_URL : "" + const solutionId: string | undefined = process.env.SOLUTION_ID + const timestamp = (new Date()).toISOString() + data.logDestinationType = process.env.LOG_DESTINATION_TYPE + data.logType = process.env.LOG_TYPE + const metrics_data = { + 'Solution': solutionId, + 'TimeStamp': timestamp, + 'UUID': uuid, + 'Data': data + } + Logger.log(LOG_LEVEL.DEBUG, "metrics data: ", metrics_data) + const response = await axios.post(metricsUrl, metrics_data, { + headers: { + 'Content-Type': 'application/json', + 'Content-Length': JSON.stringify(data).length + } + }) + Logger.log(LOG_LEVEL.DEBUG, 'Response: ', response) + } + } catch (error) { } + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/lib/common/stringUtils.ts b/source/networkFirewallAutomation/lib/common/stringUtils.ts new file mode 100644 index 0000000..e2fcc6b --- /dev/null +++ b/source/networkFirewallAutomation/lib/common/stringUtils.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { Logger, LOG_LEVEL } from './logger' + +export enum Name { + maxCharacters = 128, + delimiter= '-' +} + +/** + * @description This class performs string manipulation operations + */ +export class StringUtils { + + constructor(readonly stackId: string) { + } + + + /** + * @description This method will return name of the resource with parsed + * stack id and validates the max character allowed + * @param resourceName + * @returns modified resource name. + */ + getUniqueResourceName(resourceName: string) { + Logger.log(LOG_LEVEL.DEBUG, `Resource name input: ${resourceName}`) + if (this.stackId) { + const splitStackId = this.stackId.split("-").pop() + let customName = resourceName + Name.delimiter + splitStackId + if (splitStackId && customName.length > Name.maxCharacters) { + const sliceString = Name.maxCharacters - (splitStackId.length + Name.delimiter.length) + Logger.log(LOG_LEVEL.INFO, `Modified name is larger than 128 characters, trimming the resource name and using only first ${sliceString.toString()} characters from the name.`) + const trimmedResourceName = resourceName.substring(0, sliceString) + customName = trimmedResourceName + Name.delimiter + splitStackId + } + Logger.log(LOG_LEVEL.DEBUG, `Returning Custom name : ${resourceName}`) + return customName + } + else { + throw Error("The stack id environment variable is undefined in the" + + " CodeBuild stage environment variables.") + } + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/lib/ec2-manager.ts b/source/networkFirewallAutomation/lib/ec2-manager.ts new file mode 100644 index 0000000..5d816e4 --- /dev/null +++ b/source/networkFirewallAutomation/lib/ec2-manager.ts @@ -0,0 +1,170 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { EC2, NetworkFirewall } from "aws-sdk" +import { Ec2Service } from "./service/ec2-service" +import { LOG_LEVEL, Logger } from "./common/logger" + +export interface Ec2EnvironmentProps { + availabilityZone: string | undefined, + routeTableId: string | undefined +} + +export enum Route { + default = '0.0.0.0/0', + active = 'active' +} + +type routeStatus = { + VpcEndpointId: string | undefined, + RouteTableId: string, + DefaultRouteCreated: boolean +} + +/** + * @description This class contains all the methods to + * perform CRUD operations for the VPC route to Network Firewall. + */ +export class Ec2Manager { + + private service: Ec2Service + private vpcEndpoint: string | undefined + + constructor(public envProps: Ec2EnvironmentProps[], + public firewallSyncStates: NetworkFirewall.SyncStates) { + this.service = new Ec2Service() + } + + /** this method will check if route exists, if not will start the process to + * create the route, If route exists no action required. If any of the VPC + * endpoint is not in READY status, throw an error. + */ + async routeTableOperations(): Promise { + try { + let response: routeStatus[] = [] + for (let endpoint of this.envProps) { + Logger.log(LOG_LEVEL.INFO, `Processing `, endpoint) + + // check if routes already exist + if (endpoint.routeTableId && endpoint.availabilityZone) { + const attachmentProps = this.firewallSyncStates[endpoint.availabilityZone] + this.vpcEndpoint = attachmentProps.Attachment?.EndpointId + const foundExistingRoute = await this.checkRouteTable(endpoint.routeTableId) + + if (!foundExistingRoute) { + Logger.log(LOG_LEVEL.INFO, `Default route to Network Firewall does not exist. Creating a new default route using endpoint: ${this.vpcEndpoint} in the ready state.`) + await this.service.createRoute({ + DestinationCidrBlock: Route.default, + VpcEndpointId: this.vpcEndpoint, + RouteTableId: endpoint.routeTableId + }) + } + let status = { + VpcEndpointId: this.vpcEndpoint, + RouteTableId: endpoint.routeTableId, + DefaultRouteCreated: !foundExistingRoute + } + response.push(status) + } + } + + return response + + } catch + (error) { + Logger.log(LOG_LEVEL.ERROR, error) + throw new Error(error["message"]) + } + } + + /** + * Describe route table and analyse routes + */ + async checkRouteTable(routeTableId: string) { + // get route table details to check route already exist + const routeTables = await this.service.describeRouteTables(routeTableId) + Logger.log(LOG_LEVEL.INFO, routeTables) + + // the describe route table API should always return single value if using + // route table id + if (routeTables && routeTables.length > 1) { + Logger.log(LOG_LEVEL.DEBUG, routeTables) + throw Error(`Expected only one item in the route table array. Received : ${routeTables.length} `) + } + + let foundExistingRoute: boolean = false + // at least 1 value should be present before attempting the iteration + if (routeTables && routeTables.length == 1) { + + // the for loop would iterate only once + for (let routeTable of routeTables) { + foundExistingRoute = await this.checkExistingRoutes(routeTable) + } + } + return foundExistingRoute + } + + /** + * This method check if there is an existing default route to the VPC + * endpoint to network firewall. If + * @param routeTable + * @return List of VPC Endpoint ids in ready state. Returns empty list if + * route already exists. + */ + async checkExistingRoutes(routeTable: EC2.RouteTable): Promise { + const routes = routeTable.Routes + Logger.log(LOG_LEVEL.DEBUG, `print routes`) + Logger.log(LOG_LEVEL.DEBUG, routes) + if (routes) { + for (let route of routes) { + Logger.log(LOG_LEVEL.DEBUG, `Checking route below for VPC Endpoint: ${this.vpcEndpoint}`) + Logger.log(LOG_LEVEL.DEBUG, route) + if (route.GatewayId && route.GatewayId === this.vpcEndpoint && + route.DestinationCidrBlock === Route.default && route.State === Route.active) { + Logger.log(LOG_LEVEL.INFO, `Found Firewall VPC Endpoint ${route.GatewayId}`) + Logger.log(LOG_LEVEL.INFO, `setting foundExistingRoute to TRUE`) + return Promise.resolve(true) + } else if (route.GatewayId && route.GatewayId != this.vpcEndpoint && route.DestinationCidrBlock === Route.default && route.State === Route.active) { + //remove the route entry as possibly the firewall end point is no longer the same as it was earlier. + if (routeTable.RouteTableId) { + await this.service.deleteRoute({ + DestinationCidrBlock: Route.default, + RouteTableId: routeTable.RouteTableId + }) + } + } + } + } + // return false - could not find existing route + Logger.log(LOG_LEVEL.INFO, `Firewall VPC Endpoint not found as destination in the route table.`) + return Promise.resolve(false) + } + + /** + * Method will update the transit gateway attachement appliance mode. + * https://docs.aws.amazon.com/cli/latest/reference/ec2/modify-transit-gateway-vpc-attachment.html + * @param transitGatewayAttachmentId + * @param applianceMode + */ + static async updateTransitGatewayAttachementApplianceMode(transitGatewayAttachmentId: string, applianceMode: string) { + if (transitGatewayAttachmentId && applianceMode) { + const response = await new Ec2Service().modifyTransitGatewayAttachement({ + TransitGatewayAttachmentId: transitGatewayAttachmentId, + Options: { + ApplianceModeSupport: applianceMode + } + }) + Logger.log(LOG_LEVEL.INFO, `Response from modifyTransitGatewayAttachement API: `, response) + } + } +} diff --git a/source/networkFirewallAutomation/lib/network-firewall-manager.ts b/source/networkFirewallAutomation/lib/network-firewall-manager.ts new file mode 100644 index 0000000..184b005 --- /dev/null +++ b/source/networkFirewallAutomation/lib/network-firewall-manager.ts @@ -0,0 +1,491 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + + +import { NetworkFirewall } from "aws-sdk" +import { NetworkFirewallService } from "./service/network-firewall-service" +import { ConfigReader } from "./common/configReader/config-reader" +import { Time } from "./service/awsClientConfig"; +import { LOG_LEVEL, Logger } from "./common/logger" +import { StringUtils } from "./common/stringUtils"; + +enum LogType { + alert = "ALERT", + flow = "FLOW" +} + +export interface EnvironmentProps { + vpcId: string | undefined; + subnetIds: string | undefined; + logDestinationType: "S3" | "CloudWatchLogs" | string | undefined; + logDestination: string | undefined; //bucket name or cloudwatch log group name. + logType: "Alert" | "Flow" | "EnableBoth" | string | undefined; + logRetentionPeriod: string | undefined; + stackId: string; +} + +enum RuleGroupType { + Stateless = 'STATELESS', + Stateful = 'STATEFUL' +} + +/** + * @description This class contains all the Network Firewall methods to + * perform CRUD operations for the Network Firewall resources. + */ +export enum FirewallStatus { + Ready = 'READY', + ConfigInSync = 'IN_SYNC', +} + +export class NetworkFirewallManager { + + private stringUtils: StringUtils + private service: NetworkFirewallService + private ruleGroupArnsInFirewall: string[] = [] + + constructor(public envProps: EnvironmentProps, + public firewallObject: NetworkFirewall.Types.CreateFirewallRequest, + public fileHandler: ConfigReader) { + this.service = new NetworkFirewallService() + this.stringUtils = new StringUtils(envProps.stackId) + } + + /** get vpc id */ + getVpcId(): NetworkFirewall.VpcId { + let vpcId + if (this.envProps.vpcId) { + vpcId = this.envProps.vpcId + } else { + const error_msg = "VPC ID must be in the environment variables" + Logger.log(LOG_LEVEL.ERROR, error_msg) + throw Error(error_msg) + } + return vpcId + } + + /** get subnet mapping */ + getSubnetMapping(): NetworkFirewall.SubnetMappings { + let subnetIdArray + let subnetMappings + + if (this.envProps.subnetIds) { + subnetIdArray = this.envProps.subnetIds.split(",") + subnetMappings = subnetIdArray.map((subnetId: string) => { + return { + SubnetId: subnetId + } + }) + } else { + const error_msg = "Subnet IDs must be in the environment variables" + Logger.log(LOG_LEVEL.ERROR, error_msg) + throw Error(error_msg) + } + return subnetMappings + } + + /** Function to add delay for waiting on process. */ + delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + + /** Function will create network firewall and wait until the status of the firewall is provisioned before returning the response to the calling + * function. + */ + async createNetworkFirewall(firewallPolicyArn: string): Promise { + this.firewallObject['VpcId'] = this.getVpcId() || '' + this.firewallObject['SubnetMappings'] = this.getSubnetMapping() + this.firewallObject.FirewallPolicyArn = firewallPolicyArn + + // create network firewall + await this.service.createFirewall(this.firewallObject) + + // check + return await this.checkFirewallStatus() + + } + + /** Function will check if firewall exists, if not will start the process to create rule groups, create the firewall policy + * and then create the firewall. If firewall exists the configs are updated starting with rule groups, firewall policy and finally firewall. + */ + async firewallOperations(): Promise { + let response; + try { + // update firewall name to unique firewall name + this.firewallObject.FirewallName = this.stringUtils.getUniqueResourceName(this.firewallObject.FirewallName) + const firewallName = this.firewallObject.FirewallName; + const firewallResponse = await this.service.describeFirewall(firewallName) + if (firewallResponse && firewallResponse.Firewall) { + Logger.log(LOG_LEVEL.INFO, `Updating existing firewall: ${firewallName}`) + const firewallPolicyArn = await this.firewallPolicyOperations(this.firewallObject.FirewallPolicyArn) + Logger.log(LOG_LEVEL.INFO, `Checking Firewall Status: ${firewallPolicyArn}`) + response = await this.checkFirewallStatus() + await this.updateFirewall(firewallResponse, firewallPolicyArn) + } else { + Logger.log(LOG_LEVEL.INFO, `Firewall does not exist: ${firewallName}`) + Logger.log(LOG_LEVEL.INFO, `Checking if firewall policy exist`) + const firewallPolicyArn = await this.firewallPolicyOperations(this.firewallObject.FirewallPolicyArn) + Logger.log(LOG_LEVEL.INFO, `Creating Firewall: ${firewallName}`) + response = await this.createNetworkFirewall(firewallPolicyArn) + } + await this.setupLoggingConfigurations(firewallName) + return response; + } catch (error) { + Logger.log(LOG_LEVEL.ERROR, error) + throw new Error(error) + } + } + + /** + * This method will check if the firewall status is in READY state, firewall config sync state is 'IN_SYNC', and + * also waits until all the attachments created in each availability zone is also in IN_SYNC state. + */ + + async checkFirewallStatus(): Promise { + let firewallStatus: string | undefined = '' + let firewallConfigSyncState: string | undefined = '' + let syncStates: NetworkFirewall.SyncStates | undefined = {}; + let areAttachmentsInReadyStatus = false; + + do { + // sleep + await this.delay(Time.Seconds15) + let attachmentStatus = [] + //describe firewall + const firewallResponse = await this.service.describeFirewall(this.firewallObject.FirewallName) + if (firewallResponse && firewallResponse.FirewallStatus) { + firewallStatus = firewallResponse.FirewallStatus.Status + firewallConfigSyncState = firewallResponse.FirewallStatus.ConfigurationSyncStateSummary + syncStates = firewallResponse.FirewallStatus.SyncStates + Logger.log(LOG_LEVEL.INFO, firewallResponse.FirewallStatus) + } + + if (syncStates) { + Logger.log(LOG_LEVEL.INFO, `Sync States for the firewall. `, syncStates) + for (let availabilityZone in syncStates) { + if (syncStates[availabilityZone].Attachment) { + attachmentStatus.push(syncStates[availabilityZone].Attachment?.Status) + } + } + } + areAttachmentsInReadyStatus = attachmentStatus.every(status => status === 'READY') + + } + while (firewallStatus != FirewallStatus.Ready || firewallConfigSyncState != FirewallStatus.ConfigInSync || !areAttachmentsInReadyStatus) + + Logger.log(LOG_LEVEL.INFO, "Firewall is ready and configuration is in sync across" + + " all the availability zones. Returning the sync states for all" + + " the availability zones.") + return syncStates + } + + /** Function to create/update firewall policy */ + async firewallPolicyOperations(policyPath: string): Promise { + let describePolicyResponse; + try { + Logger.log(LOG_LEVEL.INFO, `Getting Firewall Policy Object`) + const policyObject: NetworkFirewall.CreateFirewallPolicyRequest = await this.ruleGroupOperations(this.fileHandler.convertFileToObject(policyPath)) + // update policy name to unique policy name + policyObject.FirewallPolicyName = this.stringUtils.getUniqueResourceName(policyObject.FirewallPolicyName) + Logger.log(LOG_LEVEL.INFO, `Checking if Firewall Policy exist: ${policyObject.FirewallPolicyName}`) + Logger.log(LOG_LEVEL.INFO, `Found Firewall Policy, trying to update the policy.`) + describePolicyResponse = await this.service.describeFirewallPolicy(policyObject.FirewallPolicyName) + Logger.log(LOG_LEVEL.INFO, `Describe policy response`, describePolicyResponse) + if (describePolicyResponse && describePolicyResponse.FirewallPolicyResponse.FirewallPolicyArn) { + describePolicyResponse.FirewallPolicy = policyObject.FirewallPolicy + describePolicyResponse.FirewallPolicyResponse.Description = policyObject.Description + describePolicyResponse.FirewallPolicyResponse.Tags = policyObject.Tags + let firewallPolicyUpdateResponse = await this.service.updateFirewallPolicy({ + FirewallPolicyArn: describePolicyResponse.FirewallPolicyResponse.FirewallPolicyArn, + FirewallPolicy: policyObject.FirewallPolicy, + UpdateToken: describePolicyResponse.UpdateToken, + Description: policyObject.Description, + FirewallPolicyName: describePolicyResponse.FirewallPolicyResponse.FirewallPolicyName + }) + Logger.log(LOG_LEVEL.INFO, `Firewall update policy response:`, firewallPolicyUpdateResponse) + //delete the rule groups which are currently in the firewall but not in the new firewall policy file + await this.deleteRuleGroups(policyObject); + return describePolicyResponse.FirewallPolicyResponse.FirewallPolicyArn + + } else { + Logger.log(LOG_LEVEL.INFO, `Firewall Policy does not exist, trying to create the policy.`) + const responseCreateFirewallPolicy = await this.service.createFirewallPolicy(policyObject) + return responseCreateFirewallPolicy.FirewallPolicyResponse.FirewallPolicyArn + } + } catch (error) { + Logger.log(LOG_LEVEL.INFO, error) + throw new Error(error) + } + } + + /** Function to create/update Rule Groups with a back out feature in case there is a failure. */ + async ruleGroupOperations(policyObject: NetworkFirewall.CreateFirewallPolicyRequest): Promise { + Logger.log(LOG_LEVEL.INFO, `Checking rule groups found in the firewall policy`) + let statelessRuleGroupsForRollback = [] + let statefulRuleGroupsForRollback = [] + this.ruleGroupArnsInFirewall = await this.service.listRuleGroupsForPolicy(policyObject.FirewallPolicyName); + + try { + + if (policyObject.FirewallPolicy.StatelessRuleGroupReferences) { + for (let statelessRuleGroupReference of policyObject.FirewallPolicy.StatelessRuleGroupReferences) { + let statelessRuleGroupObject: NetworkFirewall.CreateRuleGroupRequest = await this.fileHandler.convertFileToObject(statelessRuleGroupReference.ResourceArn) + Logger.log(LOG_LEVEL.INFO, `Checking if stateless rule group exists: ${statelessRuleGroupObject.RuleGroupName}`) + let describeRuleGroupResponse = await this.service.describeRuleGroup( + statelessRuleGroupObject.RuleGroupName, + RuleGroupType.Stateless + ) + Logger.log(LOG_LEVEL.INFO, `Describe Rule group response`, describeRuleGroupResponse) + if (describeRuleGroupResponse) { + statelessRuleGroupsForRollback.push(describeRuleGroupResponse) + Logger.log(LOG_LEVEL.INFO, `Found existing stateless rule group, trying to update it.`) + await this.service.updateRuleGroup({ + UpdateToken: describeRuleGroupResponse.UpdateToken, + Description: statelessRuleGroupObject.Description, + RuleGroup: statelessRuleGroupObject.RuleGroup, + RuleGroupArn: describeRuleGroupResponse.RuleGroupResponse.RuleGroupArn, + Type: statelessRuleGroupObject.Type + }) + statelessRuleGroupReference.ResourceArn = describeRuleGroupResponse.RuleGroupResponse.RuleGroupArn + } else { + Logger.log(LOG_LEVEL.INFO, `Creating rule group: ${statelessRuleGroupObject.RuleGroupName}`) + let createRuleGroupResponse = await this.service.createRuleGroup(statelessRuleGroupObject) + statelessRuleGroupReference.ResourceArn = createRuleGroupResponse.RuleGroupResponse.RuleGroupArn + Logger.log(LOG_LEVEL.INFO, statelessRuleGroupReference) + Logger.log(LOG_LEVEL.INFO, `Create Rule group response`, createRuleGroupResponse) + } + } + } + if (policyObject.FirewallPolicy.StatefulRuleGroupReferences) { + for (let statefulRuleGroupReference of policyObject.FirewallPolicy.StatefulRuleGroupReferences) { + let statefulRuleGroupObject: NetworkFirewall.CreateRuleGroupRequest = this.fileHandler.convertFileToObject(statefulRuleGroupReference.ResourceArn) + if (statefulRuleGroupObject.Rules) { + statefulRuleGroupObject.Rules = this.fileHandler.copyFileContentToString(statefulRuleGroupObject.Rules) + } + Logger.log(LOG_LEVEL.INFO, `Checking if stateful rule group exists: ${statefulRuleGroupObject.RuleGroupName}`) + let describeRuleGroupResponse = await this.service.describeRuleGroup( + statefulRuleGroupObject.RuleGroupName, + RuleGroupType.Stateful + ) + Logger.log(LOG_LEVEL.INFO, `Describe Rule group response`, describeRuleGroupResponse) + if (describeRuleGroupResponse) { + statefulRuleGroupsForRollback.push(describeRuleGroupResponse) + //if its a suricata rule group just update the statefulRuleGroupObject.Rules + if (statefulRuleGroupObject.Rules) { + await this.service.updateRuleGroup({ + UpdateToken: describeRuleGroupResponse.UpdateToken, + Description: statefulRuleGroupObject.Description, + RuleGroupArn: describeRuleGroupResponse.RuleGroupResponse.RuleGroupArn, + Rules: statefulRuleGroupObject.Rules, + Type: statefulRuleGroupObject.Type + }) + } else { + await this.service.updateRuleGroup({ + UpdateToken: describeRuleGroupResponse.UpdateToken, + Description: statefulRuleGroupObject.Description, + RuleGroup: statefulRuleGroupObject.RuleGroup, + RuleGroupArn: describeRuleGroupResponse.RuleGroupResponse.RuleGroupArn, + Type: statefulRuleGroupObject.Type + }) + } + + statefulRuleGroupReference.ResourceArn = describeRuleGroupResponse.RuleGroupResponse.RuleGroupArn + Logger.log(LOG_LEVEL.INFO, `Found existing stateful rule group, trying to update it.`) + } else { + Logger.log(LOG_LEVEL.INFO, `Creating rule group`) + let createRuleGroupResponse = await this.service.createRuleGroup(statefulRuleGroupObject) + statefulRuleGroupReference.ResourceArn = createRuleGroupResponse.RuleGroupResponse.RuleGroupArn + Logger.log(LOG_LEVEL.INFO, statefulRuleGroupReference) + Logger.log(LOG_LEVEL.INFO, `Create Rule group response`, createRuleGroupResponse) + } + } + } + + } catch (error) { + Logger.log(LOG_LEVEL.ERROR, error) + for (let statelessRuleGroup of statelessRuleGroupsForRollback) { + Logger.log(LOG_LEVEL.WARN, `Rolling back stateless rule group`, statelessRuleGroup) + await this.service.updateRuleGroup(statelessRuleGroup) + } + Logger.log(LOG_LEVEL.WARN, `Rolling back stateful rule groups`, statefulRuleGroupsForRollback) + for (let statefulRuleGroup of statefulRuleGroupsForRollback) { + Logger.log(LOG_LEVEL.WARN, `Rolling back stateful rule group`, statefulRuleGroup) + await this.service.updateRuleGroup(statefulRuleGroup) + } + Logger.log(LOG_LEVEL.ERROR, error) + throw Error(error) + } + + return policyObject; + } + + /** + * This method will take the rule groups configured for the firewall before any updates are made and compare with all the rule groups which are in the firewall policy file, + * the missing rule groups in the firewall policy file will be deleted, if the rule groups are associated with any resource in the account out of scope of this + * solution then the rule group will not be deleted. + * @param policyObject -- NetworkFirewall.CreateFirewallPolicyRequest + */ + async deleteRuleGroups(policyObject: NetworkFirewall.CreateFirewallPolicyRequest) { + await this.delay(Time.Seconds15) + Logger.log(LOG_LEVEL.DEBUG, `The rule groups currently configured in the firewall `, this.ruleGroupArnsInFirewall) + //retrieve the rule groups in policy Object + let ruleGroupsInFirewallPolicyFile: { [key: string]: string } = {}; + if (policyObject.FirewallPolicy.StatefulRuleGroupReferences) { + for (let ruleGroup of policyObject.FirewallPolicy.StatefulRuleGroupReferences) { + ruleGroupsInFirewallPolicyFile[ruleGroup.ResourceArn] = ruleGroup.ResourceArn + } + } + if (policyObject.FirewallPolicy.StatelessRuleGroupReferences) { + for (let ruleGroup of policyObject.FirewallPolicy.StatelessRuleGroupReferences) { + ruleGroupsInFirewallPolicyFile[ruleGroup.ResourceArn] = ruleGroup.ResourceArn + } + } + + Logger.log(LOG_LEVEL.DEBUG, `The rule groups configured in the new firewall policy file `, ruleGroupsInFirewallPolicyFile) + for (let oldRuleGroupArn of this.ruleGroupArnsInFirewall) { + if (!ruleGroupsInFirewallPolicyFile[oldRuleGroupArn]) { + await this.service.deleteRuleGroup(oldRuleGroupArn); + } + } + } + + /* + * This method will setup the logging configuration for the firewall, based on the environment properties in EnvironmentProps. + * If there is any error in updating the logging configurations it will log a warning and still continue the rest of the process. + */ + async setupLoggingConfigurations(firewallName: string) { + let loggingConfiguration = await this.createLoggingConfigurations(); + try { + await this.service.updateLoggingConfiguration(firewallName, { + "LogDestinationConfigs": loggingConfiguration + }) + } catch (error) { + Logger.log(LOG_LEVEL.INFO, `Logging configuration: `, loggingConfiguration); + Logger.log(LOG_LEVEL.ERROR, `Failed to update logging configuration`, error) + } + } + + async createLoggingConfigurations() { + let loggingConfiguration = [] + Logger.log(LOG_LEVEL.INFO, this.envProps) + if (this.envProps.logType && this.envProps.logType.toUpperCase() === "ENABLEBOTH") { + let alertConfig = { + LogType: LogType.alert, + LogDestinationType: '', + LogDestination: {} + } + let flowConfig = { + LogType: LogType.flow, + LogDestinationType: '', + LogDestination: {} + } + loggingConfiguration.push(alertConfig) + loggingConfiguration.push(flowConfig) + } else { + let config = { + LogType: this.envProps.logType ? this.envProps.logType.toUpperCase() : LogType.alert, + LogDestinationType: '', + LogDestination: {} + } + loggingConfiguration.push(config) + } + + loggingConfiguration.forEach(config => { + switch (this.envProps.logDestinationType?.toUpperCase()) { + case "S3": + config.LogDestinationType = "S3" + config.LogDestination = { + "bucketName": this.envProps.logDestination, + "prefix": config.LogType === LogType.alert ? "alerts" : "flow" + } + break; + case "CLOUDWATCHLOGS": + config.LogDestinationType = "CloudWatchLogs" + config.LogDestination = { + "logGroup": this.envProps.logDestination + } + break; + } + }) + Logger.log(LOG_LEVEL.INFO, loggingConfiguration) + return Promise.resolve(loggingConfiguration) + } + + /** + * Update firewall properties if they are different from the describeFirewallResponse. + * Following attributes are updated. + * DeleteProtection, FirewallPolicyChangeProtection, Description. + * Associates a new firewall policy arn if the describeFirewallResponse + * and the firewallPolicyArn parameter are not same. + */ + async updateFirewall(describeFirewallResponse: NetworkFirewall.Types.DescribeFirewallResponse, firewallPolicyArn: string) { + + if (describeFirewallResponse.Firewall) { + + //update firewall delete protection attribute + if (describeFirewallResponse.Firewall.DeleteProtection !== this.firewallObject.DeleteProtection) { + const response = await this.service.updateFirewallDeleteProtection({ + FirewallName: this.firewallObject.FirewallName, + DeleteProtection: this.firewallObject.DeleteProtection ? this.firewallObject.DeleteProtection : false + }) + Logger.log(LOG_LEVEL.INFO, 'Update firewall delete protection response: ', response) + + } + + //update firewall policy change protection. + if (describeFirewallResponse.Firewall.FirewallPolicyChangeProtection !== this.firewallObject.FirewallPolicyChangeProtection) { + const response = await this.service.updateFirewallPolicyChangeProtection({ + FirewallName: this.firewallObject.FirewallName, + FirewallPolicyChangeProtection: this.firewallObject.FirewallPolicyChangeProtection ? this.firewallObject.FirewallPolicyChangeProtection : false + }) + Logger.log(LOG_LEVEL.INFO, 'Update firewall policy change protection response: ', response) + } + //update subnet change protection. + if (describeFirewallResponse.Firewall.SubnetChangeProtection !== this.firewallObject.SubnetChangeProtection) { + const response = await this.service.updateSubnetChangeProtection({ + FirewallName: this.firewallObject.FirewallName, + SubnetChangeProtection: this.firewallObject.SubnetChangeProtection ? this.firewallObject.SubnetChangeProtection : false + }) + Logger.log(LOG_LEVEL.INFO, 'Update firewall policy change protection response: ', response) + } + //update firewall description + if (describeFirewallResponse.Firewall.Description !== this.firewallObject.Description) { + const response = await this.service.updateFirewallDescription({ + Description: this.firewallObject.Description, + FirewallName: this.firewallObject.FirewallName + }) + Logger.log(LOG_LEVEL.INFO, 'Update firewall description response: ', response) + } + + //associate firewall policy arn to the firewall. + if (describeFirewallResponse.Firewall.FirewallPolicyArn !== firewallPolicyArn) { + const response = await this.service.associateFirewallPolicy({ + FirewallPolicyArn: firewallPolicyArn, + FirewallName: this.firewallObject.FirewallName + }) + Logger.log(LOG_LEVEL.INFO, `associate/update new firewall policy ${this.firewallObject.FirewallPolicyArn} for the firewall name: ${this.firewallObject.FirewallName} response:`, response) + } + + if (this.firewallObject.Tags && describeFirewallResponse.Firewall.FirewallArn) { + const response = await this.service.tagResource({ + ResourceArn: describeFirewallResponse.Firewall.FirewallArn, + Tags: this.firewallObject.Tags + }) + Logger.log(LOG_LEVEL.INFO, `Update Tags for firewall ${this.firewallObject.FirewallPolicyArn} for the firewall name: ${this.firewallObject.FirewallName} response:`, response) + } + + + } + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/lib/service/awsClientConfig.ts b/source/networkFirewallAutomation/lib/service/awsClientConfig.ts new file mode 100644 index 0000000..31b92cb --- /dev/null +++ b/source/networkFirewallAutomation/lib/service/awsClientConfig.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +import {ConfigurationOptions} from 'aws-sdk' + +export enum Time { + Seconds5 = 5000, + Seconds15 = 15000 +} + +export enum Count { + minRetry = 3, + maxRetry = 10 +} + +/** + * @description This class setup the retry options for AWS APIs + */ +export class AwsClientConfig { + + /** + * @description Retry method returns the ConfigurationOptions instances with retryDelayOptions and maxRetries options set. + * @returns ConfigurationOptions + */ + retry(): ConfigurationOptions { + return { + retryDelayOptions: {base: Time.Seconds5}, + maxRetries: Count.maxRetry + } + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/lib/service/ec2-service.ts b/source/networkFirewallAutomation/lib/service/ec2-service.ts new file mode 100644 index 0000000..37b6cd8 --- /dev/null +++ b/source/networkFirewallAutomation/lib/service/ec2-service.ts @@ -0,0 +1,97 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { ConfigurationOptions, EC2 } from 'aws-sdk' +import { AwsClientConfig } from './awsClientConfig' +import { Logger, LOG_LEVEL } from '../common/logger' + +/** + * Service class which handles all the EC2 API integrations. + */ +export class Ec2Service { + + private Ec2Client: EC2 + config: ConfigurationOptions + + constructor() { + this.config = new AwsClientConfig().retry() + this.Ec2Client = new EC2(this.config); + } + + /** Describes the route. */ + async describeRouteTables(routeTableId: string): Promise { + Logger.log(LOG_LEVEL.INFO, 'Describe Route Table') + Logger.log(LOG_LEVEL.INFO, `Print Route Table Id: ${routeTableId}`) + let response: EC2.DescribeRouteTablesResult + try { + response = await this.Ec2Client.describeRouteTables({ + RouteTableIds: [routeTableId] + } + ).promise() + + let nextToken = response.NextToken + let routeTables = response.RouteTables + + // handle next token + while (nextToken) { + response = await this.Ec2Client.describeRouteTables({ + RouteTableIds: [routeTableId], + NextToken: nextToken + } + ).promise() + if (response.RouteTables) { + routeTables?.concat(response.RouteTables) + } + nextToken = response.NextToken + } + return Promise.resolve(routeTables) + } catch (error) { + Logger.log(LOG_LEVEL.INFO, JSON.stringify(error)) + return Promise.reject(error) + } + } + + /** Creates route in the given route table. */ + async createRoute(props: EC2.CreateRouteRequest): Promise { + Logger.log(LOG_LEVEL.INFO, 'Create Route') + Logger.log(LOG_LEVEL.INFO, `Print Props: `, props) + try { + const response = await this.Ec2Client.createRoute(props).promise() + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + + async deleteRoute(props: EC2.DeleteRouteRequest): Promise { + Logger.log(LOG_LEVEL.INFO, 'delete Route') + Logger.log(LOG_LEVEL.INFO, `Print Props: `, props) + try { + await this.Ec2Client.deleteRoute(props).promise() + return Promise.resolve() + } catch (error) { + return Promise.reject(error) + } + } + + async modifyTransitGatewayAttachement(props: EC2.ModifyTransitGatewayVpcAttachmentRequest) { + Logger.log(LOG_LEVEL.INFO, `modify the transit gateway attachment`) + Logger.log(LOG_LEVEL.INFO, `Print Props: `, props) + try { + const response = await this.Ec2Client.modifyTransitGatewayVpcAttachment(props).promise() + return Promise.resolve(response) + } catch (error) { + return Promise.resolve(error) + } + } +} \ No newline at end of file diff --git a/source/networkFirewallAutomation/lib/service/network-firewall-service.ts b/source/networkFirewallAutomation/lib/service/network-firewall-service.ts new file mode 100644 index 0000000..9c7a0d6 --- /dev/null +++ b/source/networkFirewallAutomation/lib/service/network-firewall-service.ts @@ -0,0 +1,308 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { ConfigurationOptions, NetworkFirewall } from 'aws-sdk' +import { AwsClientConfig, Count } from './awsClientConfig' +import { LOG_LEVEL, Logger } from '../common/logger' + +/** + * Service class which handles all the Network Firewall API integrations. + */ +export class NetworkFirewallService { + + private NetworkFirewallInstance: NetworkFirewall + config: ConfigurationOptions + count: number + + constructor() { + this.config = new AwsClientConfig().retry() + this.count = 0 + this.NetworkFirewallInstance = new NetworkFirewall(this.config); + } + + /** Creates Firewall configurations returns an void/undefined if the firewall doesn't not exist. */ + async createFirewall(props: NetworkFirewall.CreateFirewallRequest) { + Logger.log(LOG_LEVEL.INFO, 'Creating Firewall') + Logger.log(LOG_LEVEL.INFO, `Print Props: ${JSON.stringify(props)}`) + try { + const response = await this.NetworkFirewallInstance.createFirewall(props).promise() + return Promise.resolve(response) + } catch (e) { + if (e.code === "ResourceNotFoundException") { + Logger.log(LOG_LEVEL.INFO, "Firewall Not Found") + return + } + return Promise.reject(e) + } + } + + /** Creates a firewall policy and returns the response object received + * from the Network Firewall API. */ + async createFirewallPolicy(props: NetworkFirewall.CreateFirewallPolicyRequest) { + Logger.log(LOG_LEVEL.INFO, 'Creating Firewall Policy') + Logger.log(LOG_LEVEL.INFO, `Print Props: ${JSON.stringify(props)}`) + return await this.NetworkFirewallInstance.createFirewallPolicy(props).promise() + } + + /** Creates a rule group and returns the response object received from the Network Firewall API */ + async createRuleGroup(props: NetworkFirewall.CreateRuleGroupRequest) { + Logger.log(LOG_LEVEL.INFO, 'Creating Firewall Rule Group') + Logger.log(LOG_LEVEL.INFO, `Print createRuleGroup Props`) + Logger.log(LOG_LEVEL.INFO, props) + return await this.NetworkFirewallInstance.createRuleGroup(props).promise() + } + + /** Describes the firewall based on the input param firewallName, return void/undefined if there is not firewall with the firewall Name defined. */ + async describeFirewall(firewallName: string): Promise { + Logger.log(LOG_LEVEL.INFO, 'Describe Firewall') + Logger.log(LOG_LEVEL.INFO, `Print Firewall Name: ${firewallName}`) + try { + const response = await this.NetworkFirewallInstance.describeFirewall({ + FirewallName: firewallName + } + ).promise() + return Promise.resolve(response) + } catch (error) { + Logger.log(LOG_LEVEL.INFO, JSON.stringify(error)) + if (error.code === "ResourceNotFoundException") { + Logger.log(LOG_LEVEL.INFO, "Firewall Not Found.") + return Promise.resolve() + } + return Promise.reject(error) + } + } + + /** Describes the firewall policy and returns void/undefined if there is no firewall policy with the Name and/or Arn defined */ + async describeFirewallPolicy(firewallPolicyName: string): Promise { + try { + const response = await this.NetworkFirewallInstance.describeFirewallPolicy({ + FirewallPolicyName: firewallPolicyName + }).promise(); + return Promise.resolve(response) + } catch (error) { + Logger.log(LOG_LEVEL.INFO, JSON.stringify(error)) + if (error.code === "ResourceNotFoundException") { + Logger.log(LOG_LEVEL.INFO, "Firewall Policy Not Found.") + return Promise.resolve() + } + return Promise.reject(error) + } + } + + /** Describes the rule group and returns an rule response object from the api, return void/undefined in case none is found, the + * method will retry API calls for a maximum of Count.minRetry value. + */ + async describeRuleGroup(RuleGroupName: string, Type: string): Promise { + do { + try { + Logger.log(LOG_LEVEL.INFO, `Describing Rule Group: ${RuleGroupName} | Type: ${Type}`) + const response = await this.NetworkFirewallInstance.describeRuleGroup({ + RuleGroupName: RuleGroupName, + Type: Type + } + ).promise() + return Promise.resolve(response) + } catch (error) { + Logger.log(LOG_LEVEL.INFO, JSON.stringify(error)) + if (error.message === "ThrottlingException") { + this.count++ //increment the count + Logger.log(LOG_LEVEL.INFO, `Caught throttling exception, trying count: ${this.count}`) + } + if (error.code === "ResourceNotFoundException") { + Logger.log(LOG_LEVEL.INFO, "Rule Group Not Found.") + return Promise.resolve() + } + return Promise.reject(error) + } + } while (this.count == Count.minRetry) + } + + /** Associates the firewall policy to the firewall. */ + async associateFirewallPolicy(request: NetworkFirewall.AssociateFirewallPolicyRequest) { + try { + return await this.NetworkFirewallInstance.associateFirewallPolicy(request).promise() + } catch (error) { + Logger.log(LOG_LEVEL.DEBUG, error) + return Promise.reject(error) + } + } + + /** associate tags to the firewall resource. */ + async tagResource(request: NetworkFirewall.Types.TagResourceRequest) { + try { + return await this.NetworkFirewallInstance.tagResource(request).promise() + } catch (error) { + Logger.log(LOG_LEVEL.ERROR, `Failed to update tags for the firewall ${error}`) + // returning resolve to avoid pipeline failure due to tag change failure. + return Promise.resolve() + } + } + + /** Updates the firewall policy and will override any configurations done to the firewall policy in the AWS console. Method will attempt multiple updates to the + * firewall policy until successful. + */ + async updateFirewallPolicy(request: NetworkFirewall.Types.UpdateFirewallPolicyRequest) { + do { + try { + return await this.NetworkFirewallInstance.updateFirewallPolicy(request).promise() + } catch (error) { + if (error['message'] === 'Update token is invalid.') { + const describeResponse = await this.NetworkFirewallInstance.describeFirewallPolicy({ + FirewallPolicyName: request.FirewallPolicyName + }).promise() + request.UpdateToken = describeResponse.UpdateToken + } else { + Logger.log(LOG_LEVEL.DEBUG, error) + return Promise.reject(error) + } + } + } while (request.UpdateToken) + return Promise.resolve() + } + + async updateRuleGroup(updateRuleGroupRequest: NetworkFirewall.Types.UpdateRuleGroupRequest) { + let updateResponse; + do { + try { + updateResponse = await this.NetworkFirewallInstance.updateRuleGroup(updateRuleGroupRequest).promise(); + updateRuleGroupRequest.UpdateToken = '' + } catch (error) { + if (error['message'] == 'Update token is invalid.') { + const describeResponse = await this.NetworkFirewallInstance.describeRuleGroup({ RuleGroupArn: updateRuleGroupRequest.RuleGroupArn }).promise() + updateRuleGroupRequest.UpdateToken = describeResponse.UpdateToken + } else { + Logger.log(LOG_LEVEL.INFO, `Error while trying to update the rule group ${updateRuleGroupRequest}: ${error}`) + return Promise.reject(error) + } + } + } while (updateRuleGroupRequest.UpdateToken) + Logger.log(LOG_LEVEL.INFO, `update response ${JSON.stringify(updateResponse)}`) + return Promise.resolve(updateResponse); + } + + /** + * Update the firewall description. + * @param request NetworkFirewall.Types.UpdateFirewallDescriptionRequest + */ + async updateFirewallDescription(request: NetworkFirewall.Types.UpdateFirewallDescriptionRequest) { + try { + return await this.NetworkFirewallInstance.updateFirewallDescription(request).promise(); + } catch (error) { + Logger.log(LOG_LEVEL.DEBUG, error) + return Promise.reject(error) + } + } + /** + * Update the firewall delete protection attribute. + * @param request NetworkFirewall.Types.UpdateFirewallDeleteProtectionRequest + */ + async updateFirewallDeleteProtection(request: NetworkFirewall.Types.UpdateFirewallDeleteProtectionRequest) { + try { + return await this.NetworkFirewallInstance.updateFirewallDeleteProtection(request).promise(); + } catch (error) { + Logger.log(LOG_LEVEL.DEBUG, error) + return Promise.reject(error) + } + } + + /** + * Update the firewall policy change protection attribute. + * @param request NetworkFirewall.Types.UpdateFirewallPolicyChangeProtectionRequest + */ + async updateFirewallPolicyChangeProtection(request: NetworkFirewall.Types.UpdateFirewallPolicyChangeProtectionRequest) { + try { + return await this.NetworkFirewallInstance.updateFirewallPolicyChangeProtection(request).promise(); + } catch (error) { + Logger.log(LOG_LEVEL.DEBUG, error) + return Promise.reject(error) + } + } + /** + * Update the subnet change protection attribute. + * @param request NetworkFirewall.Types.UpdateSubnetChangeProtectionRequest + */ + async updateSubnetChangeProtection(request: NetworkFirewall.Types.UpdateSubnetChangeProtectionRequest) { + try { + return await this.NetworkFirewallInstance.updateSubnetChangeProtection(request).promise(); + } catch (error) { + Logger.log(LOG_LEVEL.DEBUG, error) + return Promise.reject(error) + } + } + + async updateLoggingConfiguration(firewallName: string, loggingConfiguration: NetworkFirewall.Types.LoggingConfiguration) { + Logger.log(LOG_LEVEL.INFO, loggingConfiguration) + let describeFirewallLoggingResponse + try { + describeFirewallLoggingResponse = await this.NetworkFirewallInstance.describeLoggingConfiguration({ + FirewallName: firewallName + }).promise() + Logger.log(LOG_LEVEL.INFO, describeFirewallLoggingResponse); + //cleaning up the configuration stack currently in the firewall. + while (describeFirewallLoggingResponse.LoggingConfiguration && describeFirewallLoggingResponse.LoggingConfiguration.LogDestinationConfigs.length > 0) { + + Logger.log(LOG_LEVEL.INFO, describeFirewallLoggingResponse) + if (describeFirewallLoggingResponse.LoggingConfiguration) { + describeFirewallLoggingResponse.LoggingConfiguration.LogDestinationConfigs.pop() + } + + describeFirewallLoggingResponse = await this.NetworkFirewallInstance.updateLoggingConfiguration(describeFirewallLoggingResponse).promise() + } + + for (let config of loggingConfiguration.LogDestinationConfigs) { + describeFirewallLoggingResponse.LoggingConfiguration?.LogDestinationConfigs.push(config) + describeFirewallLoggingResponse = await this.NetworkFirewallInstance.updateLoggingConfiguration(describeFirewallLoggingResponse).promise() + } + + Logger.log(LOG_LEVEL.INFO, describeFirewallLoggingResponse) + } catch (error) { + Logger.log(LOG_LEVEL.INFO, `Failed to update firewall logging configuration`, error) + return Promise.resolve() + } + return Promise.resolve(describeFirewallLoggingResponse) + } + + async listRuleGroupsForPolicy(firewallPolicyName: string): Promise { + let ruleGroupArns: string[] = []; + let response; + + try { + response = await this.NetworkFirewallInstance.describeFirewallPolicy({ FirewallPolicyName: firewallPolicyName }).promise(); + if (response && response.FirewallPolicy) { + response.FirewallPolicy?.StatefulRuleGroupReferences?.forEach((ruleGroup) => { + ruleGroupArns.push(ruleGroup.ResourceArn) + }) + response.FirewallPolicy?.StatelessRuleGroupReferences?.forEach((ruleGroup) => { + ruleGroupArns.push(ruleGroup.ResourceArn) + }) + } else { + Logger.log(LOG_LEVEL.INFO, `No firewall policy of the name: ${firewallPolicyName}`) + return Promise.resolve([]) + } + return Promise.resolve(ruleGroupArns) + } catch (error) { + Logger.log(LOG_LEVEL.INFO, `Error trying to retrieve current rule groups configured ${JSON.stringify(error)}`) + return Promise.resolve([]) + } + + } + + async deleteRuleGroup(ruleGroupArn: string) { + try { + await this.NetworkFirewallInstance.deleteRuleGroup({ RuleGroupArn: ruleGroupArn }).promise() + } catch (error) { + Logger.log(LOG_LEVEL.INFO, `Unable to delete rule group ${JSON.stringify(error)}`) + } + } + +} diff --git a/source/networkFirewallAutomation/package.json b/source/networkFirewallAutomation/package.json new file mode 100644 index 0000000..12a35c5 --- /dev/null +++ b/source/networkFirewallAutomation/package.json @@ -0,0 +1,39 @@ +{ + "name": "network-firewall", + "version": "1.0.0", + "description": "Network Firewall Manager", + "main": "index.js", + "types": "index.d.ts", + "author": "@aws-solutions", + "license": "Apache-2.0", + "dependencies": { + "aws-sdk": "^2.804.0", + "axios": "^0.21.1", + "moment": "^2.27.0", + "uuid": "^8.3.2" + }, + "scripts": { + "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -f package-lock.json", + "watch": "tsc -w", + "test": "jest --coverage", + "cdk": "cdk", + "build:tsc": "tsc", + "build-init": "rm -rf dist && rm -f archive.zip && rm -rf coverage && mkdir -p dist/lib/service && mkdir -p dist/lib/common/configReader", + "build:copy": "for file in `find . -name '*.js' | egrep -v '__tests__|node_modules'`;do echo \"Copying $file\"; cp $file dist/$file; done", + "build:install": "cp package.json dist/ && cd dist && ls -ltRr && npm install --production", + "build": "tsc && npm run build-init && npm run build:copy && npm run build:install", + "zip": "cd dist && zip -rq network-firewall-automation.zip ." + }, + "devDependencies": { + "@types/jest": "^26.0.0", + "@types/moment": "^2.13.0", + "@types/node": "^14.14.10", + "@types/uuid": "^8.3.0", + "aws-sdk-mock": "^5.1.0", + "jest": "^25.0.0", + "jest-sonar-reporter": "^2.0.0", + "ts-jest": "^25.3.1", + "ts-node": "^9.0.0", + "typescript": "^3.4.0" + } +} diff --git a/source/networkFirewallAutomation/tsconfig.json b/source/networkFirewallAutomation/tsconfig.json new file mode 100644 index 0000000..07afb1f --- /dev/null +++ b/source/networkFirewallAutomation/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "alwaysStrict": true, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ + "es2018" + ], + "module": "CommonJS", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "stripInternal": true, + "target": "ES2018" + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ], + "_generated_by_jsii_": "Generated by jsii - safe to delete, and ideally should be in .gitignore" +} diff --git a/source/package.json b/source/package.json new file mode 100755 index 0000000..ae3b71b --- /dev/null +++ b/source/package.json @@ -0,0 +1,47 @@ +{ + "name": "network-firewall-automation-solution", + "version": "1.0.0", + "description": "Network Firewall Automation solution.", + "bin": { + "network-firewall-auto-solution": "bin/network-firewall-auto-solution.js" + }, + "scripts": { + "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -f package-lock.json", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk", + "build": "tsc" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "1.77.0", + "@types/jest": "^25.2.1", + "@types/node": "10.17.5", + "@types/source-map-support": "^0.5.3", + "aws-cdk": "^1.77.0", + "jest": "^25.0.0", + "ts-jest": "^25.3.1", + "ts-node": "^8.1.0", + "typescript": "~3.7.2" + }, + "dependencies": { + "@aws-cdk/aws-codebuild": "1.77.0", + "@aws-cdk/aws-codecommit": "1.77.0", + "@aws-cdk/aws-codedeploy": "1.77.0", + "@aws-cdk/aws-codepipeline": "1.77.0", + "@aws-cdk/aws-codepipeline-actions": "1.77.0", + "@aws-cdk/aws-ec2": "1.77.0", + "@aws-cdk/aws-events-targets": "1.77.0", + "@aws-cdk/aws-kms": "1.77.0", + "@aws-cdk/aws-lambda": "1.77.0", + "@aws-cdk/aws-s3": "1.77.0", + "@aws-cdk/aws-ssm": "1.77.0", + "@aws-cdk/core": "1.77.0", + "source-map-support": "^0.5.16" + } +} diff --git a/source/run-all-tests.sh b/source/run-all-tests.sh new file mode 100755 index 0000000..9ece813 --- /dev/null +++ b/source/run-all-tests.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# +# This script runs all tests for the root CDK project, as well as any microservices, Lambda functions, or dependency +# source code packages. These include unit tests, integration tests, and snapshot tests. +# +# This script is called by the ../initialize-repo.sh file and the buildspec.yml file. It is important that this script +# be tested and validated to ensure that all available test fixtures are run. +# +# The if/then blocks are for error handling. They will cause the script to stop executing if an error is thrown from the +# node process running the test case(s). Removing them or not using them for additional calls with result in the +# script continuing to execute despite an error being thrown. + +# Save the current working directory +source_dir=$PWD + +# Test the CDK project +npm install +npm run build +npm run test +if [ "$?" = "1" ]; then + echo "(source/run-all-tests.sh) ERROR: there is likely output above." 1>&2 + exit 1 +fi + +#Run the npm install for the lambda projects +echo "cd $source_dir/networkFirewallAutomation" +cd $source_dir/networkFirewallAutomation +echo "npm run test" +npm run test + +if [ "$?" = "1" ]; then + echo "(source/run-all-tests.sh) ERROR: there is likely output above." 1>&2 + exit 1 +fi + +# Return to the source/ level +cd $source_dir \ No newline at end of file diff --git a/source/test/__snapshots__/network-firewall-automation-solution.test.ts.snap b/source/test/__snapshots__/network-firewall-automation-solution.test.ts.snap new file mode 100644 index 0000000..7aa6334 --- /dev/null +++ b/source/test/__snapshots__/network-firewall-automation-solution.test.ts.snap @@ -0,0 +1,3364 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NetworkFirewallAutomationStack Snapshot test 1`] = ` +Object { + "AWSTemplateFormatVersion": "2010-09-09", + "Conditions": Object { + "CreateDefaultRouteFirewallRT": Object { + "Fn::And": Array [ + Object { + "Fn::Not": Array [ + Object { + "Fn::Equals": Array [ + Object { + "Ref": "TransitGatewayRTIdForDefaultRoute", + }, + "", + ], + }, + ], + }, + Object { + "Condition": "CreateTransitGatewayAttachment", + }, + ], + }, + "CreateTransitGatewayAttachment": Object { + "Fn::Not": Array [ + Object { + "Fn::Equals": Array [ + Object { + "Ref": "ExistingTransitGateway", + }, + "", + ], + }, + ], + }, + "CreateTransitGatewayRTAssociation": Object { + "Fn::And": Array [ + Object { + "Fn::Not": Array [ + Object { + "Fn::Equals": Array [ + Object { + "Ref": "TransitGatewayRouteTableIdForAssociation", + }, + "", + ], + }, + ], + }, + Object { + "Condition": "CreateTransitGatewayAttachment", + }, + ], + }, + "LoggingInCloudWatch": Object { + "Fn::Equals": Array [ + Object { + "Ref": "logDestinationType", + }, + "CloudWatchLogs", + ], + }, + "LoggingInS3": Object { + "Fn::Equals": Array [ + Object { + "Ref": "logDestinationType", + }, + "S3", + ], + }, + "NotLoggingConfigureManually": Object { + "Fn::Not": Array [ + Object { + "Fn::Equals": Array [ + Object { + "Ref": "logDestinationType", + }, + "ConfigureManually", + ], + }, + ], + }, + }, + "Mappings": Object { + "Send": Object { + "AnonymousUsage": Object { + "Data": "Yes", + }, + "ParameterKey": Object { + "UniqueId": "/Solutions/network-firewall-automation/UUID", + }, + }, + "SolutionMapping": Object { + "CodeCommitRepo": Object { + "Name": "network-firewall-config-repo-", + }, + "Log": Object { + "Level": "info", + }, + "Metrics": Object { + "URL": "https://metrics.awssolutionsbuilder.com/generic", + }, + "Route": Object { + "QuadZero": "0.0.0.0/0", + }, + "Solution": Object { + "Identifier": "SO0108", + }, + "TransitGatewayAttachment": Object { + "ApplianceMode": "enable", + }, + "Version": Object { + "Latest": "latest", + }, + }, + }, + "Metadata": Object { + "AWS::CloudFormation::Interface": Object { + "ParameterGroups": Array [ + Object { + "Label": Object { + "default": "VPC Configuration", + }, + "Parameters": Array [ + "cidrBlock", + ], + }, + Object { + "Label": Object { + "default": "Transit Gateway Configuration", + }, + "Parameters": Array [ + "ExistingTransitGateway", + "TransitGatewayRouteTableIdForAssociation", + "TransitGatewayRTIdForDefaultRoute", + ], + }, + Object { + "Label": Object { + "default": "Firewall Logging Configuration", + }, + "Parameters": Array [ + "logDestinationType", + "logType", + "LogRetentionPeriod", + ], + }, + ], + "ParameterLabels": Object { + "ExistingTransitGateway": Object { + "default": "Provide the existing AWS Transit Gateway ID you wish to attach to the Inspection VPC", + }, + "LogRetentionPeriod": Object { + "default": "Select the log retention period for Network Firewall Logs.", + }, + "TransitGatewayRTIdForDefaultRoute": Object { + "default": "Provide the AWS Transit Gateway Route Table to receive 0.0.0.0/0 route to the Inspection VPC TGW Attachment.", + }, + "TransitGatewayRouteTableIdForAssociation": Object { + "default": "Provide AWS Transit Gateway Route Table to be associated with the Inspection VPC TGW Attachment.", + }, + "cidrBlock": Object { + "default": "Provide the CIDR block for the Inspection VPC", + }, + "logDestinationType": Object { + "default": "Select the type of log destination for the Network Firewall", + }, + "logType": Object { + "default": "Select the type of log to send to the defined log destination.", + }, + }, + }, + }, + "Outputs": Object { + "ArtifactBucketforCodePipeline": Object { + "Description": "Artifact bucket name configured for the CodePipeline.", + "Value": Object { + "Ref": "NetworkFirewallCodePipelineArtifactsBucketF2569455", + }, + }, + "CloudWatchLogGroupforFirewallLogs": Object { + "Description": "CloudWatch Log Group used as the log destination for Firewall Logs.", + "Value": Object { + "Fn::If": Array [ + "LoggingInCloudWatch", + Object { + "Ref": "CloudWatchLogGroup", + }, + "NotConfigured", + ], + }, + }, + "CodeBuildsourcecodebucket": Object { + "Description": "Code Build source code bucket", + "Value": Object { + "Ref": "CodeBuildStagesSourceCodeBucketFA98E7C7", + }, + }, + "FirewallSubnet1ID": Object { + "Description": "Subnet 1 associated with Network Firewall.", + "Value": Object { + "Ref": "NetworkFirewallSubnet1", + }, + }, + "FirewallSubnet2ID": Object { + "Description": "Subnet 2 associated with Network Firewall.", + "Value": Object { + "Ref": "NetworkFirewallSubnet2", + }, + }, + "InspectionVPCID": Object { + "Description": "Inspection VPC ID to create Network Firewall.", + "Value": Object { + "Ref": "VPC", + }, + }, + "NetworkFirewallAvailabilityZone1": Object { + "Description": "Availability Zone configured for Network Firewall subnet 1", + "Value": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallSubnet1", + "AvailabilityZone", + ], + }, + }, + "NetworkFirewallAvailabilityZone2": Object { + "Description": "Availability Zone configured for Network Firewall subnet 2", + "Value": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallSubnet2", + "AvailabilityZone", + ], + }, + }, + "S3BucketforFirewallLogs": Object { + "Description": "S3 Bucket used as the log destination for Firewall Logs.", + "Value": Object { + "Fn::If": Array [ + "LoggingInS3", + Object { + "Ref": "Logs6819BB44", + }, + "NotConfigured", + ], + }, + }, + "TransitGatewaySubnet1ID": Object { + "Description": "Subnet 1 associated with Transit Gateway.", + "Value": Object { + "Ref": "VPCTGWSubnet1", + }, + }, + "TransitGatewaySubnet2ID": Object { + "Description": "Subnet 1 associated with Transit Gateway.", + "Value": Object { + "Ref": "VPCTGWSubnet2", + }, + }, + }, + "Parameters": Object { + "ExistingTransitGateway": Object { + "Default": "", + "Description": "Existing AWS Transit Gateway id.", + "Type": "String", + }, + "LogRetentionPeriod": Object { + "AllowedValues": Array [ + "1", + "3", + "5", + "7", + "14", + "30", + "60", + "90", + "120", + "150", + "180", + "365", + "400", + "545", + "731", + "1827", + "3653", + ], + "Default": 90, + "Description": "Log retention period in days.", + "Type": "Number", + }, + "TransitGatewayRTIdForDefaultRoute": Object { + "Default": "", + "Description": "Existing AWS Transit Gateway route table id. Example: Spoke VPC Route Table. Format: tgw-rtb-4e5f6g7h", + "Type": "String", + }, + "TransitGatewayRouteTableIdForAssociation": Object { + "Default": "", + "Description": "Existing AWS Transit Gateway route table id. Example: Firewall Route Table. Format: tgw-rtb-0a1b2c3d", + "Type": "String", + }, + "cidrBlock": Object { + "AllowedPattern": "^(?:[0-9]{1,3}.){3}[0-9]{1,3}[/]([0-9]?[0-6]?|[1][7-9])$", + "Default": "192.168.1.0/26", + "Description": "CIDR Block for VPC. Must be /26 or larger CIDR block.", + "Type": "String", + }, + "logDestinationType": Object { + "AllowedValues": Array [ + "S3", + "CloudWatchLogs", + "ConfigureManually", + ], + "Default": "CloudWatchLogs", + "Description": "The type of storage destination to send these logs to. You can send logs to an Amazon S3 bucket or a CloudWatch log group.", + "Type": "String", + }, + "logType": Object { + "AllowedValues": Array [ + "ALERT", + "FLOW", + "EnableBoth", + ], + "Default": "FLOW", + "Description": "The type of log to send. Alert logs report traffic that matches a StatefulRule with an action setting that sends an alert log message. Flow logs are standard network traffic flow logs.", + "Type": "String", + }, + }, + "Resources": Object { + "BuildProject097C5DB7": Object { + "Properties": Object { + "Artifacts": Object { + "Type": "CODEPIPELINE", + }, + "EncryptionKey": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + "Environment": Object { + "ComputeType": "BUILD_GENERAL1_SMALL", + "EnvironmentVariables": Array [ + Object { + "Name": "LOG_LEVEL", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "Log", + "Level", + ], + }, + }, + Object { + "Name": "VPC_ID", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "VPC", + }, + }, + Object { + "Name": "SUBNET_IDS", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "NetworkFirewallSubnet1", + }, + ",", + Object { + "Ref": "NetworkFirewallSubnet2", + }, + ], + ], + }, + }, + Object { + "Name": "LOG_TYPE", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "logType", + }, + }, + Object { + "Name": "LOG_DESTINATION_TYPE", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "logDestinationType", + }, + }, + Object { + "Name": "S3_LOG_BUCKET_NAME", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::If": Array [ + "LoggingInS3", + Object { + "Ref": "Logs6819BB44", + }, + "NotConfigured", + ], + }, + }, + Object { + "Name": "CLOUDWATCH_LOG_GROUP_NAME", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::If": Array [ + "LoggingInCloudWatch", + Object { + "Ref": "CloudWatchLogGroup", + }, + "NotConfigured", + ], + }, + }, + Object { + "Name": "VPC_TGW_ATTACHMENT_AZ_1", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallSubnet1", + "AvailabilityZone", + ], + }, + }, + Object { + "Name": "VPC_TGW_ATTACHMENT_AZ_2", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallSubnet2", + "AvailabilityZone", + ], + }, + }, + Object { + "Name": "VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_1", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "VPCTGWRouteTable1", + }, + }, + Object { + "Name": "VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_2", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "VPCTGWRouteTable2", + }, + }, + Object { + "Name": "CODE_BUILD_SOURCE_CODE_S3_KEY", + "Type": "PLAINTEXT", + "Value": "network-firewall-automation/v1.0.0", + }, + Object { + "Name": "STACK_ID", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "AWS::StackId", + }, + }, + Object { + "Name": "SSM_PARAM_FOR_UUID", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "Send", + "ParameterKey", + "UniqueId", + ], + }, + }, + Object { + "Name": "SEND_ANONYMOUS_METRICS", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "Send", + "AnonymousUsage", + "Data", + ], + }, + }, + Object { + "Name": "SOLUTION_ID", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "Solution", + "Identifier", + ], + }, + }, + Object { + "Name": "METRICS_URL", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "Metrics", + "URL", + ], + }, + }, + Object { + "Name": "TRANSIT_GATEWAY_ATTACHMENT_ID", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::If": Array [ + "CreateTransitGatewayAttachment", + Object { + "Ref": "VPCTGWATTACHMENT", + }, + "", + ], + }, + }, + Object { + "Name": "TRANSIT_GATEWAY_ATTACHMENT_APPLIANCE_MODE", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "TransitGatewayAttachment", + "ApplianceMode", + ], + }, + }, + ], + "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER", + }, + "ServiceRole": Object { + "Fn::GetAtt": Array [ + "BuildProjectRoleAA92C755", + "Arn", + ], + }, + "Source": Object { + "BuildSpec": Object { + "Fn::Join": Array [ + "", + Array [ + "{ + \\"version\\": \\"0.2\\", + \\"phases\\": { + \\"install\\": { + \\"runtime-versions\\": { + \\"nodejs\\": \\"12\\" + }, + \\"commands\\": [ + \\"export current=$(pwd)\\", + \\"export sourceCodeKey=$CODE_BUILD_SOURCE_CODE_S3_KEY\\" + ] + }, + \\"pre_build\\": { + \\"commands\\": [ + \\"cd $current\\", + \\"pwd; ls -ltr\\", + \\"echo 'Download Network Firewall Solution Package'\\", + \\"aws s3 cp s3://", + Object { + "Ref": "CodeBuildStagesSourceCodeBucketFA98E7C7", + }, + "/$sourceCodeKey/network-firewall-automation.zip $current || true\\", + \\"if [ -f $current/network-firewall-automation.zip ];then exit 0;else echo \\\\\\"Copy file to s3 bucket\\\\\\"; aws s3 cp s3://solutions-", + Object { + "Ref": "AWS::Region", + }, + "/$sourceCodeKey/network-firewall-automation.zip s3://", + Object { + "Ref": "CodeBuildStagesSourceCodeBucketFA98E7C7", + }, + "/$sourceCodeKey/network-firewall-automation.zip; aws s3 cp s3://", + Object { + "Ref": "CodeBuildStagesSourceCodeBucketFA98E7C7", + }, + "/$sourceCodeKey/network-firewall-automation.zip $current; fi;\\", + \\"unzip -o $current/network-firewall-automation.zip -d $current\\", + \\"pwd; ls -ltr\\" + ] + }, + \\"build\\": { + \\"commands\\": [ + \\"echo \\\\\\"Validating the firewall config\\\\\\"\\", + \\"node build.js\\" + ] + } + }, + \\"artifacts\\": { + \\"files\\": \\"**/*\\" + } +}", + ], + ], + }, + "Type": "CODEPIPELINE", + }, + }, + "Type": "AWS::CodeBuild::Project", + }, + "BuildProjectRoleAA92C755": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "codebuild.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "BuildProjectRoleDefaultPolicy3E9F248C": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":logs:eu-west-1:1234:log-group:/aws/codebuild/", + Object { + "Ref": "BuildProject097C5DB7", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":logs:eu-west-1:1234:log-group:/aws/codebuild/", + Object { + "Ref": "BuildProject097C5DB7", + }, + ":*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "codebuild:CreateReportGroup", + "codebuild:CreateReport", + "codebuild:UpdateReport", + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codebuild:eu-west-1:1234:report-group/", + Object { + "Ref": "BuildProject097C5DB7", + }, + "-*", + ], + ], + }, + }, + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "BuildProjectRoleDefaultPolicy3E9F248C", + "Roles": Array [ + Object { + "Ref": "BuildProjectRoleAA92C755", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "CloudWatchLogGroup": Object { + "Condition": "LoggingInCloudWatch", + "Properties": Object { + "KmsKeyId": Object { + "Fn::GetAtt": Array [ + "KMSKeyForNetworkFirewallLogDestinations70A79322", + "Arn", + ], + }, + "RetentionInDays": Object { + "Ref": "LogRetentionPeriod", + }, + }, + "Type": "AWS::Logs::LogGroup", + }, + "CloudWatchLogsForNetworkFirewallBucketPolicy611AC31C": Object { + "Condition": "LoggingInS3", + "DeletionPolicy": "Retain", + "Properties": Object { + "Bucket": Object { + "Ref": "Logs6819BB44", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:GetObject", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": false, + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "Logs6819BB44", + "Arn", + ], + }, + "/*", + ], + ], + }, + Object { + "Fn::GetAtt": Array [ + "Logs6819BB44", + "Arn", + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + "UpdateReplacePolicy": "Retain", + }, + "CodeBuildStageSourceCodeBucketPolicyF19BA2A0": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Bucket": Object { + "Ref": "CodeBuildStagesSourceCodeBucketFA98E7C7", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:GetObject", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": false, + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "CodeBuildStagesSourceCodeBucketFA98E7C7", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "CodeBuildStagesSourceCodeBucketFA98E7C7", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + "UpdateReplacePolicy": "Retain", + }, + "CodeBuildStagesSourceCodeBucketFA98E7C7": Object { + "DeletionPolicy": "Retain", + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W35", + "reason": "Source Code bucket bucket does not require logging configuration", + }, + Object { + "id": "W51", + "reason": "Source Code bucket is private and does not require a bucket policy", + }, + ], + }, + }, + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "KMSMasterKeyID": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + "SSEAlgorithm": "aws:kms", + }, + }, + ], + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "CodePipelineArtifactS3BucketPolicy6FFF9EE9": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Bucket": Object { + "Ref": "NetworkFirewallCodePipelineArtifactsBucketF2569455", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:DeleteBucket", + "Effect": "Allow", + "Principal": Object { + "Service": "cloudformation.amazonaws.com", + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + }, + Object { + "Action": "s3:GetObject", + "Condition": Object { + "Bool": Object { + "aws:SecureTransport": false, + }, + }, + "Effect": "Deny", + "Principal": "*", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + "/*", + ], + ], + }, + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + "UpdateReplacePolicy": "Retain", + }, + "DefaultRouteSpokeVPCTGWRouteTable": Object { + "Condition": "CreateDefaultRouteFirewallRT", + "DeletionPolicy": "Retain", + "Properties": Object { + "DestinationCidrBlock": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "Route", + "QuadZero", + ], + }, + "TransitGatewayAttachmentId": Object { + "Ref": "VPCTGWATTACHMENT", + }, + "TransitGatewayRouteTableId": Object { + "Ref": "TransitGatewayRTIdForDefaultRoute", + }, + }, + "Type": "AWS::EC2::TransitGatewayRoute", + }, + "DeployProject1CF7CB79": Object { + "Properties": Object { + "Artifacts": Object { + "Type": "CODEPIPELINE", + }, + "EncryptionKey": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + "Environment": Object { + "ComputeType": "BUILD_GENERAL1_SMALL", + "EnvironmentVariables": Array [ + Object { + "Name": "LOG_LEVEL", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "Log", + "Level", + ], + }, + }, + Object { + "Name": "VPC_ID", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "VPC", + }, + }, + Object { + "Name": "SUBNET_IDS", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "NetworkFirewallSubnet1", + }, + ",", + Object { + "Ref": "NetworkFirewallSubnet2", + }, + ], + ], + }, + }, + Object { + "Name": "LOG_TYPE", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "logType", + }, + }, + Object { + "Name": "LOG_DESTINATION_TYPE", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "logDestinationType", + }, + }, + Object { + "Name": "S3_LOG_BUCKET_NAME", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::If": Array [ + "LoggingInS3", + Object { + "Ref": "Logs6819BB44", + }, + "NotConfigured", + ], + }, + }, + Object { + "Name": "CLOUDWATCH_LOG_GROUP_NAME", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::If": Array [ + "LoggingInCloudWatch", + Object { + "Ref": "CloudWatchLogGroup", + }, + "NotConfigured", + ], + }, + }, + Object { + "Name": "VPC_TGW_ATTACHMENT_AZ_1", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallSubnet1", + "AvailabilityZone", + ], + }, + }, + Object { + "Name": "VPC_TGW_ATTACHMENT_AZ_2", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallSubnet2", + "AvailabilityZone", + ], + }, + }, + Object { + "Name": "VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_1", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "VPCTGWRouteTable1", + }, + }, + Object { + "Name": "VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_2", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "VPCTGWRouteTable2", + }, + }, + Object { + "Name": "CODE_BUILD_SOURCE_CODE_S3_KEY", + "Type": "PLAINTEXT", + "Value": "network-firewall-automation/v1.0.0", + }, + Object { + "Name": "STACK_ID", + "Type": "PLAINTEXT", + "Value": Object { + "Ref": "AWS::StackId", + }, + }, + Object { + "Name": "SSM_PARAM_FOR_UUID", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "Send", + "ParameterKey", + "UniqueId", + ], + }, + }, + Object { + "Name": "SEND_ANONYMOUS_METRICS", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "Send", + "AnonymousUsage", + "Data", + ], + }, + }, + Object { + "Name": "SOLUTION_ID", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "Solution", + "Identifier", + ], + }, + }, + Object { + "Name": "METRICS_URL", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "Metrics", + "URL", + ], + }, + }, + Object { + "Name": "TRANSIT_GATEWAY_ATTACHMENT_ID", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::If": Array [ + "CreateTransitGatewayAttachment", + Object { + "Ref": "VPCTGWATTACHMENT", + }, + "", + ], + }, + }, + Object { + "Name": "TRANSIT_GATEWAY_ATTACHMENT_APPLIANCE_MODE", + "Type": "PLAINTEXT", + "Value": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "TransitGatewayAttachment", + "ApplianceMode", + ], + }, + }, + ], + "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER", + }, + "ServiceRole": Object { + "Fn::GetAtt": Array [ + "DeployProjectRole588C8C1D", + "Arn", + ], + }, + "Source": Object { + "BuildSpec": Object { + "Fn::Join": Array [ + "", + Array [ + "{ + \\"version\\": \\"0.2\\", + \\"phases\\": { + \\"install\\": { + \\"runtime-versions\\": { + \\"nodejs\\": \\"12\\" + }, + \\"commands\\": [ + \\"export current=$(pwd)\\", + \\"export sourceCodeKey=$CODE_BUILD_SOURCE_CODE_S3_KEY\\" + ] + }, + \\"pre_build\\": { + \\"commands\\": [ + \\"cd $current\\", + \\"pwd; ls -ltr\\", + \\"echo 'Download Network Firewall Solution Package'\\", + \\"aws s3 cp s3://", + Object { + "Ref": "CodeBuildStagesSourceCodeBucketFA98E7C7", + }, + "/$sourceCodeKey/network-firewall-automation.zip $current\\", + \\"unzip -o $current/network-firewall-automation.zip -d $current\\", + \\"pwd; ls -ltr\\" + ] + }, + \\"build\\": { + \\"commands\\": [ + \\"echo \\\\\\"Initiating Network Firewall Automation\\\\\\"\\", + \\"node index.js\\" + ] + }, + \\"post_build\\": { + \\"commands\\": [] + } + }, + \\"artifacts\\": { + \\"files\\": \\"**/*\\" + } +}", + ], + ], + }, + "Type": "CODEPIPELINE", + }, + }, + "Type": "AWS::CodeBuild::Project", + }, + "DeployProjectRole588C8C1D": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "codebuild.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "DeployProjectRoleDefaultPolicy52AEA98B": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":logs:eu-west-1:1234:log-group:/aws/codebuild/", + Object { + "Ref": "DeployProject1CF7CB79", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":logs:eu-west-1:1234:log-group:/aws/codebuild/", + Object { + "Ref": "DeployProject1CF7CB79", + }, + ":*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "codebuild:CreateReportGroup", + "codebuild:CreateReport", + "codebuild:UpdateReport", + "codebuild:BatchPutTestCases", + "codebuild:BatchPutCodeCoverages", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codebuild:eu-west-1:1234:report-group/", + Object { + "Ref": "DeployProject1CF7CB79", + }, + "-*", + ], + ], + }, + }, + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "DeployProjectRoleDefaultPolicy52AEA98B", + "Roles": Array [ + Object { + "Ref": "DeployProjectRole588C8C1D", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "FirewallSubnetRouteTable": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Tags": Array [ + Object { + "Key": "Name", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + "-FirewallSubnetRouteTable", + ], + ], + }, + }, + ], + "VpcId": Object { + "Ref": "VPC", + }, + }, + "Type": "AWS::EC2::RouteTable", + "UpdateReplacePolicy": "Retain", + }, + "FlowLog": Object { + "Properties": Object { + "DeliverLogsPermissionArn": Object { + "Fn::GetAtt": Array [ + "RoleFlowLogsCA794118", + "Arn", + ], + }, + "LogGroupName": Object { + "Ref": "AWS::StackName", + }, + "ResourceId": Object { + "Ref": "VPC", + }, + "ResourceType": "VPC", + "TrafficType": "ALL", + }, + "Type": "AWS::EC2::FlowLog", + }, + "KMSKeyForNetworkFirewallLogDestinations70A79322": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Description": "This key will be used for encrypting the vpc flow logs and firewall logs.", + "EnableKeyRotation": true, + "KeyPolicy": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::1234:root", + ], + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": "kms:GenerateDataKey*", + "Effect": "Allow", + "Principal": Object { + "Service": "delivery.logs.amazonaws.com", + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*", + ], + "Effect": "Allow", + "Principal": Object { + "Service": Object { + "Fn::Join": Array [ + "", + Array [ + "logs.", + Object { + "Ref": "AWS::Region", + }, + ".amazonaws.com", + ], + ], + }, + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "LogGroupFlowLogs": Object { + "Properties": Object { + "KmsKeyId": Object { + "Fn::GetAtt": Array [ + "KMSKeyForNetworkFirewallLogDestinations70A79322", + "Arn", + ], + }, + "LogGroupName": Object { + "Ref": "AWS::StackName", + }, + "RetentionInDays": Object { + "Ref": "LogRetentionPeriod", + }, + }, + "Type": "AWS::Logs::LogGroup", + }, + "Logs6819BB44": Object { + "Condition": "LoggingInS3", + "DeletionPolicy": "Retain", + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W35", + "reason": "Logs bucket does not require logging configuration", + }, + Object { + "id": "W51", + "reason": "Logs bucket is private and does not require a bucket policy", + }, + ], + }, + }, + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "KMSMasterKeyID": Object { + "Fn::GetAtt": Array [ + "KMSKeyForNetworkFirewallLogDestinations70A79322", + "Arn", + ], + }, + "SSEAlgorithm": "aws:kms", + }, + }, + ], + }, + "LifecycleConfiguration": Object { + "Rules": Array [ + Object { + "ExpirationInDays": Object { + "Ref": "LogRetentionPeriod", + }, + "Status": "Enabled", + }, + ], + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "NetworkFirewallCodePipelineA72E3ADD": Object { + "DependsOn": Array [ + "NetworkFirewallCodePipelineRoleDefaultPolicyF0142ABD", + "NetworkFirewallCodePipelineRoleDDD28B15", + ], + "Properties": Object { + "ArtifactStore": Object { + "EncryptionKey": Object { + "Id": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + "Type": "KMS", + }, + "Location": Object { + "Ref": "NetworkFirewallCodePipelineArtifactsBucketF2569455", + }, + "Type": "S3", + }, + "RoleArn": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineRoleDDD28B15", + "Arn", + ], + }, + "Stages": Array [ + Object { + "Actions": Array [ + Object { + "ActionTypeId": Object { + "Category": "Source", + "Owner": "AWS", + "Provider": "CodeCommit", + "Version": "1", + }, + "Configuration": Object { + "BranchName": "master", + "PollForSourceChanges": false, + "RepositoryName": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodeRepositoryF7BA0495", + "Name", + ], + }, + }, + "Name": "Source", + "OutputArtifacts": Array [ + Object { + "Name": "SourceArtifact", + }, + ], + "RoleArn": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineSourceCodePipelineActionRole67C89750", + "Arn", + ], + }, + "RunOrder": 1, + }, + ], + "Name": "Source", + }, + Object { + "Actions": Array [ + Object { + "ActionTypeId": Object { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1", + }, + "Configuration": Object { + "ProjectName": Object { + "Ref": "BuildProject097C5DB7", + }, + }, + "InputArtifacts": Array [ + Object { + "Name": "SourceArtifact", + }, + ], + "Name": "CodeBuild", + "OutputArtifacts": Array [ + Object { + "Name": "BuildArtifact", + }, + ], + "RoleArn": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineValidationCodeBuildCodePipelineActionRole2A3E8726", + "Arn", + ], + }, + "RunOrder": 1, + }, + ], + "Name": "Validation", + }, + Object { + "Actions": Array [ + Object { + "ActionTypeId": Object { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1", + }, + "Configuration": Object { + "ProjectName": Object { + "Ref": "DeployProject1CF7CB79", + }, + }, + "InputArtifacts": Array [ + Object { + "Name": "BuildArtifact", + }, + ], + "Name": "CodeDeploy", + "RoleArn": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineDeploymentCodeDeployCodePipelineActionRole6EA7639D", + "Arn", + ], + }, + "RunOrder": 1, + }, + ], + "Name": "Deployment", + }, + ], + }, + "Type": "AWS::CodePipeline::Pipeline", + }, + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "EnableKeyRotation": true, + "KeyPolicy": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::1234:root", + ], + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineRoleDDD28B15", + "Arn", + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineSourceCodePipelineActionRole67C89750", + "Arn", + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "BuildProjectRoleAA92C755", + "Arn", + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "BuildProjectRoleAA92C755", + "Arn", + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "DeployProjectRole588C8C1D", + "Arn", + ], + }, + }, + "Resource": "*", + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "DeployProjectRole588C8C1D", + "Arn", + ], + }, + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Delete", + }, + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKeyAlias1704A536": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "AliasName": Object { + "Fn::Join": Array [ + "", + Array [ + "alias/", + Object { + "Ref": "AWS::StackName", + }, + "-artifactBucket-EncryptionKeyAlias", + ], + ], + }, + "TargetKeyId": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + }, + "Type": "AWS::KMS::Alias", + "UpdateReplacePolicy": "Delete", + }, + "NetworkFirewallCodePipelineArtifactsBucketF2569455": Object { + "DeletionPolicy": "Retain", + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W35", + "reason": "This S3 bucket is used as the destination for 'NetworkFirewallCodePipelineArtifactsBucket'", + }, + ], + }, + }, + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "KMSMasterKeyID": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + "SSEAlgorithm": "aws:kms", + }, + }, + ], + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "NetworkFirewallCodePipelineDeploymentCodeDeployCodePipelineActionRole6EA7639D": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::1234:root", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "NetworkFirewallCodePipelineDeploymentCodeDeployCodePipelineActionRoleDefaultPolicyAB6FC4F9": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "DeployProject1CF7CB79", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "NetworkFirewallCodePipelineDeploymentCodeDeployCodePipelineActionRoleDefaultPolicyAB6FC4F9", + "Roles": Array [ + Object { + "Ref": "NetworkFirewallCodePipelineDeploymentCodeDeployCodePipelineActionRole6EA7639D", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "NetworkFirewallCodePipelineEventsRole94323A48": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "events.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "NetworkFirewallCodePipelineEventsRoleDefaultPolicy5835E037": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "codepipeline:StartPipelineExecution", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codepipeline:eu-west-1:1234:", + Object { + "Ref": "NetworkFirewallCodePipelineA72E3ADD", + }, + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "NetworkFirewallCodePipelineEventsRoleDefaultPolicy5835E037", + "Roles": Array [ + Object { + "Ref": "NetworkFirewallCodePipelineEventsRole94323A48", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "NetworkFirewallCodePipelineRoleDDD28B15": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "codepipeline.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "NetworkFirewallCodePipelineRoleDefaultPolicyF0142ABD": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + }, + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineSourceCodePipelineActionRole67C89750", + "Arn", + ], + }, + }, + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineValidationCodeBuildCodePipelineActionRole2A3E8726", + "Arn", + ], + }, + }, + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineDeploymentCodeDeployCodePipelineActionRole6EA7639D", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "NetworkFirewallCodePipelineRoleDefaultPolicyF0142ABD", + "Roles": Array [ + Object { + "Ref": "NetworkFirewallCodePipelineRoleDDD28B15", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "NetworkFirewallCodePipelineSourceCodePipelineActionRole67C89750": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::1234:root", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "NetworkFirewallCodePipelineSourceCodePipelineActionRoleDefaultPolicyB01603D9": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketF2569455", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineArtifactsBucketEncryptionKey086ED060", + "Arn", + ], + }, + }, + Object { + "Action": Array [ + "codecommit:GetBranch", + "codecommit:GetCommit", + "codecommit:UploadArchive", + "codecommit:GetUploadArchiveStatus", + "codecommit:CancelUploadArchive", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodeRepositoryF7BA0495", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "NetworkFirewallCodePipelineSourceCodePipelineActionRoleDefaultPolicyB01603D9", + "Roles": Array [ + Object { + "Ref": "NetworkFirewallCodePipelineSourceCodePipelineActionRole67C89750", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "NetworkFirewallCodePipelineValidationCodeBuildCodePipelineActionRole2A3E8726": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::1234:root", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "NetworkFirewallCodePipelineValidationCodeBuildCodePipelineActionRoleDefaultPolicyA4A71A44": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "BuildProject097C5DB7", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "NetworkFirewallCodePipelineValidationCodeBuildCodePipelineActionRoleDefaultPolicyA4A71A44", + "Roles": Array [ + Object { + "Ref": "NetworkFirewallCodePipelineValidationCodeBuildCodePipelineActionRole2A3E8726", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "NetworkFirewallCodeRepositoryF7BA0495": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Code": Object { + "S3": Object { + "Bucket": "solutions-eu-west-1", + "Key": Object { + "Fn::Join": Array [ + "", + Array [ + "network-firewall-automation/", + Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "Version", + "Latest", + ], + }, + "/network-firewall-configuration.zip", + ], + ], + }, + }, + }, + "RepositoryDescription": "This repository is created by the AWS Network Firewall solution for AWS Transit Gateway, to store and trigger changes to the network firewall rules and configurations.", + "RepositoryName": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "CodeCommitRepo", + "Name", + ], + }, + Object { + "Ref": "AWS::StackName", + }, + ], + ], + }, + }, + "Type": "AWS::CodeCommit::Repository", + "UpdateReplacePolicy": "Retain", + }, + "NetworkFirewallCodeRepositoryMyTestStackNetworkFirewallCodePipelineD8BFDC90EventRule5C587E07": Object { + "Properties": Object { + "EventPattern": Object { + "detail": Object { + "event": Array [ + "referenceCreated", + "referenceUpdated", + ], + "referenceName": Array [ + "master", + ], + }, + "detail-type": Array [ + "CodeCommit Repository State Change", + ], + "resources": Array [ + Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodeRepositoryF7BA0495", + "Arn", + ], + }, + ], + "source": Array [ + "aws.codecommit", + ], + }, + "State": "ENABLED", + "Targets": Array [ + Object { + "Arn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":codepipeline:eu-west-1:1234:", + Object { + "Ref": "NetworkFirewallCodePipelineA72E3ADD", + }, + ], + ], + }, + "Id": "Target0", + "RoleArn": Object { + "Fn::GetAtt": Array [ + "NetworkFirewallCodePipelineEventsRole94323A48", + "Arn", + ], + }, + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "NetworkFirewallSubnet1": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AvailabilityZone": Object { + "Fn::Select": Array [ + "0", + Object { + "Fn::GetAZs": "", + }, + ], + }, + "CidrBlock": Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Cidr": Array [ + Object { + "Fn::GetAtt": Array [ + "VPC", + "CidrBlock", + ], + }, + 4, + "4", + ], + }, + ], + }, + "Tags": Array [ + Object { + "Key": "Name", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + "-FirewallSubnet1", + ], + ], + }, + }, + ], + "VpcId": Object { + "Ref": "VPC", + }, + }, + "Type": "AWS::EC2::Subnet", + "UpdateReplacePolicy": "Retain", + }, + "NetworkFirewallSubnet1RouteTableAssociation": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "RouteTableId": Object { + "Ref": "FirewallSubnetRouteTable", + }, + "SubnetId": Object { + "Ref": "NetworkFirewallSubnet1", + }, + }, + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "UpdateReplacePolicy": "Retain", + }, + "NetworkFirewallSubnet2": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AvailabilityZone": Object { + "Fn::Select": Array [ + "1", + Object { + "Fn::GetAZs": "", + }, + ], + }, + "CidrBlock": Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Cidr": Array [ + Object { + "Fn::GetAtt": Array [ + "VPC", + "CidrBlock", + ], + }, + 4, + "4", + ], + }, + ], + }, + "Tags": Array [ + Object { + "Key": "Name", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + "-FirewallSubnet2", + ], + ], + }, + }, + ], + "VpcId": Object { + "Ref": "VPC", + }, + }, + "Type": "AWS::EC2::Subnet", + "UpdateReplacePolicy": "Retain", + }, + "NetworkFirewallSubnet2RouteTableAssociation": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "RouteTableId": Object { + "Ref": "FirewallSubnetRouteTable", + }, + "SubnetId": Object { + "Ref": "NetworkFirewallSubnet2", + }, + }, + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "UpdateReplacePolicy": "Retain", + }, + "RoleFlowLogsCA794118": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "vpc-flow-logs.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "RoleFlowLogsDefaultPolicyD1F03EF4": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:CreateLogGroup", + "logs:DescribeLogGroups", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "LogGroupFlowLogs", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "RoleFlowLogsDefaultPolicyD1F03EF4", + "Roles": Array [ + Object { + "Ref": "RoleFlowLogsCA794118", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "TGWRoute": Object { + "Condition": "CreateTransitGatewayAttachment", + "DependsOn": Array [ + "VPCTGWATTACHMENT", + ], + "Properties": Object { + "DestinationCidrBlock": Object { + "Fn::FindInMap": Array [ + "SolutionMapping", + "Route", + "QuadZero", + ], + }, + "RouteTableId": Object { + "Ref": "FirewallSubnetRouteTable", + }, + "TransitGatewayId": Object { + "Ref": "ExistingTransitGateway", + }, + }, + "Type": "AWS::EC2::Route", + }, + "VPC": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "CidrBlock": Object { + "Ref": "cidrBlock", + }, + "Tags": Array [ + Object { + "Key": "created-by", + "Value": "network-firewall-automation", + }, + Object { + "Key": "Name", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + "-Inspection-VPC", + ], + ], + }, + }, + ], + }, + "Type": "AWS::EC2::VPC", + "UpdateReplacePolicy": "Retain", + }, + "VPCTGWATTACHMENT": Object { + "Condition": "CreateTransitGatewayAttachment", + "DeletionPolicy": "Retain", + "Properties": Object { + "SubnetIds": Array [ + Object { + "Ref": "VPCTGWSubnet1", + }, + Object { + "Ref": "VPCTGWSubnet2", + }, + ], + "Tags": Array [ + Object { + "Key": "Name", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + "-Inspection-VPC-Attachment", + ], + ], + }, + }, + ], + "TransitGatewayId": Object { + "Ref": "ExistingTransitGateway", + }, + "VpcId": Object { + "Ref": "VPC", + }, + }, + "Type": "AWS::EC2::TransitGatewayAttachment", + }, + "VPCTGWRouteTable1": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Tags": Array [ + Object { + "Key": "Name", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + "-TGWSubnetRouteTable1", + ], + ], + }, + }, + ], + "VpcId": Object { + "Ref": "VPC", + }, + }, + "Type": "AWS::EC2::RouteTable", + "UpdateReplacePolicy": "Retain", + }, + "VPCTGWRouteTable2": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Tags": Array [ + Object { + "Key": "Name", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + "-TGWSubnetRouteTable2", + ], + ], + }, + }, + ], + "VpcId": Object { + "Ref": "VPC", + }, + }, + "Type": "AWS::EC2::RouteTable", + "UpdateReplacePolicy": "Retain", + }, + "VPCTGWRouteTableAssociation": Object { + "Condition": "CreateTransitGatewayRTAssociation", + "DeletionPolicy": "Retain", + "Properties": Object { + "TransitGatewayAttachmentId": Object { + "Ref": "VPCTGWATTACHMENT", + }, + "TransitGatewayRouteTableId": Object { + "Ref": "TransitGatewayRouteTableIdForAssociation", + }, + }, + "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", + }, + "VPCTGWSubnet1": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AvailabilityZone": Object { + "Fn::Select": Array [ + "0", + Object { + "Fn::GetAZs": "", + }, + ], + }, + "CidrBlock": Object { + "Fn::Select": Array [ + 2, + Object { + "Fn::Cidr": Array [ + Object { + "Fn::GetAtt": Array [ + "VPC", + "CidrBlock", + ], + }, + 4, + "4", + ], + }, + ], + }, + "Tags": Array [ + Object { + "Key": "Name", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + "-VPCTGWSubnet1", + ], + ], + }, + }, + ], + "VpcId": Object { + "Ref": "VPC", + }, + }, + "Type": "AWS::EC2::Subnet", + "UpdateReplacePolicy": "Retain", + }, + "VPCTGWSubnet1RouteTableAssociation": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "RouteTableId": Object { + "Ref": "VPCTGWRouteTable1", + }, + "SubnetId": Object { + "Ref": "VPCTGWSubnet1", + }, + }, + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "UpdateReplacePolicy": "Retain", + }, + "VPCTGWSubnet2": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AvailabilityZone": Object { + "Fn::Select": Array [ + "1", + Object { + "Fn::GetAZs": "", + }, + ], + }, + "CidrBlock": Object { + "Fn::Select": Array [ + 3, + Object { + "Fn::Cidr": Array [ + Object { + "Fn::GetAtt": Array [ + "VPC", + "CidrBlock", + ], + }, + 4, + "4", + ], + }, + ], + }, + "Tags": Array [ + Object { + "Key": "Name", + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + "-VPCTGWSubnet2", + ], + ], + }, + }, + ], + "VpcId": Object { + "Ref": "VPC", + }, + }, + "Type": "AWS::EC2::Subnet", + "UpdateReplacePolicy": "Retain", + }, + "VPCTGWSubnet2RouteTableAssociation": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "RouteTableId": Object { + "Ref": "VPCTGWRouteTable2", + }, + "SubnetId": Object { + "Ref": "VPCTGWSubnet2", + }, + }, + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "UpdateReplacePolicy": "Retain", + }, + "buildStageIAMPolicyB31D4B98": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "network-firewall:CreateFirewallPolicy", + "network-firewall:CreateRuleGroup", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Sub": "arn:\${AWS::Partition}:network-firewall:\${AWS::Region}:\${AWS::AccountId}:stateful-rulegroup/*", + }, + Object { + "Fn::Sub": "arn:\${AWS::Partition}:network-firewall:\${AWS::Region}:\${AWS::AccountId}:firewall-policy/*", + }, + Object { + "Fn::Sub": "arn:\${AWS::Partition}:network-firewall:\${AWS::Region}:\${AWS::AccountId}:stateless-rulegroup/*", + }, + ], + }, + Object { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Sub": Array [ + "arn:\${AWS::Partition}:s3:::\${CodeBucketName}/\${KeyName}/*", + Object { + "CodeBucketName": "solutions-eu-west-1", + "KeyName": "network-firewall-automation", + }, + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":s3:::", + Object { + "Ref": "CodeBuildStagesSourceCodeBucketFA98E7C7", + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":s3:::", + Object { + "Ref": "CodeBuildStagesSourceCodeBucketFA98E7C7", + }, + "/*", + ], + ], + }, + }, + Object { + "Action": Array [ + "ssm:PutParameter", + "ssm:GetParameter", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Sub": Array [ + "arn:\${AWS::Partition}:ssm:\${AWS::Region}:\${AWS::AccountId}:parameter/\${ParameterKey}", + Object { + "ParameterKey": Object { + "Fn::FindInMap": Array [ + "Send", + "ParameterKey", + "UniqueId", + ], + }, + }, + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "buildStageIAMPolicyB31D4B98", + "Roles": Array [ + Object { + "Ref": "BuildProjectRoleAA92C755", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "deployStageFirewallLoggingCWPolicyD4098456": Object { + "Condition": "LoggingInCloudWatch", + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W12", + "reason": "Resource * is required for describe APIs", + }, + ], + }, + }, + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies", + ], + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": "logs:DescribeLogGroups", + "Effect": "Allow", + "Resource": Object { + "Fn::Sub": "arn:\${AWS::Partition}:logs:*:\${AWS::AccountId}:log-group:*", + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "deployStageFirewallLoggingCWPolicyD4098456", + "Roles": Array [ + Object { + "Ref": "DeployProjectRole588C8C1D", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "deployStageFirewallLoggingPolicy15AD5CD5": Object { + "Condition": "NotLoggingConfigureManually", + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W12", + "reason": "Resource * is required for these actions.", + }, + ], + }, + }, + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "deployStageFirewallLoggingPolicy15AD5CD5", + "Roles": Array [ + Object { + "Ref": "DeployProjectRole588C8C1D", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "deployStageFirewallLoggingS3Policy8F79BDD2": Object { + "Condition": "LoggingInS3", + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:PutBucketPolicy", + "s3:GetBucketPolicy", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "Logs6819BB44", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "deployStageFirewallLoggingS3Policy8F79BDD2", + "Roles": Array [ + Object { + "Ref": "DeployProjectRole588C8C1D", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "deployStageFirewallPolicy72BE60BE": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W12", + "reason": "Resource * is required for describe APIs", + }, + ], + }, + }, + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "network-firewall:CreateFirewall", + "network-firewall:UpdateFirewallDeleteProtection", + "network-firewall:DeleteRuleGroup", + "network-firewall:DescribeLoggingConfiguration", + "network-firewall:UpdateFirewallDescription", + "network-firewall:CreateRuleGroup", + "network-firewall:DescribeFirewall", + "network-firewall:DeleteFirewallPolicy", + "network-firewall:UpdateRuleGroup", + "network-firewall:DescribeRuleGroup", + "network-firewall:ListRuleGroups", + "network-firewall:UpdateSubnetChangeProtection", + "network-firewall:UpdateFirewallPolicyChangeProtection", + "network-firewall:AssociateFirewallPolicy", + "network-firewall:DescribeFirewallPolicy", + "network-firewall:UpdateFirewallPolicy", + "network-firewall:DescribeResourcePolicy", + "network-firewall:CreateFirewallPolicy", + "network-firewall:UpdateLoggingConfiguration", + "network-firewall:TagResource", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Sub": "arn:\${AWS::Partition}:network-firewall:\${AWS::Region}:\${AWS::AccountId}:stateful-rulegroup/*", + }, + Object { + "Fn::Sub": "arn:\${AWS::Partition}:network-firewall:\${AWS::Region}:\${AWS::AccountId}:firewall-policy/*", + }, + Object { + "Fn::Sub": "arn:\${AWS::Partition}:network-firewall:\${AWS::Region}:\${AWS::AccountId}:firewall/*", + }, + Object { + "Fn::Sub": "arn:\${AWS::Partition}:network-firewall:\${AWS::Region}:\${AWS::AccountId}:stateless-rulegroup/*", + }, + ], + }, + Object { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Sub": Array [ + "arn:\${AWS::Partition}:s3:::\${CodeBucketName}/\${KeyName}/*", + Object { + "CodeBucketName": "solutions-eu-west-1", + "KeyName": "network-firewall-automation", + }, + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":s3:::", + Object { + "Ref": "CodeBuildStagesSourceCodeBucketFA98E7C7", + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": Array [ + "ec2:DescribeVpcs", + "ec2:DescribeSubnets", + "ec2:DescribeRouteTables", + ], + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": Array [ + "ec2:CreateRoute", + "ec2:DeleteRoute", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":ec2:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":route-table/", + Object { + "Ref": "VPCTGWRouteTable1", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":ec2:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":route-table/", + Object { + "Ref": "VPCTGWRouteTable2", + }, + ], + ], + }, + ], + }, + Object { + "Action": "iam:CreateServiceLinkedRole", + "Effect": "Allow", + "Resource": Object { + "Fn::Sub": "arn:aws:iam::\${AWS::AccountId}:role/aws-service-role/network-firewall.amazonaws.com/AWSServiceRoleForNetworkFirewall", + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "deployStageFirewallPolicy72BE60BE", + "Roles": Array [ + Object { + "Ref": "DeployProjectRole588C8C1D", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "deployStageModifyTransitGatewayAttachmentPolicy993566C2": Object { + "Condition": "CreateTransitGatewayAttachment", + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "ec2:ModifyTransitGatewayVpcAttachment", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":ec2:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":transit-gateway-attachment/", + Object { + "Ref": "VPCTGWATTACHMENT", + }, + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "deployStageModifyTransitGatewayAttachmentPolicy993566C2", + "Roles": Array [ + Object { + "Ref": "DeployProjectRole588C8C1D", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, +} +`; diff --git a/source/test/network-firewall-automation-solution.test.ts b/source/test/network-firewall-automation-solution.test.ts new file mode 100644 index 0000000..38c6580 --- /dev/null +++ b/source/test/network-firewall-automation-solution.test.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import * as cdk from '@aws-cdk/core'; +import { SynthUtils } from '@aws-cdk/assert'; +import * as NetworkFirewallAutomationStack from "../lib/network-firewall-automation-solution-stack" +import '@aws-cdk/assert/jest'; + + +function getTestStack(): cdk.Stack { + const app = new cdk.App(); + const props: NetworkFirewallAutomationStack.NetworkFirewallAutomationStackProps = { + env: { account: '1234', region: 'eu-west-1' }, + solutionBucket: 'solutions', + solutionId: 'SO0108', + solutionName: 'network-firewall-automation', + solutionProvider: 'AWS Solutions Builders', + solutionTradeMarkName: 'network-firewall-automation', + solutionVersion: 'v1.0.0' + }; + return new NetworkFirewallAutomationStack.NetworkFirewallAutomationStack(app, 'MyTestStack', props) +} +/* + * Snapshot test + */ +test('NetworkFirewallAutomationStack Snapshot test', () => { + expect(SynthUtils.toCloudFormation(getTestStack())).toMatchSnapshot(); +}); \ No newline at end of file diff --git a/source/tsconfig.json b/source/tsconfig.json new file mode 100644 index 0000000..f4da57a --- /dev/null +++ b/source/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + "alwaysStrict": true, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ + "es2018" + ], + "module": "CommonJS", + "moduleResolution": "node", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "stripInternal": true, + "target": "ES2018" + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules", + "networkFirewallAutomation" + ], + "_generated_by_jsii_": "Generated by jsii - safe to delete, and ideally should be in .gitignore" +}