Skip to content

Commit

Permalink
Merge pull request #26 from globaldatanet/feature/pricecalculation
Browse files Browse the repository at this point in the history
Add PriceCalculation Feature
  • Loading branch information
daknhh authored Mar 22, 2022
2 parents 1458c88 + 28327da commit 3e7ce56
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Change Log

## Released
## 2.1.2

### Added

- Price calculation for your WAF
## 2.1.1

### Fixed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ If you want to learn more about the AWS Firewall Factory feel free to look at th

- Components of a label are separated by a colon (:).

19. While Deployment the Price for your WAF will be calculated using the Pricing API
### Coming soon

- Deployment via Teamcity
Expand Down
5 changes: 4 additions & 1 deletion bin/plattform-wafv2-cdk-automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { PlattformWafv2CdkAutomationStack } from "../lib/plattform-wafv2-cdk-aut
import * as cdk from "aws-cdk-lib";
import { realpathSync, existsSync } from "fs";
import { validate } from "../lib/tools/config-validator";
import { Config } from "../lib/types/config";
import { Config, PriceRegions, RegionString } from "../lib/types/config";
import { isPolicyQuotaReached, isWcuQuotaReached, setOutputsFromStack, initRuntimeProperties } from "../lib/tools/helpers";
import {isPriceCalculated, GetCurrentPrices} from "../lib/tools/price-calculator";
import * as packageJsonObject from "../package.json";

/**
Expand Down Expand Up @@ -80,6 +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)
})();
} else {
console.log(`
Expand Down
37 changes: 35 additions & 2 deletions lib/tools/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ async function calculateCapacities(
);
} else {
while (count < config.WebAcl.PreProcess.CustomRules.length) {
runtimeProperties.PreProcess.CustomRuleCount += 1;
if ("Captcha" in config.WebAcl.PreProcess.CustomRules[count].Action) {
runtimeProperties.PreProcess.CustomCaptchaRuleCount += 1;
const rules : Rule[] = [];
const { CloudWatchMetricsEnabled, SampledRequestsEnabled } =
config.WebAcl.PreProcess.CustomRules[count].VisibilityConfig;
Expand Down Expand Up @@ -282,6 +284,7 @@ async function calculateCapacities(
);
} else {
while (count < config.WebAcl.PostProcess.CustomRules.length) {
runtimeProperties.PostProcess.CustomRuleCount += 1;
const rule_calculated_capacity_json = [];
const { CloudWatchMetricsEnabled, SampledRequestsEnabled } =
config.WebAcl.PostProcess.CustomRules[count].VisibilityConfig;
Expand All @@ -296,6 +299,7 @@ async function calculateCapacities(
},
};
if ("Captcha" in config.WebAcl.PostProcess.CustomRules[count].Action) {
runtimeProperties.PostProcess.CustomCaptchaRuleCount += 1;
rule.CaptchaConfig =
config.WebAcl.PostProcess.CustomRules[count].CaptchaConfig;
}
Expand Down Expand Up @@ -341,6 +345,9 @@ async function calculateCapacities(
"]"
);
runtimeProperties.ManagedRuleCapacity += capacity;
runtimeProperties.PreProcess.ManagedRuleGroupCount += 1;
managedrule.Name == "AWSManagedRulesBotControlRuleSet" ? runtimeProperties.PreProcess.ManagedRuleBotControlCount +=1 : ""
managedrule.Name == "AWSManagedRulesATPRuleSet" ? runtimeProperties.PreProcess.ManagedRuleATPCount += 1 : ""
}
}
if (!config.WebAcl.PostProcess.ManagedRuleGroups) {
Expand All @@ -364,6 +371,9 @@ async function calculateCapacities(
"]"
);
runtimeProperties.ManagedRuleCapacity += capacity;
runtimeProperties.PostProcess.ManagedRuleGroupCount += 1;
managedrule.Name == "AWSManagedRulesBotControlRuleSet" ? runtimeProperties.PostProcess.ManagedRuleBotControlCount +=1 : ""
managedrule.Name == "AWSManagedRulesATPRuleSet" ? runtimeProperties.PostProcess.ManagedRuleATPCount += 1 : ""
}
}
runtimeProperties.PostProcess.Capacity = PostProcessCapacity;
Expand Down Expand Up @@ -428,15 +438,38 @@ export function initRuntimeProperties() : RuntimeProperties {
DeployedRuleGroupCapacities: [],
DeployedRuleGroupIdentifier: [],
DeployedRuleGroupNames: [],
RuleCapacities: []
RuleCapacities: [],
ManagedRuleGroupCount: 0,
ManagedRuleBotControlCount: 0,
ManagedRuleATPCount: 0,
CustomRuleCount: 0,
CustomRuleGroupCount: 0,
CustomCaptchaRuleCount: 0
},
PreProcess: {
Capacity: 0,
DeployedRuleGroupCapacities: [],
DeployedRuleGroupIdentifier: [],
DeployedRuleGroupNames: [],
RuleCapacities: []
RuleCapacities: [],
ManagedRuleGroupCount: 0,
ManagedRuleBotControlCount: 0,
ManagedRuleATPCount: 0,
CustomRuleCount: 0,
CustomRuleGroupCount: 0,
CustomCaptchaRuleCount: 0
},
Pricing: {
Policy: 0,
Rule: 0,
WebACL: 0,
Request: 0,
BotControl: 0,
BotControlRequest: 0,
Captcha: 0,
AccountTakeoverPrevention: 0,
AccountTakeoverPreventionRequest: 0,
}
};
}

Expand Down
143 changes: 143 additions & 0 deletions lib/tools/price-calculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { PricingClient, GetProductsCommand, GetProductsCommandInput } from "@aws-sdk/client-pricing";
import { RuntimeProperties } from "../types/runtimeprops";
import { PriceRegions } from "../types/config";
/**
* Amazon Web Services Price List Service API Endpoint
*/
const PRICING_API_ENDPOINT_REGION = "us-east-1";


/**
*
* @param obj object where the key is included
* @param key key which includes the needed value
* @returns value from key
*/
function findValues(obj: any, key: string){
return findValuesHelper(obj, key, []);
}

function findValuesHelper(obj:any, key:string, list: any) {
if (!obj) return list;
if (obj instanceof Array) {
for (const i in obj) {
list = list.concat(findValuesHelper(obj[i], key, []));
}
return list;
}
if (obj[key]) list.push(obj[key]);

if ((typeof obj === "object") && (obj !== null) ){
const children = Object.keys(obj);
if (children.length > 0){
for (let i = 0; i < children.length; i++ ){
list = list.concat(findValuesHelper(obj[children[i]], key, []));
}
}
}
return list;
}

/**
*
* @param deploymentRegion AWS region, e.g. eu-central-1
* @param runtimeProps runtime properties object, where to store prices
* @returns true if prices are update in runtimeprops
*/
export async function GetCurrentPrices(deploymentRegion: PriceRegions, runtimeProps: RuntimeProperties): Promise<boolean> {
try{
runtimeProps.Pricing.Policy = Number(await getProductPrice(deploymentRegion,"AWSFMS","WAFv2"));
runtimeProps.Pricing.Rule = Number(await getProductPrice(deploymentRegion,"awswaf",undefined,"Rule"));
runtimeProps.Pricing.WebACL = Number(await getProductPrice(deploymentRegion,"awswaf",undefined,"Web ACL"));
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.AccountTakeoverPrevention = Number(await getProductPrice(deploymentRegion,"awswaf",undefined,"AMR ATP Entity"));
const AccountTakeoverPreventionRequest: any = await getProductPrice(deploymentRegion,"awswaf",undefined,"AMR ATP Login Attempt");
runtimeProps.Pricing.AccountTakeoverPreventionRequest = (AccountTakeoverPreventionRequest[0] * 1000);
return true;
}
catch{
return false;
}
}

/**
*
* @param deploymentRegion AWS region, e.g. eu-central-1
* @param servicecode The code for the service whose products you want to retrieve.
* @param operation
* @returns price for one product
*/
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}[] = [];
if(groupDescription){
Filters.push({
Type: "TERM_MATCH",
Field: "groupDescription",
Value: groupDescription});
}
if(group){
Filters.push({
Type: "TERM_MATCH",
Field: "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,
ServiceCode: servicecode
};
const command = new GetProductsCommand(input);
const response : any = await client.send(command);
if (!response.PriceList || !response.PriceList[0]) {
throw new Error("Price list does not exist");
}
const priceList = response.PriceList[0] as any;
const USD = findValues(JSON.parse(priceList.toJSON()),"USD");
return USD || 0;
}


/**
* The function calculated the price of the deployed WAF
* @param runtimeProps runtime properties object, where to get prices
* @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 requestscost = runtimeProps.Pricing.Request;
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");
(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.") : "";
(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;
}
26 changes: 26 additions & 0 deletions lib/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,33 @@ export interface Config {
readonly PostProcess: RuleGroupSet
},
}
export type RegionString = "us-west-2" | "us-west-1" | "us-east-2" | "us-east-1" | "ap-south-1"| "ap-northeast-2" | "ap-northeast-1" | "ap-southeast-1" | "ap-southeast-2" | "ca-central-1" | "cn-north-1" | "eu-central-1" | "eu-west-1" | "eu-west-2" | "eu-west-3" | "sa-east-1" | "us-gov-west-1" | "ap-east-1" | "ap-southeast-3" | "ap-northeast-3" | "eu-south-1" | "eu-north-1" | "me-south-1";

export enum PriceRegions{
"us-west-2"= "US West (Oregon)",
"us-west-1"= "US West (N. California)",
"us-east-2"= "US East (Ohio)",
"us-east-1"= "US East (N. Virginia)",
"ap-south-1"= "Asia Pacific (Mumbai)",
"ap-northeast-2"= "Asia Pacific (Seoul)",
"ap-northeast-1"= "Asia Pacific (Tokyo)",
"ap-southeast-1"= "Asia Pacific (Singapore)",
"ap-southeast-2"= "Asia Pacific (Sydney)",
"ca-central-1"= "Canada (Central)",
"cn-north-1"= "China (Beijing)",
"eu-central-1"= "EU (Frankfurt)",
"eu-west-1"= "EU (Ireland)",
"eu-west-2"= "EU (London)",
"eu-west-3"= "EU (Paris)",
"sa-east-1"= "South America (São Paulo)",
"us-gov-west-1"= "AWS GovCloud (US)",
"ap-east-1" = "Asia Pacific (Hong Kong)",
"ap-southeast-3" = "Asia Pacific (Jakarta)",
"ap-northeast-3" = "Asia Pacific (Osaka)",
"eu-south-1" = "Europe (Milan)",
"eu-north-1" = "Europe (Stockholm)",
"me-south-1" = "Middle East (Bahrain)"
}
export interface RuleGroupSet {
CustomRules?: Rule[],
ManagedRuleGroups?: ManagedRuleGroup[];
Expand Down
23 changes: 21 additions & 2 deletions lib/types/runtimeprops.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
export interface RuntimeProperties {
PreProcess: ProcessProperties,
PostProcess: ProcessProperties,
ManagedRuleCapacity: number
ManagedRuleCapacity: number,
Pricing: ResourcePrices,
}
export interface ResourcePrices {
Policy: number,
Rule: number,
WebACL: number,
Request: number,
BotControl: number,
BotControlRequest: number,
Captcha: number,
AccountTakeoverPrevention: number,
AccountTakeoverPreventionRequest: number,
}

export interface ProcessProperties {
Capacity: number,
RuleCapacities: number[],
DeployedRuleGroupCapacities: number[],
DeployedRuleGroupNames: string[],
DeployedRuleGroupIdentifier: string[]
DeployedRuleGroupIdentifier: string[],
ManagedRuleGroupCount: number,
ManagedRuleBotControlCount: number,
ManagedRuleATPCount: number,
CustomRuleCount: number,
CustomRuleGroupCount: number,
CustomCaptchaRuleCount: number

}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "plattform-wafv2-cdk-automation",
"version": "2.1.1",
"version": "2.1.2",
"bin": {
"plattform-wafv2-cdk-automation": "bin/plattform-wafv2-cdk-automation.js"
},
Expand Down Expand Up @@ -29,6 +29,7 @@
"dependencies": {
"@aws-sdk/client-cloudformation": "^3.52.0",
"@aws-sdk/client-fms": "^3.52.0",
"@aws-sdk/client-pricing": "^3.54.1",
"@aws-sdk/client-service-quotas": "^3.52.0",
"@aws-sdk/client-wafv2": "^3.52.0",
"@mhlabs/cfn-diagram": "^1.1.32",
Expand Down

0 comments on commit 3e7ce56

Please sign in to comment.