Parameters: RuleGroupName: Type: String Default: 'AutoUpdating-TorProjectIPList' RuleGroupAction: Type: String Description: "Used to define the action to take on a matching rule if found" Default : 'drop' AllowedValues: - 'alert' - 'drop' Resources: StatefulRulegroup: Type: 'AWS::NetworkFirewall::RuleGroup' Properties: RuleGroupName: !Sub "${AWS::StackName}-${RuleGroupName}-${RuleGroupAction}" Type: STATEFUL RuleGroup: RulesSource: RulesString: '#This will be updated via the Lambda function' Capacity: 4000 Description: >- Used to track a list of Emerging IP Threats from https://check.torproject.org/exit-addresses Tags: - Key: "ProjectName" Value: "TorProjectIPFiltering" LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: LambdaLogs PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*' - PolicyName: NetworkFirewall PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'network-firewall:*' Resource: - !GetAtt StatefulRulegroup.RuleGroupArn Tags: - Key: "ProjectName" Value: "TorProjectIPFiltering" LambdaLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub "/aws/lambda/${LambdaFunction}" DeletionPolicy: Retain ScheduledRule: Type: AWS::Events::Rule Properties: Description: "TorProjectDailyTrigger" ScheduleExpression: "cron(0 0 * * ? *)" State: "ENABLED" Targets: - Arn: Fn::GetAtt: - "LambdaFunction" - "Arn" Id: "TargetFunctionV1" PermissionForEventsToInvokeLambda: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref "LambdaFunction" Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" SourceArn: Fn::GetAtt: - "ScheduledRule" - "Arn" LambdaInvoke: Type: AWS::CloudFormation::CustomResource Version: "1.0" Properties: ServiceToken: !GetAtt LambdaFunction.Arn LambdaFunction: Type: AWS::Lambda::Function Properties: Role: !GetAtt LambdaExecutionRole.Arn Runtime: nodejs16.x Handler: index.handler Timeout: 60 Description: Used to fetch data from the Emerging Threats IP list and update the associated RuleGroup Tags: - Key: "ProjectName" Value: "TorProjectIPFiltering" Code: ZipFile: !Sub | var AWS = require("aws-sdk"); var response = require('cfn-response'); var https = require("https"); var listOfIps = []; const TorProjectUrl = "https://check.torproject.org/exit-addresses"; const networkfirewall = new AWS.NetworkFirewall(); function fetchIPs() { console.log("Fetching the list of IP addresses..."); return new Promise((resolve, reject) => { let dataString = ''; let post_req = https.request(TorProjectUrl, (res) => { res.setEncoding("utf8"); res.on('data', chunk => { dataString += chunk; }); res.on('end', () => { listOfIps = dataString .split(/\r?\n/) .filter((line) => line.match(/ExitAddress /)) .map(s => s.split(" ")[1]); console.log("Fetched " + listOfIps.length + " IP addresses..."); resolve(); }); res.on('error', (err) => { reject(err); }); }); post_req.end(); }); } let updateRules = async function (ruleGroup) { let params = ruleGroup; delete params.Capacity; params.RuleGroupName = params.RuleGroupResponse.RuleGroupName; params.Type = params.RuleGroupResponse.Type; delete params.RuleGroupResponse; console.log("Updating rules..."); let res = await networkfirewall.updateRuleGroup(params).promise(); if (res) { console.log("Updated '" + params.RuleGroupName + "'."); } else { console.log("Error updating the rules for '" + params.RuleGroupName + "'..."); } return; }; let createRules = async function (ruleGroup, type) { if (listOfIps.length == 0) { await fetchIPs(); } else { console.log("Using recently fetched list of " + listOfIps.length + " IP addresses..."); } let rulesString = "# Last updated: " + new Date().toUTCString() + "\n"; rulesString += "# Using a list of " + listOfIps.length + " IP addresses\n"; listOfIps.forEach((ip, index) => { rulesString += type + ' ip ' + ip + ' any -> any any (msg:"' + type + ' emerging threats traffic from ' + ip + '"; rev:1; sid:55' + index + ';)\n'; rulesString += type + ' ip any any -> ' + ip + ' any (msg:"' + type + ' emerging threats traffic to ' + ip + '"; rev:1; sid:66' + index + ';)\n'; }); ruleGroup.RuleGroup.RulesSource.RulesString = rulesString; await updateRules(ruleGroup); return; }; exports.handler = async (event, context) => { if (event.RequestType == "Delete") { await response.send(event, context, "SUCCESS"); return; } var params = {Type: "STATEFUL", RuleGroupArn: '${StatefulRulegroup.RuleGroupArn}'}; console.log("Searching for Rule Groups..."); let res = await networkfirewall.describeRuleGroup(params).promise(); if (res.RuleGroupResponse) { console.log("Found Rule Group..."); await createRules(res,"${RuleGroupAction}"); if (event.ResponseURL) await response.send(event, context, response.SUCCESS); } else { console.log("ERROR: No matching Rule Group found..."); if (event.ResponseURL) await response.send(event, context, response.FAILED); } return; };