Parameters: AbuseDLName: Description: "The name of the Domain List created to hold the rules" Type: String Default: 'AutoUpdating-Abuse-Hostfile' Resources: AbuseDL: Type: AWS::Route53Resolver::FirewallDomainList Properties: Name: !Sub ${AbuseDLName} Tags: - Key: "ProjectName" Value: "R53-Abuse-AutoUpate" - Key: "downloaded-from" Value: "https://urlhaus.abuse.ch/downloads/hostfile/" - Key: "description" Value: "abuse.ch URLhaus Host file" - Key: "terms-of-use" Value: "https://urlhaus.abuse.ch/api/" S3Bucket: Type: AWS::S3::Bucket Properties: PublicAccessBlockConfiguration: BlockPublicAcls: true IgnorePublicAcls: true BlockPublicPolicy: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: 'AES256' S3BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref S3Bucket PolicyDocument: Statement: - Action: - 's3:GetObject' - 's3:PutObject' - 's3:DeleteObject' Effect: Allow Resource: !Sub "arn:aws:s3:::${S3Bucket}/autoupdating-dnsfirewall-domains.txt" Principal: AWS: !GetAtt LambdaExecutionRole.Arn 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: R53Resolver PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'route53resolver:*' Resource: - !GetAtt AbuseDL.Arn Tags: - Key: "ProjectName" Value: "R53-Abuse-AutoUpate" LambdaLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub "/aws/lambda/${LambdaFunction}" DeletionPolicy: Retain ScheduledRule: Type: AWS::Events::Rule Properties: Description: "AbuseDLDailyTrigger" 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 DependsOn: S3BucketPolicy 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 abuse.ch rule list and update the associated Domain List Tags: - Key: "ProjectName" Value: "R53-Abuse-AutoUpate" Code: ZipFile: !Sub | const AWS = require("aws-sdk"); const response = require("cfn-response"); const https = require("https"); const s3 = new AWS.S3(); const route53resolver = new AWS.Route53Resolver(); const hostfileUrl = "https://urlhaus.abuse.ch/downloads/hostfile/"; async function importList (s3Obj){ let params = { DomainFileUrl: s3Obj, FirewallDomainListId: "${AbuseDL}", Operation: "REPLACE" }; let res = await route53resolver.importFirewallDomains(params, function(err, data) { if (err) console.log(err, err.stack); // an error occurred else console.log("Updating the domain list using " + s3Obj); }).promise(); return res; } async function writeToS3(domains){ let params = { Bucket: "${S3Bucket}", Key: "autoupdating-dnsfirewall-domains.txt", Body: domains.join('\n'), ContentType: "text/plain" }; let res = await s3.putObject(params, function(err, data){ if (err) console.log(err, err.stack); // an error occurred }).promise(); if (res) { return "https://" + params.Bucket + ".s3.amazonaws.com/" + params.Key; } } async function deleteFromS3(){ let params = { Bucket: "${S3Bucket}", Key: "autoupdating-dnsfirewall-domains.txt" }; let res = await s3.deleteObject(params, function(err, data){ if (err) console.log(err, err.stack); // an error occurred }).promise(); if (res) { console.log("Deleted the import file"); } } async function getDomains (){ let listOfDomains = []; console.log("Fetching the list of domains from " + hostfileUrl); return new Promise((resolve, reject) => { const url = hostfileUrl; let dataString = ''; let post_req = https.request(url, (res) => { res.setEncoding("utf8"); res.on('data', chunk => { dataString += chunk; }); res.on('end', () => { //console.log(dataString); listOfDomains = dataString .split(/\r?\n/) .filter((line) => line.match(/^\d+/)) .map((line)=> {return line.replace(/127.0.0.1\t/,'')}) .filter((line) => line.match(/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/)); console.log("Fetched " + listOfDomains.length + " Domains"); resolve(listOfDomains); }); res.on('error', (err) => { reject(err); }); }); post_req.write("test"); post_req.end(); }); } exports.handler = async (event, context) => { if (event.RequestType == "Delete") { await deleteFromS3(); await response.send(event, context, "SUCCESS"); return; } let domains = await getDomains(); if (domains.length) { let s3Obj = await writeToS3(domains, event.key); if (s3Obj) { let res = await importList(s3Obj); if (res) { if (event.ResponseURL) await response.send(event, context, response.SUCCESS); console.log("Done"); } else { console.log("ERROR - Import failed"); if (event.ResponseURL) await response.send(event, context, response.FAILED); } } else { console.log("ERROR - Unable to write domain file to S3"); if (event.ResponseURL) await response.send(event, context, response.FAILED); } } else { console.log("ERROR - Unable to fetch domain list"); if (event.ResponseURL) await response.send(event, context, response.FAILED); } return; };