Skip to content

Commit

Permalink
Merge pull request #30 from globaldatanet/feature/resources-in-scope
Browse files Browse the repository at this point in the history
2.5.0
  • Loading branch information
daknhh authored Jun 7, 2022
2 parents 46931b5 + 7adcca2 commit b4a2c28
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 174 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

## Released

## 2.5.0

### Added

- Added:
- RemediationEnabled?: Indicates if the policy should be automatically applied to new resources.
- IncludeMap: Specifies the AWS account IDs and AWS Organizations organizational units (OUs) to include in the policy.
- ExcludeMap?: Specifies the AWS account IDs and AWS Organizations organizational units (OUs) to exclude from the policy.
- ResourceTags?: An array of ResourceTag objects, used to explicitly include resources in the policy scope or explicitly exclude them.
- ResourcesCleanUp?: Indicates whether AWS Firewall Manager should automatically remove protections from resources that leave the policy scope and clean up resources that Firewall Manager is managing for accounts when those accounts leave policy scope.
- TaskFile:
validateconfig: Validates the current config
generateconfig: Generate skeleton for a waf configuration file
### Removed

- DeployTo will now be managed trough the includeMap
- Example JSON WAF
### Changed:
- A Firewall can now deployed using: task deploy config=NAMEOFYOURCONFIGFILE without JSON

## 2.1.3

### Fixed
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[![Mentioned in Awesome CDK](https://awesome.re/mentioned-badge.svg)](https://github.com/kolomied/awesome-cdk)
[![License: Apache2](https://img.shields.io/badge/license-Apache%202-lightgrey.svg)](http://www.apache.org/licenses/) [![cdk](https://img.shields.io/badge/aws_cdk-v2-orange.svg)](https://docs.aws.amazon.com/cdk/v2/guide/home.html)
[![latest](https://img.shields.io/badge/latest-release-yellow.svg)](https://github.com/globaldatanet/aws-firewall-factory/releases)
[![gdn](https://img.shields.io/badge/opensource-@globaldatanet-%2300ecbd)](https://globaldatanet.com/opensource) [![dakn](https://img.shields.io/badge/by-dakn-%23ae0009.svg)](https://github.com/daknhh)
[![language](https://img.shields.io/badge/typescript-3.9.7-purple.svg)](https://docs.aws.amazon.com/cdk/v2/guide/home.html)

# AWS FIREWALL FACTORY v2 [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=AWS%20FIREWALL%20FACTORY%20-%20Deploy%2C%20update%2C%20and%20stage%20your%20WAFs%20while%20managing%20them%20centrally%20via%20FMS&url=https://github.com/globaldatanet/aws-firewall-factory&hashtags=aws,security,waf)

Expand Down Expand Up @@ -40,7 +42,9 @@ AWS Web Application Firewalls (WAFs) protect web applications and APIs from typi

If you want to learn more about the AWS Firewall Factory feel free to look at the following media resources.

- [📺 Webinar: Web Application Firewalls at Scale](https://globaldatanet.com/webinars/aws-security-with-security-in-the-cloud)
- [📺 Webinar: Web Application Firewalls at Scale - Language: 🇩🇪](https://globaldatanet.com/webinars/aws-security-with-security-in-the-cloud)
- [📺 Webinar: Managing AWS Web Application Firewalls at Scale - Language: 🇺🇸](https://globaldatanet.com/webinars/managing-aws-web-application-firewalls-at-scale)

- [🎙 Podcast coming soon](https://github.com/richarvey/aws-community-radio/issues/3)

## Architecture
Expand Down Expand Up @@ -81,7 +85,6 @@ If you want to learn more about the AWS Firewall Factory feel free to look at th

| Parameter | Value |
|--------------------|----------------------------------------------------------------------------------------------|
| PROCESS_PARAMETERS | path to values file eg. values/example-waf.json |
| SKIP_QUOTA_CHECK | true (Stop deployment if calculated WCU is above the quota) </br> false (Skipping WCU Check) |
| WAF_TEST | true (testing your waf with GoTestWAF) </br> false (Skipping WAF testing) |
| CREATE_DIAGRAM | true (generating a diagram using draw.io) </br> false (Skipping diagram generation) |
Expand Down Expand Up @@ -117,9 +120,9 @@ If you want to learn more about the AWS Firewall Factory feel free to look at th
### Deployment via Taskfile

0. Create new json file for you WAF and configure Rules in the JSON (see [example.json](values/example-waf.json) to see structure)
1. Set `PROCESS_PARAMETERS` in `Taskfile.yml` for new json file
2. Assume AWS Profile `awsume PROFILENAME`
3. Enter `task deploy`
1. Assume AWS Profile `awsume PROFILENAME`
2. (Optional) Enter `task generateconfig`
3. Enter `task deploy config=NAMEOFYOURCONFIGFILE`

## Contributors

Expand Down
38 changes: 33 additions & 5 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
version: '3'
#output: prefixed
env:
PROCESS_PARAMETERS: values/example-waf.json
SKIP_QUOTA_CHECK: true
WAF_TEST: false
CREATE_DIAGRAM: false
CDK_DIFF: false

vars:
config: # without .json
REGION:
sh: aws configure get profile.$AWSUME_PROFILE.region
sh: echo $AWS_REGION
CDK_DEFAULT_ACCOUNT:
sh: aws sts get-caller-identity |jq -r .Account

Expand Down Expand Up @@ -43,6 +44,13 @@ tasks:
- if [[ {{.CDK_DIFF}} = true ]];then cdk diff; echo -n "Continue (y/n)?"; read CONT; if [ "$CONT" = "n" ]; then echo "NO" | exit 1; else echo "YES"; fi; else exit 0;fi;
silent: true
interactive: true
env:
PROCESS_PARAMETERS: values/{{.config}}.json
preconditions:
- sh: "test {{.config}}"
msg: "config Variable was not set"
- sh: "test -f values/{{.config}}.json"
msg: "Values file not found: values/{{.config}}.json"
cdkdeploy:
desc: CDK Deploy
cmds:
Expand All @@ -53,23 +61,43 @@ tasks:
sh: aws sts get-caller-identity |jq -r .Account
TAGS:
sh: cat tags/tags.json | jq -j '.[]|"--tags " + (.Key)+"="+(.Value)+" "'
preconditions:
- sh: "test {{.config}}"
msg: "config Variable was not set"
- sh: "test -f values/{{.config}}.json"
msg: "Values file not found: values/{{.config}}.json"
env:
PROCESS_PARAMETERS: values/{{.config}}.json
silent: true
interactive: true
creatediagram:
desc: Create Diagram
cmds:
- if [[ {{.CREATE_DIAGRAM}} = true ]] ; then echo 🤳🏻 $(cfn-dia draw.io --cdk-output cdk.out --output-file $(sed "s/values/diagrams/g;s/.json/.drawio/g" <<< {{.PROCESS_PARAMETERS}}) --ci-mode --skip-synth); else echo ⏭ Skipping Diagram generation 🤳🏻; fi
- if [[ {{.CREATE_DIAGRAM}} = true ]] ; then echo 🤳🏻 $(cfn-dia draw.io --cdk-output cdk.out --output-file $(sed "s/values/diagrams/g;s/.json/.drawio/g" <<< values/{{.config}}.json) --ci-mode --skip-synth); else echo ⏭ Skipping Diagram generation 🤳🏻 ; fi
silent: true
testwaf:
desc: Test of your waf using GoTestWAF
cmds:
- echo 🧪 Testing of your new 🔥 WAF using GoTestWAF
- ./gotestwaf/gotestwaf --url=$(cat {{.PROCESS_PARAMETERS}} | jq -r '.[].SecuredDomain') --wafName="$(cat {{.PROCESS_PARAMETERS}} | jq -r '.General.Prefix')-$(cat {{.PROCESS_PARAMETERS}} | jq -r '.WebAcl.Name')-$(cat {{.PROCESS_PARAMETERS}} | jq -r '.General.Stage')-$(cat {{.PROCESS_PARAMETERS}} | jq -r '.General.DeployHash')" --configPath=./gotestwaf/config.yaml --testCasesPath=./gotestwaf/testcases --skipWAFBlockCheck
- ./gotestwaf/gotestwaf --url=$(cat values/{{.config}}.json | jq -r '.[].SecuredDomain') --wafName="$(cat values/{{.config}}.json | jq -r '.General.Prefix')-$(cat values/{{.config}}.json | jq -r '.WebAcl.Name')-$(cat values/{{.config}}.json | jq -r '.General.Stage')-$(cat values/{{.config}}.json | jq -r '.General.DeployHash')" --configPath=./gotestwaf/config.yaml --testCasesPath=./gotestwaf/testcases --skipWAFBlockCheck
silent: true
preconditions:
- sh: if [[ {{.WAF_TEST}} = true ]] ; then exit 0; else exit 1; fi
msg: ⏭ Skipping WAF Testing 🧪
validateconfig:
desc: Validation of the current config
desc: Validates the current config
cmds:
- ts-node test/config-loader.ts
silent: true
env:
PROCESS_PARAMETERS: values/{{.config}}.json
preconditions:
- sh: "test {{.config}}"
msg: "config Variable was not set"
- sh: "test -f values/{{.config}}.json"
msg: "Values file not found: values/{{.config}}.json"
generateconfig:
desc: Generate skeleton for a waf configuration file
cmds:
- if [[ "{{.config}}" ]] ; then ts-node lib/tools/generate-skeleton.ts >> values/{{.config}}.json ; else ts-node lib/tools/generate-skeleton.ts ; fi
silent: true
4 changes: 2 additions & 2 deletions bin/plattform-wafv2-cdk-automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ if (configFile && existsSync(configFile)) {
account: process.env.CDK_DEFAULT_ACCOUNT,
},
});
const Prices = await GetCurrentPrices(PriceRegions[deploymentRegion as RegionString], runtimeProperties)
const PriceCalculated = await isPriceCalculated(runtimeProperties)
const Prices = await GetCurrentPrices(PriceRegions[deploymentRegion as RegionString], runtimeProperties);
const PriceCalculated = await isPriceCalculated(runtimeProperties);
})();
} else {
console.log(`
Expand Down
17 changes: 10 additions & 7 deletions lib/plattform-wafv2-cdk-automation-stack.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { aws_wafv2 as wafv2 } from "aws-cdk-lib";
import { aws_wafv2 as wafv2, Tag } from "aws-cdk-lib";
import { aws_fms as fms } from "aws-cdk-lib";
import { aws_kinesisfirehose as firehouse } from "aws-cdk-lib";
import { aws_iam as iam } from "aws-cdk-lib";
Expand Down Expand Up @@ -138,10 +138,8 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack {
logDestinationConfigs: ["${S3DeliveryStream.Arn}"],
},
};

new fms.CfnPolicy(this, "CfnPolicy", {
excludeResourceTags: false,
remediationEnabled: false,
const CfnPolicyProps = {
remediationEnabled: props.config.WebAcl.RemediationEnabled ? props.config.WebAcl.RemediationEnabled : false,
resourceType: props.config.WebAcl.Type,
policyName:
props.config.General.Prefix.toUpperCase() +
Expand All @@ -151,14 +149,19 @@ export class PlattformWafv2CdkAutomationStack extends cdk.Stack {
props.config.General.Stage +
"-" +
props.config.General.DeployHash,
includeMap: { account: props.config.General.DeployTo },
includeMap: props.config.WebAcl.IncludeMap,
excludeMap: props.config.WebAcl.ExcludeMap,
securityServicePolicyData: {
Type: "WAFV2",
ManagedServiceData: cdk.Fn.sub(
JSON.stringify(managedServiceData)
),
},
});
resourcesCleanUp: props.config.WebAcl.ResourcesCleanUp ? props.config.WebAcl.ResourcesCleanUp : false,
resourceTags: props.config.WebAcl.ResourceTags,
excludeResourceTags: props.config.WebAcl.ExcludeResourceTags ? props.config.WebAcl.ExcludeResourceTags : false,
};
new fms.CfnPolicy(this, "CfnPolicy", CfnPolicyProps);

const options = { flag: "w", force: true };
(async () => {
Expand Down
41 changes: 41 additions & 0 deletions lib/tools/generate-skeleton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Config } from "../types/config";

const skeletonConfig : Config = {
General: {
DeployHash: "",
FireHoseKeyArn: "",
Prefix: "myPrefix",
Stage: "dev|int|clone|live",
S3LoggingBucketName: "myBucketName",
SecuredDomain: "yourapp.<stage>.<domain>"
},
WebAcl: {
IncludeMap: {
account: ["123456789123"]
},
Name: "myWAF-Name",
PreProcess: {
ManagedRuleGroups: [
{
Vendor: "AWS",
Name: "AWSManagedRulesAmazonIpReputationList",
Capacity: 25,
Version: ""
},
{
Vendor: "AWS",
Name: "AWSManagedRulesCommonRuleSet",
Capacity: 700,
Version: "",
}
]
},
PostProcess: {

},
Scope: "REGIONAL",
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
}
};

console.log(JSON.stringify(skeletonConfig, null, 2));
40 changes: 20 additions & 20 deletions lib/tools/price-calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function findValuesHelper(obj:any, key:string, list: any) {
}
if (obj[key]) list.push(obj[key]);

if ((typeof obj === "object") && (obj !== null) ){
if ((typeof obj == "object") && (obj !== null) ){
const children = Object.keys(obj);
if (children.length > 0){
for (let i = 0; i < children.length; i++ ){
Expand All @@ -52,15 +52,15 @@ export async function GetCurrentPrices(deploymentRegion: PriceRegions, runtimePr
runtimeProps.Pricing.Request = (await getProductPrice(deploymentRegion,"awswaf",undefined,"Request") * 1000000);
runtimeProps.Pricing.BotControl = Number(await getProductPrice(deploymentRegion,"awswaf",undefined,"AMR Bot Control Entity"));
const BotControlRequest: any = await getProductPrice(deploymentRegion,"awswaf",undefined,undefined,"AMR Bot Control Request Processed");
runtimeProps.Pricing.BotControlRequest = (BotControlRequest[0] * 1000000);
runtimeProps.Pricing.Captcha = 0.4;
runtimeProps.Pricing.BotControlRequest = (BotControlRequest[0] * 1000000)
runtimeProps.Pricing.Captcha = 0.4
runtimeProps.Pricing.AccountTakeoverPrevention = Number(await getProductPrice(deploymentRegion,"awswaf",undefined,"AMR ATP Entity"));
const AccountTakeoverPreventionRequest: any = await getProductPrice(deploymentRegion,"awswaf",undefined,"AMR ATP Login Attempt");
const AccountTakeoverPreventionRequest: any = await getProductPrice(deploymentRegion,"awswaf",undefined,"AMR ATP Login Attempt")
runtimeProps.Pricing.AccountTakeoverPreventionRequest = (AccountTakeoverPreventionRequest[0] * 1000);
return true;
return true
}
catch{
return false;
return false
}
}

Expand All @@ -73,31 +73,31 @@ export async function GetCurrentPrices(deploymentRegion: PriceRegions, runtimePr
*/
async function getProductPrice(deploymentRegion: PriceRegions, servicecode: string, operation?: string,group?: string, groupDescription?: string): Promise<number> {
const client = new PricingClient({region: PRICING_API_ENDPOINT_REGION});
const Filters: {Type: string, Field: string, Value: string}[] = [];
const Filters: {Type: string, Field: string, Value: string}[] = []
if(groupDescription){
Filters.push({
Type: "TERM_MATCH",
Field: "groupDescription",
Value: groupDescription});
Value: groupDescription})
}
if(group){
Filters.push({
Type: "TERM_MATCH",
Field: "group",
Value: group});
Value: group})
}
if(operation){
Filters.push({
Type: "TERM_MATCH",
Field: "operation",
Value: operation
});
})
}
Filters.push({
Type: "TERM_MATCH",
Field: "location",
Value: deploymentRegion
});
})

const input: GetProductsCommandInput = {
Filters,
Expand All @@ -120,23 +120,23 @@ async function getProductPrice(deploymentRegion: PriceRegions, servicecode: stri
* @returns whether price is successfully calculated or not
*/
export async function isPriceCalculated(runtimeProps: RuntimeProperties): Promise<string> {
const preprocessfixedcost = (runtimeProps.PreProcess.CustomRuleCount * runtimeProps.Pricing.Rule) + runtimeProps.PreProcess.CustomRuleGroupCount + runtimeProps.PreProcess.ManagedRuleGroupCount;
const postprocessfixedcost = (runtimeProps.PostProcess.CustomRuleCount * runtimeProps.Pricing.Rule) + runtimeProps.PostProcess.CustomRuleGroupCount + runtimeProps.PostProcess.ManagedRuleGroupCount;
const captchacost = (runtimeProps.PostProcess.CustomCaptchaRuleCount + runtimeProps.PreProcess.CustomCaptchaRuleCount) * runtimeProps.Pricing.Captcha;
const botcontrolfixedcost = (runtimeProps.PostProcess.ManagedRuleBotControlCount + runtimeProps.PreProcess.ManagedRuleBotControlCount) * runtimeProps.Pricing.BotControl;
const atpfixedcost = (runtimeProps.PostProcess.ManagedRuleATPCount + runtimeProps.PreProcess.ManagedRuleATPCount) * runtimeProps.Pricing.AccountTakeoverPrevention;
const fixedcost = runtimeProps.Pricing.Policy + runtimeProps.Pricing.WebACL + postprocessfixedcost + preprocessfixedcost + botcontrolfixedcost + atpfixedcost;
const preprocessfixedcost = (runtimeProps.PreProcess.CustomRuleCount * runtimeProps.Pricing.Rule) + runtimeProps.PreProcess.CustomRuleGroupCount + runtimeProps.PreProcess.ManagedRuleGroupCount
const postprocessfixedcost = (runtimeProps.PostProcess.CustomRuleCount * runtimeProps.Pricing.Rule) + runtimeProps.PostProcess.CustomRuleGroupCount + runtimeProps.PostProcess.ManagedRuleGroupCount
const captchacost = (runtimeProps.PostProcess.CustomCaptchaRuleCount + runtimeProps.PreProcess.CustomCaptchaRuleCount) * runtimeProps.Pricing.Captcha
const botcontrolfixedcost = (runtimeProps.PostProcess.ManagedRuleBotControlCount + runtimeProps.PreProcess.ManagedRuleBotControlCount) * runtimeProps.Pricing.BotControl
const atpfixedcost = (runtimeProps.PostProcess.ManagedRuleATPCount + runtimeProps.PreProcess.ManagedRuleATPCount) * runtimeProps.Pricing.AccountTakeoverPrevention
const fixedcost = runtimeProps.Pricing.Policy + runtimeProps.Pricing.WebACL + postprocessfixedcost + preprocessfixedcost + botcontrolfixedcost + atpfixedcost
const requestscost = runtimeProps.Pricing.Request;
const totalcost = fixedcost + (requestscost * 5) + (captchacost * 5);
const totalcost = fixedcost + (requestscost * 5) + (captchacost * 5)
console.log("\n💰 Cost: \n");
console.log(" WAF Rules cost: " + fixedcost + " $ per month");
console.log(" WAF Requests: "+ requestscost + " $ per 1 mio requests");
console.log(" WAF Requests: "+ requestscost + " $ pro 1 mio requests");
(captchacost > 0) ? console.log(" WAF Analysis fee:\n Captcha: " +captchacost +"$ per thousand challenge attempts analyzed") : " ";
console.log("\n Total WAF cost (monthly): "+ totalcost + " $ *");
console.log("\n * This costs are based on expectation that the WAF gets 5 mio requests per month. ");
(atpfixedcost !== 0) ? console.log("\n *This costs are based on expectation that 10.000 login attempts where analyzed. ") : "";
console.log("\n ℹ The costs are calculated based on the provided information at https://aws.amazon.com/waf/pricing/. ");
(botcontrolfixedcost !== 0) ? console.log(" The deployed WAF includes BotControl rules this costs an extra fee of "+runtimeProps.Pricing.BotControl +" $ and " +runtimeProps.Pricing.BotControlRequest +"$ per 1 mio requests (10 mio request Free Tier). \n These costs are already included in the price calculation.") : "";
(botcontrolfixedcost !== 0) ? console.log(" The deployed WAF includes BotControl rules this costs an extra fee of "+runtimeProps.Pricing.BotControl +" $ and " +runtimeProps.Pricing.BotControlRequest +"$ pro 1 mio requests (10 mio request Free Tier). \n These costs are already included in the price calculation.") : "";
(atpfixedcost !== 0) ? console.log(" The deployed WAF includes Account Takeover Prevention rules this costs an extra fee of "+runtimeProps.Pricing.AccountTakeoverPrevention+" $ and " + runtimeProps.Pricing.AccountTakeoverPreventionRequest +" $ per thousand login attempts analyzed (10,000 attempts analyzed Free Tier). \n These costs are already included in the price calculation.") : "";
const pricecalculated = "True";
return pricecalculated;
Expand Down
Loading

0 comments on commit b4a2c28

Please sign in to comment.