{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Parameters": {
        "RFAPIToken": {
            "Type": "String",
            "Description": "Insert Recorded Future API token",
            "NoEcho": true
        },
        "Frequency": {
            "Description": "Insert the number of hours between executions - when the solution downloads a new Risk List (min 24 - max 170)",
            "Default": "24",
            "MinValue": "24",
            "MaxValue": "170",
            "Type": "Number"
        },
        "RiskLists": {
            "Type": "String",
            "Description": "Insert a comma-delimited list of Recorded Future Risk Lists (contact Recorded Future for availible lists)",
            "Default": "ip_risklist_gt_65,c2_communicating_scf_ips"
        }
    },
    "Metadata": {
        "AWS::CloudFormation::Interface": {
            "ParameterGroups": [
                {
                    "Label": {
                        "default": "Recorded Future Information"
                    },
                    "Parameters": [
                        "RFAPIToken",
                        "RiskLists"
                    ]
                },
                {
                    "Label": {
                        "default": "Advanced Settings"
                    },
                    "Parameters": [
                        "Frequency"
                    ]
                }
            ],
            "ParameterLabels": {
                "RFAPIToken": {
                    "default": "Recorded Future API Token"
                },
                "RiskLists": {
                    "default": "Risk Lists"
                },
                "Frequency": {
                    "default": "Update Schedule"
                }
            }
        }
    },
    "Conditions": {
        "Singular": {
            "Fn::Equals": [
                {
                    "Ref": "Frequency"
                },
                "1"
            ]
        }
    },
    "Resources": {
        "ThreatFeedBucket": {
            "Type": "AWS::S3::Bucket",
            "DeletionPolicy": "Delete",
            "Properties": {
                "AccessControl": "Private",
                "PublicAccessBlockConfiguration": {
                    "BlockPublicAcls": true,
                    "BlockPublicPolicy": true,
                    "IgnorePublicAcls": true,
                    "RestrictPublicBuckets": true
                }
            }
        },
        "RFAPITokenStorage": {
            "Type": "AWS::SSM::Parameter",
            "Properties": {
                "Type": "String",
                "Value": {
                    "Ref": "RFAPIToken"
                },
                "Description": "Storage for Recorded Future API Token"
            }
        },
        "UpdateThreatFeedRole": {
            "Type": "AWS::IAM::Role",
            "DependsOn": [
                "ThreatFeedBucket"
            ],
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": [
                                    "lambda.amazonaws.com"
                                ]
                            },
                            "Action": [
                                "sts:AssumeRole"
                            ]
                        }
                    ]
                },
                "Path": "/",
                "RoleName": {
                    "Fn::Join": [
                        "",
                        [
                            {
                                "Ref": "AWS::StackName"
                            },
                            "CLCRole-",
                            {
                                "Ref": "AWS::Region"
                            }
                        ]
                    ]
                },
                "Policies": [
                    {
                        "PolicyName": {
                            "Fn::Join": [
                                "",
                                [
                                    {
                                        "Ref": "AWS::StackName"
                                    },
                                    "CLCLogPolicy"
                                ]
                            ]
                        },
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "logs:CreateLogGroup",
                                        "logs:CreateLogStream",
                                        "logs:PutLogEvents"
                                    ],
                                    "Resource": {
                                        "Fn::Join": [
                                            "",
                                            [
                                                "arn:aws:logs:",
                                                {
                                                    "Ref": "AWS::Region"
                                                },
                                                ":",
                                                {
                                                    "Ref": "AWS::AccountId"
                                                },
                                                ":log-group:/aws/lambda/*"
                                            ]
                                        ]
                                    }
                                }
                            ]
                        }
                    },
                    {
                        "PolicyName": {
                            "Fn::Join": [
                                "",
                                [
                                    {
                                        "Ref": "AWS::StackName"
                                    },
                                    "CLCS3Access"
                                ]
                            ]
                        },
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "s3:GetObject",
                                        "s3:PutObject",
                                        "s3:DeleteObject"
                                    ],
                                    "Resource": {
                                        "Fn::Join": [
                                            "",
                                            [
                                                {
                                                    "Fn::GetAtt": [
                                                        "ThreatFeedBucket",
                                                        "Arn"
                                                    ]
                                                },
                                                "/*"
                                            ]
                                        ]
                                    }
                                }
                            ]
                        }
                    },
                    {
                        "PolicyName": {
                            "Fn::Join": [
                                "",
                                [
                                    {
                                        "Ref": "AWS::StackName"
                                    },
                                    "GuardDutyAccess"
                                ]
                            ]
                        },
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "guardduty:ListDetectors",
                                        "guardduty:CreateThreatIntelSet",
                                        "guardduty:GetThreatIntelSet",
                                        "guardduty:ListThreatIntelSets",
                                        "guardduty:UpdateThreatIntelSet",
                                        "guardduty:DeleteThreatIntelSet",
                                        "guardduty:TagResource"
                                    ],
                                    "Resource": {
                                        "Fn::Join": [
                                            "",
                                            [
                                                "arn:aws:guardduty:",
                                                {
                                                    "Ref": "AWS::Region"
                                                },
                                                ":",
                                                {
                                                    "Ref": "AWS::AccountId"
                                                },
                                                ":detector/*"
                                            ]
                                        ]
                                    }
                                },
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "iam:PutRolePolicy",
                                        "iam:DeleteRolePolicy"
                                    ],
                                    "Resource": {
                                        "Fn::Join": [
                                            "",
                                            [
                                                "arn:aws:iam::",
                                                {
                                                    "Ref": "AWS::AccountId"
                                                },
                                                ":role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty"
                                            ]
                                        ]
                                    }
                                }
                            ]
                        }
                    },
                    {
                        "PolicyName": {
                            "Fn::Join": [
                                "",
                                [
                                    {
                                        "Ref": "AWS::StackName"
                                    },
                                    "SSMParametersAccess"
                                ]
                            ]
                        },
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Effect": "Allow",
                                    "Action": [
                                        "ssm:GetParameter"
                                    ],
                                    "Resource": {
                                        "Fn::Join": [
                                            "",
                                            [
                                                "arn:aws:ssm:",
                                                {
                                                    "Ref": "AWS::Region"
                                                },
                                                ":",
                                                {
                                                    "Ref": "AWS::AccountId"
                                                },
                                                ":parameter/",
                                                {
                                                    "Ref": "RFAPITokenStorage"
                                                }
                                            ]
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "UpdateThreatFeedFunction": {
            "Type": "AWS::Lambda::Function",
            "DependsOn": [
                "ThreatFeedBucket",
                "UpdateThreatFeedRole",
                "RFAPITokenStorage"
            ],
            "Properties": {
                "Code": {
                    "ZipFile": {
                        "Fn::Join": [
                            "\n",
                            [
                                "import os, json, boto3",
                                "from urllib import request, error",
                                "",
                                "def send_response(event, context):",
                                "  '''",
                                "  Sends response back to Event.",
                                "  Only executed when Lambda Function is called by custom CloudFormation Rule",
                                "  '''",
                                "",
                                "  body = {'Status': 'SUCCESS', 'StackId': event['StackId']}",
                                "  body['PhysicalResourceId'] = context.log_stream_name",
                                "  body['RequestId'] = event['RequestId']",
                                "  body['LogicalResourceId'] = event['LogicalResourceId']",
                                "  body = json.dumps(body)",
                                "  headers = {'content-type': '', 'content-length': str(len(body))}",
                                "  try:",
                                "    req = request.Request(event['ResponseURL'],",
                                "                   data=body.encode('utf-8'),",
                                "                   headers=headers, method='PUT')",
                                "    request.urlopen(req)",
                                "",
                                "  except Exception as e:",
                                "    print(e)",
                                "",
                                "def upload_to_s3(endpoint, token, client, bucket):",
                                "  '''",
                                "  Given an endpoint name, download the file from Recorded Future and",
                                "  upload to S3 bucket.",
                                "  '''",
                                "  file = endpoint + '.csv'",
                                "  url = f'https://api.recordedfuture.com/v2/fusion/files/public/GuardDuty/{file}'",
                                "  headers = {'X-RFToken': token, 'X-RF-User-Agent': 'AWS-GuardDuty-v1'}",
                                "  req = request.Request(url, None,  headers)",
                                "  fname = '/tmp/' + file",
                                "  try:",
                                "    with request.urlopen(req) as res:",
                                "      open(fname, 'wb').write(res.read())",
                                "      res = client.upload_file(fname, bucket, file)",
                                "  except error.HTTPError as e:",
                                "    print(f'Unable to fetch Risk List {endpoint}. ')",
                                "    print(e)",
                                "",
                                "",
                                "def update_threat_sets(client, detector, bucket, endpoints, set_ids):",
                                "  '''Updates existing threat intel sets'''",
                                "",
                                "  for set_id in set_ids:",
                                "    set_ = client.get_threat_intel_set(DetectorId=detector, ThreatIntelSetId=set_id)",
                                "    set_name = set_['Name']",
                                "    #if threat intel feed already a threat intel set",
                                "    if set_name in endpoints:",
                                "      endpoints.remove(set_name)",
                                "      try:",
                                "        client.update_threat_intel_set(DetectorId=detector,",
                                "                                       ThreatIntelSetId=set_id,",
                                "                                       Location=f's3://{bucket}/{set_name}.csv',",
                                "                                       Activate=True",
                                "                                      )",
                                "      except Exception as e:",
                                "        print(f'Unable to update Threat Intel Set {set_name}.')",
                                "        print(e)",
                                "",
                                "    elif set_['Tags'].get('Source') == 'RecordedFuture':",
                                "      client.delete_threat_intel_set(DetectorId=detector, ThreatIntelSetId=set_id)",
                                "",
                                "  # for all threat feeds not currently in Guard Duty",
                                "  for e in endpoints:",
                                "    try:",
                                "      client.create_threat_intel_set(DetectorId=detector,",
                                "                                     Name=e,",
                                "                                     Format='TXT',",
                                "                                     Location=f's3://{bucket}/{e}.csv',",
                                "                                     Activate=True,",
                                "                                     Tags={'Source':'RecordedFuture'}",
                                "                                    )",
                                "    except Exception as e:",
                                "      print(f'Unable to create Threat Intel Set {set_name}.')",
                                "      print(e)",
                                "",
                                "def lambda_handler(event, context):",
                                "  '''Main script.'''",
                                "  try:",
                                "    #pull environment variables",
                                "    bucket = os.environ['destBucket']",
                                "    token_name = os.environ['tokenName']",
                                "    token = boto3.client('ssm').get_parameter(Name=token_name)['Parameter']['Value']",
                                "    endpoints = [e.strip() for e in os.environ['endpoints'].split(',')]",
                                "",
                                "    s3_client = boto3.client('s3')",
                                "    gd_client = boto3.client('guardduty')",
                                "    detector = gd_client.list_detectors()['DetectorIds'][0]",
                                "",
                                "    set_ids = gd_client.list_threat_intel_sets(DetectorId=detector)['ThreatIntelSetIds']",
                                "",
                                "    for endpoint in endpoints:",
                                "      upload_to_s3(endpoint, token, s3_client, bucket)",
                                "",
                                "    update_threat_sets(gd_client, detector, bucket, endpoints, set_ids)",
                                "  except Exception as e:",
                                "    print(e)",
                                "  finally:",
                                "    #if custom event invocation",
                                "    if event.get('ResourceProperties'):",
                                "      send_response(event, context)"
                            ]
                        ]
                    }
                },
                "MemorySize": "512",
                "Handler": "index.lambda_handler",
                "Role": {
                    "Fn::GetAtt": [
                        "UpdateThreatFeedRole",
                        "Arn"
                    ]
                },
                "Timeout": "450",
                "Runtime": "python3.12",
                "Description": "Function to pull IP Risk Lists, upload them to an S3 bucket, and then create/update Threat Intel Sets in AWS GD",
                "Environment": {
                    "Variables": {
                        "destBucket": {
                            "Ref": "ThreatFeedBucket"
                        },
                        "tokenName": {
                            "Ref": "RFAPITokenStorage"
                        },
                        "endpoints": {
                            "Ref": "RiskLists"
                        }
                    }
                }
            }
        },
        "UpdateThreatFeedEvent": {
            "Type": "Custom::UpdateThreatFeedEvent",
            "DependsOn": [
                "UpdateThreatFeedFunction",
                "ThreatFeedBucket"
            ],
            "Properties": {
                "ServiceToken": {
                    "Fn::GetAtt": [
                        "UpdateThreatFeedFunction",
                        "Arn"
                    ]
                },
                "firstPass": "true"
            }
        },
        "UpdateThreatFeedScheduler": {
            "Type": "AWS::Events::Rule",
            "DependsOn": [
                "UpdateThreatFeedFunction",
                "ThreatFeedBucket"
            ],
            "Description": "GuardDuty threat feed auto update scheduler",
            "Properties": {
                "ScheduleExpression": {
                    "Fn::Join": [
                        "",
                        [
                            "rate(",
                            {
                                "Ref": "Frequency"
                            },
                            {
                                "Fn::If": [
                                    "Singular",
                                    " hour)",
                                    " hours)"
                                ]
                            }
                        ]
                    ]
                },
                "Targets": [
                    {
                        "Arn": {
                            "Fn::GetAtt": [
                                "UpdateThreatFeedFunction",
                                "Arn"
                            ]
                        },
                        "Id": "UpdateThreatFeedFunction"
                    }
                ]
            }
        },
        "UpdateThreatFeedSchedulerInvokePermissions": {
            "Type": "AWS::Lambda::Permission",
            "DependsOn": [
                "UpdateThreatFeedFunction",
                "UpdateThreatFeedScheduler"
            ],
            "Properties": {
                "FunctionName": {
                    "Ref": "UpdateThreatFeedFunction"
                },
                "Action": "lambda:InvokeFunction",
                "Principal": "events.amazonaws.com",
                "SourceArn": {
                    "Fn::GetAtt": [
                        "UpdateThreatFeedScheduler",
                        "Arn"
                    ]
                }
            }
        }
    }
}