Skip to content

Commit

Permalink
Merge pull request #195 from globaldatanet/4.1.1
Browse files Browse the repository at this point in the history
4.1.1
  • Loading branch information
daknhh authored Oct 10, 2023
2 parents d5404d0 + 0f558b4 commit 511fdaa
Show file tree
Hide file tree
Showing 15 changed files with 2,718 additions and 1,250 deletions.
8 changes: 4 additions & 4 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ assignees: ''

---

**Describe the bug
**Describe the bug**
A clear and concise description of what the bug is.

**How to reproduce it
**How to reproduce it**
Steps to reproduce the behaviour.

**Expected behaviour
**Expected behaviour**
A clear and concise description of what you expect to happen.

**Please fill in the following information about the solution:**.
Expand All @@ -25,5 +25,5 @@ A clear and concise description of what you expect to happen.
**Screenshots**.
If applicable, add screenshots to help explain your problem (please **do NOT include sensitive information**).

**Additional context
**Additional context**
Add any other context to the issue here.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
# Change Log

## Released
## 4.1.1
### Added
- Added Console output if ManagedRuleGroup OverrideAction is set to Count - This option is not commonly used. If any rule in the rule group results in a match, this override sets the resulting action from the rule group to Count.
- Enums for all AWS ManagedRuleGroup Rules and Labels, this will help you to not create exclude Rules of Label Match Statements for none existing Rules or Labels. AWS CloudFormation even not trow any error right now if you try use not existing Labels or Rules.
- Optional Lambda function to prerequisite Stack that sends notifications about changes in AWS managed rule groups, such as upcoming new versions and urgent security updates, to messaging platforms like Slack or Teams.
### Fixed
- RegexPatternSets and IPSets in NotStatements AWS Firewall Factory are ignored while WCU calculation
- gotestwaf task was not customized for Typescript configuration files.
- ManagedRuleGroupVersion lambda was always using the latest ManagedRulegroup version if no version was specified. Now the lambda function is using the [current Default version](https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups-versioning.html).
- Added Optional Parameter for ManagedRuleGroupVersion lambda, you can now set enforceUpdate to load the latest or the current Default version during WAF update.
- Bump @aws-cdk-lib from 2.93.0 to 2.100.0
- Bump @aws-cdk from 2.93.0 to 2.100.0
- Bump @aws-sdk/client-cloudformation from 3.398.0 to 3.427.0
- Bump @aws-sdk/client-cloudwatch from 3.398.0 to 3.427.0
- Bump @aws-sdk/client-fms from 3.398.0 to 3.427.0
- Bump @aws-sdk/client-pricing from 3.398.0 to 3.427.0
- Bump @aws-sdk/client-service-quotas from 3.398.0 to 3.427.0
- Bump @aws-sdk/client-shield from 3.398.0 to 3.427.0
- Bump @aws-sdk/client-wafv2 from 3.398.0 to 3.427.0
- Bump @typescript-eslint/eslint-plugin from 6.4.1 to 6.7.4

## 4.1.0

### Added
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ If you want to learn more about the AWS Firewall Factory feel free to look at th
#### 🔗 Useful Links

- [🐦🤖 Twitter Bot to get Notified for Managed Rules Updates](https://twitter.com/AWSMgMtRulesBot)
- [🔔 Slack automation to get Notified for Managed Rules Updates](https://github.com/globaldatanet/WAF-Managed-Rules-Update-Slack-Notification-Service)
- [🏫 AWS WAF Workshop](https://catalog.us-east-1.prod.workshops.aws/workshops/c2f03000-cf61-42a6-8e62-9eaf04907417/en-US/02-custom-rules)
## 🗺️ Architecture

Expand Down
11 changes: 7 additions & 4 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,24 @@ tasks:
creatediagram:
desc: Create Diagram
cmds:
- if [[ {{.CREATE_DIAGRAM}} = true ]] ; then echo 🤳🏻 $(cfn-dia draw.io -t cdk.out/"$(cat values/{{.config}}.json | jq -r '.General.Prefix')-WAF-$(cat values/{{.config}}.json | jq -r '.WebAcl.Name')-$(cat values/{{.config}}.json | jq -r '.General.Stage')-$(cat values/{{.config}}.json | jq -r '.General.DeployHash')".template.json --output-file $(sed "s/values/diagrams/g;s/.json/.drawio/g" <<< values/{{.config}}.json) --ci-mode --skip-synth); else echo ⏭ Skipping Diagram generation 🤳🏻 ; fi
- if [[ {{.CREATE_DIAGRAM}} = true ]] ; then echo 🤳🏻 $(cfn-dia draw.io -t cdk.out/"$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.General.Prefix')-WAF-$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.WebAcl.Name')-$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.General.Stage')-$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.General.DeployHash')".template.json --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
env:
PROCESS_PARAMETERS: values/{{.config}}.json
testwaf:
desc: Test of your waf using GoTestWAF
cmds:
- echo 🧪 Testing of your new 🔥 WAF using GoTestWAF
- mkdir -p ./waf-evaluation-report/$(date '+%Y-%m-%d')
- |
items=$(cat values/{{.config}}.json | jq -r '.[] | .SecuredDomain[]?')
items=$(ts-node ./gotestwaf/gotestwaf.ts | jq -r '.[] | .SecuredDomain[]?')
for item in ${items[@]}; do
echo "Using fqdn in 🖥 url : $item"
./gotestwaf/gotestwaf --url https://$item --workers 50 --blockConnReset --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 --reportPath "./waf-evaluation-report/$(date '+%Y-%m-%d')" --reportName "$(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')"
echo "Using fqdn in 🖥 url : $item"
./gotestwaf/gotestwaf --url https://$item --workers 50 --blockConnReset --wafName="$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.General.Prefix')-$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.WebAcl.Name')-$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.General.Stage')-$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.General.DeployHash')" --configPath=./gotestwaf/config.yaml --testCasesPath=./gotestwaf/testcases --skipWAFBlockCheck --reportPath "./waf-evaluation-report/$(date '+%Y-%m-%d')" --reportName "$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.General.Prefix')-$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.WebAcl.Name')-$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.General.Stage')-$(ts-node ./gotestwaf/gotestwaf.ts| jq -r '.General.DeployHash')"
done
silent: true
env:
PROCESS_PARAMETERS: "{{.config}}"
preconditions:
- sh: "[ '{{.WAF_TEST}}' != 'true' ]"
msg: ⏭ Skipping WAF Testing 🧪
Expand Down
51 changes: 49 additions & 2 deletions lib/firewall-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,10 @@ const subVariables : SubVariables = {};
function buildServiceDataManagedRgs(scope: Construct, managedRuleGroups: ManagedRuleGroup[], managedRuleGroupVersionProvider: cr.Provider, wafScope: string) : ServiceDataManagedRuleGroup[] {
const cfnManagedRuleGroup : ServiceDataManagedRuleGroup[] = [];
for (const managedRuleGroup of managedRuleGroups) {

if(managedRuleGroup.overrideAction?.type === "COUNT"){
// eslint-disable-next-line quotes
console.log("\x1b[31m",`\n🚨 OverrideAction of ManagedRuleGroup ${managedRuleGroup.name} is set to COUNT, which simply tallies all rules within the group.\n However, this practice may create a vulnerability in your firewall and is not recommended.`,"\x1b[0m");
}
if(NONEVERSIONEDMANAGEDRULEGRPOUP.find((rulegroup) => rulegroup === managedRuleGroup.name)){
console.log("\nℹ️ ManagedRuleGroup " + managedRuleGroup.name + " is not versioned. Skip Custom Resource for Versioning.");
cfnManagedRuleGroup.push({
Expand All @@ -297,7 +300,8 @@ function buildServiceDataManagedRgs(scope: Construct, managedRuleGroups: Managed
Name: managedRuleGroup.name,
Scope: wafScope,
ManagedRuleGroupVersion: managedRuleGroup.version,
Latest: managedRuleGroup.latestVersion ? managedRuleGroup.latestVersion : false
Latest: managedRuleGroup.latestVersion ? managedRuleGroup.latestVersion : false,
EnforceUpdate: managedRuleGroup.enforceUpdate ? Date.now() : false
},
serviceToken: managedRuleGroupVersionProvider.serviceToken,
});
Expand Down Expand Up @@ -733,6 +737,21 @@ function transformRuleStatements(rule: Rule, prefix: string, stage: string, ipSe
let ipSetReferenceStatement = rule.statement.ipSetReferenceStatement as wafv2.CfnWebACL.IPSetReferenceStatementProperty | undefined;
let regexPatternSetReferenceStatement = rule.statement.regexPatternSetReferenceStatement as wafv2.CfnWebACL.RegexPatternSetReferenceStatementProperty | undefined;

const notStatement = rule.statement.notStatement as wafv2.CfnWebACL.NotStatementProperty | undefined;
if(notStatement && (ipSets || regexPatternSets)) {
let statement = notStatement.statement as cdk.aws_wafv2.CfnWebACL.StatementProperty;
ipSetReferenceStatement = statement.ipSetReferenceStatement as wafv2.CfnWebACL.IPSetReferenceStatementProperty | undefined;
if (ipSetReferenceStatement && ipSets) {
statement = getActualIpReferenceStatementInStatement(ipSetReferenceStatement, prefix, stage, ipSets);
}
regexPatternSetReferenceStatement = statement.regexPatternSetReferenceStatement as wafv2.CfnWebACL.RegexPatternSetReferenceStatementProperty | undefined;
if(regexPatternSetReferenceStatement && regexPatternSets) {
statement = getActualRegexPatternSetReferenceStatementProperty(regexPatternSetReferenceStatement, prefix, stage, regexPatternSets);
}
const adjustedstatement = {notStatement: {statement}};
statement = adjustedstatement as cdk.aws_wafv2.CfnWebACL.StatementProperty;
}

const andStatement = rule.statement.andStatement as wafv2.CfnWebACL.AndStatementProperty | undefined;

if (andStatement) {
Expand All @@ -746,6 +765,20 @@ function transformRuleStatements(rule: Rule, prefix: string, stage: string, ipSe
if(regexPatternSetReferenceStatement && regexPatternSets) {
statements[i] = getActualRegexPatternSetReferenceStatementProperty(regexPatternSetReferenceStatement, prefix, stage, regexPatternSets);
}
const notStatement = statements[i].notStatement as wafv2.CfnWebACL.NotStatementProperty | undefined;
if(notStatement && (ipSets || regexPatternSets)) {
let statement = notStatement.statement as cdk.aws_wafv2.CfnWebACL.StatementProperty;
ipSetReferenceStatement = statement.ipSetReferenceStatement as wafv2.CfnWebACL.IPSetReferenceStatementProperty | undefined;
if (ipSetReferenceStatement && ipSets) {
statement = getActualIpReferenceStatementInStatement(ipSetReferenceStatement, prefix, stage, ipSets);
}
regexPatternSetReferenceStatement = statement.regexPatternSetReferenceStatement as wafv2.CfnWebACL.RegexPatternSetReferenceStatementProperty | undefined;
if(regexPatternSetReferenceStatement && regexPatternSets) {
statement = getActualRegexPatternSetReferenceStatementProperty(regexPatternSetReferenceStatement, prefix, stage, regexPatternSets);
}
const adjustedstatement = {notStatement: {statement}};
statements[i] = adjustedstatement as cdk.aws_wafv2.CfnWebACL.StatementProperty;
}
}
}

Expand All @@ -762,6 +795,20 @@ function transformRuleStatements(rule: Rule, prefix: string, stage: string, ipSe
if(regexPatternSetReferenceStatement && regexPatternSets) {
statements[i] = getActualRegexPatternSetReferenceStatementProperty(regexPatternSetReferenceStatement, prefix, stage, regexPatternSets);
}
const notStatement = statements[i].notStatement as wafv2.CfnWebACL.NotStatementProperty | undefined;
if(notStatement && (ipSets || regexPatternSets)) {
let statement = notStatement.statement as cdk.aws_wafv2.CfnWebACL.StatementProperty;
ipSetReferenceStatement = statement.ipSetReferenceStatement as wafv2.CfnWebACL.IPSetReferenceStatementProperty | undefined;
if (ipSetReferenceStatement && ipSets) {
statement = getActualIpReferenceStatementInStatement(ipSetReferenceStatement, prefix, stage, ipSets);
}
regexPatternSetReferenceStatement = statement.regexPatternSetReferenceStatement as wafv2.CfnWebACL.RegexPatternSetReferenceStatementProperty | undefined;
if(regexPatternSetReferenceStatement && regexPatternSets) {
statement = getActualRegexPatternSetReferenceStatementProperty(regexPatternSetReferenceStatement, prefix, stage, regexPatternSets);
}
const adjustedstatement = {notStatement: {statement}};
statements[i] = adjustedstatement as cdk.aws_wafv2.CfnWebACL.StatementProperty;
}
}
}

Expand Down
136 changes: 136 additions & 0 deletions lib/lambda/ManagedRuleGroupInfo/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import json
import logging
import os
import urllib.request
import boto3

wafv2 = boto3.client('wafv2')

HOOK_URL = os.environ['WebhookUrl']
MESSENGER = os.environ['Messenger']

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def get_latest_default_rule_set_version(rule_set_name):
try:
response = wafv2.list_available_managed_rule_group_versions(Name=rule_set_name, Scope='REGIONAL', VendorName='AWS')
DefaultVersion = response.get('CurrentDefaultVersion')
return DefaultVersion
except Exception as e:
print('Error getting the default version for the rule set:', str(e))
return None


def format_slack_message(data, latest_default_rule_set_version):
payload = {
'username': f"WAF {data['Type']}",
'icon_emoji': ':managedrule:',
'text': f"{data['Subject']}",
'attachments': [
{
'fallback': "Detailed information on {data['Type']}.",
'color': 'green',
'title': data['Subject'],
'text': data['Message'],
'fields': [
{
'title': 'Managed Rule Group',
'value': data['MessageAttributes']['managed_rule_group']['Value'],
'short': True
},
{
'title': 'Default Version',
'value': latest_default_rule_set_version,
'short': True
},
{
'title': 'Info',
'value': "AWS WAF sets the default version to the static version recommended by the provider. Default version is automatically updated when the provider recommends a new version.",
'short': False
}
]
}
]
}
return payload


def format_teams_message(data, latest_default_rule_set_version):
message = {
"type":"message",
"attachments":[
{
"contentType":"application/vnd.microsoft.card.adaptive",
"contentUrl": "",
"content":{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"msteams": {
"width": "Full"
},
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": f"{data['Subject']}",
"size": "Large",
"weight": "Bolder",
"wrap": True
},
{
"type": "TextBlock",
"text": f"{data['Message']}",
"separator": True,
"wrap": True
},
{
"type": "FactSet",
"facts": [
{
"title": "Managed Rule Group",
"value": data['MessageAttributes']['managed_rule_group']['Value']
},
{
"title": "Default Version",
"value": latest_default_rule_set_version
}
],
"separator": True
},
{
"type": "TextBlock",
"text": f"AWS WAF sets the default version to the static version recommended by the provider. Default version is automatically updated when the provider recommends a new version.",
"separator": True,
"wrap": True
}
]
}
}
]
}
return message


def notify(url, payload):
data = json.dumps(payload).encode('utf-8')
method = 'POST'
headers = {'Content-Type': 'application/json'}

request = urllib.request.Request(url, data = data, method = method, headers = headers)
with urllib.request.urlopen(request) as response:
return response.read().decode('utf-8')


def lambda_handler(event, context):
logger.info("Message: " + str(event))
for(record) in event['Records']:
default_version = get_latest_default_rule_set_version(record['Sns']['MessageAttributes']['managed_rule_group']['Value'])
if(MESSENGER == "Slack"):
payload = format_slack_message(record['Sns'], default_version)
elif(MESSENGER == "Teams"):
payload = format_teams_message(record['Sns'], default_version)
response = notify(HOOK_URL, payload)
print(response)

12 changes: 7 additions & 5 deletions lib/lambda/ManagedRuleGroupVersion/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { getManagedRuleGroupVersion } from "./services/waf";
import {ManagedRuleGroupVersionResponse} from "./types/index";

export const handler = async (
event: CdkCustomResourceEvent
event: CdkCustomResourceEvent,
): Promise<CdkCustomResourceResponse> => {
let ManagedVersionInfo: ManagedRuleGroupVersionResponse;
let ParamVersion: string | undefined;
let Latest: boolean | undefined;
let Latest: boolean;
let enforceUpdate: boolean;
console.log("Lambda is invoked with:" + JSON.stringify(event));

const response: CdkCustomResourceResponse = {
Expand All @@ -32,12 +33,13 @@ export const handler = async (
console.log(`🔎 Searching Managed Rule Version for: \n 🏬 ${event.ResourceProperties.VendorName} \n 🏷️ ${event.ResourceProperties.Name} \n 🌏 ${event.ResourceProperties.Scope}`);
if (event.RequestType === "Update") {
console.log("🔄 Update Event");
event.OldResourceProperties.Version === "" ? ParamVersion = undefined : ParamVersion = event.OldResourceProperties.Version;
event.OldResourceProperties.Version === "" || event.ResourceProperties.EnforceUpdate === "true" ? ParamVersion = undefined : ParamVersion = event.OldResourceProperties.Version;
}
try {
event.ResourceProperties.ManagedRuleGroupVersion === "" ? ParamVersion = undefined : ParamVersion = event.ResourceProperties.ManagedRuleGroupVersion;
event.ResourceProperties.Latest === undefined ? Latest = undefined : Latest = event.ResourceProperties.Latest;
ManagedVersionInfo = await getManagedRuleGroupVersion(event.ResourceProperties.VendorName, event.ResourceProperties.Name, event.ResourceProperties.Scope, ParamVersion, Latest);
event.ResourceProperties.Latest === "true" ? Latest = true : Latest = false;
event.ResourceProperties.EnforceUpdate === "false" ? enforceUpdate = false : enforceUpdate = true;
ManagedVersionInfo = await getManagedRuleGroupVersion(event.ResourceProperties.VendorName, event.ResourceProperties.Name, event.ResourceProperties.Scope, ParamVersion, Latest, enforceUpdate);
if(ManagedVersionInfo.State === "SUCCESS"){
response.Status = "SUCCESS";
response.Data = { Version: ManagedVersionInfo.Version, Result: ManagedVersionInfo.Description };
Expand Down
Loading

0 comments on commit 511fdaa

Please sign in to comment.