AWSTemplateFormatVersion: 2010-09-09
Description: This CloudFormation template to create S3 Event notification, queuing the messages
Parameters:
  pS3BucketName:
    Type: String
    Description: S3 Bucket Name to enable event notification
    Default: 'vinjak-archive'
  pTargetedAccountId:
    Type: String
    Description: AWS Account Id that will consume the logs
    Default: ''
    AllowedPattern: '(^$|^[0-9]{12}$)'
Conditions:
  cTargetedAccountId: !Not [!Equals [!Ref pTargetedAccountId, '']]
Resources:
  rLambdaFunction:
    Type: AWS::Lambda::Function
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W58
            reason: Access to CloudWatch Logs is granted to the Lambda execution role.
          - id: W89
            reason: This function do not communicate with VPC resources.
          - id: W92
            reason: Lambda does not need reserved concurrent executions.
      checkov:
        skip:
          - id: CKV_AWS_115
            comment: Lambda does not need reserved concurrent executions.
          - id: CKV_AWS_116
            comment: DLQ not needed. This Lambda function is triggered only by CloudFormation events.
          - id: CKV_AWS_117
            comment: This function do not communicate with VPC resources.
          - id: CKV_AWS_173
            comment: Environment variables are not sensitive.
    Properties:
      Description: !Sub Lambda function to enable s3 event notification to SQS for an existing S3 bucket ${pS3BucketName}
      Runtime: python3.12
      Timeout: 60
      Handler: index.lambda_handler
      Role: !GetAtt rLambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import json
          import boto3
          import logging
          import cfnresponse
          from botocore.exceptions import ClientError
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)
          S3 = boto3.client('s3')
          def create_event_notification(bucket_name, notif_type, notif_arn, events):
              notif_type = notif_type.lower()
              notif_key = {'sqs': ['QueueConfigurations', 'QueueArn', 'SQSQueue'],
                           'sns': ['TopicConfigurations', 'TopicArn', 'SNSQueue']}
              status = False
              try:
                  if notif_type in notif_key:
                      notif_config={
                      notif_key[notif_type][0] : [{
                      'Id': notif_key[notif_type][2],
                      notif_key[notif_type][1]: notif_arn,
                      'Events': events
                      }]
                      }
                      S3.put_bucket_notification_configuration(Bucket=bucket_name, NotificationConfiguration=notif_config)
                      status = True
                  else:
                      logger.warn(f'Notification type {notif_type} not supported')
              except ClientError as exe:
                  logger.info(f'Exception: {exe}')
              return status
          def delete_event_notification(bucket_name):
              try:
                  S3.put_bucket_notification_configuration(
                      Bucket=bucket_name,
                      NotificationConfiguration={}
                  )
              except ClientError as exe:
                  logger.info(f'Exception: {exe}')
                  return False
              return True
          def lambda_handler(event, context):
              logger.info(f'Event: {event}')
              response_data = {}
              bucket = event['ResourceProperties']['S3BucketName']
              queue = event['ResourceProperties']['QueueArn']
              events = ['s3:ObjectCreated:*']
              logger.info(f'Bucket: {bucket}, Queue: {queue}')
              cu_events = ['Create', 'Update']
              del_events = ['Delete']
              request_type = event['RequestType']
              try:
                  if request_type in cu_events:
                      result = create_event_notification(bucket, 'sqs', queue, events)
                      response_data = {'Status': 'SUCCESS', 'Output': result}
                      logger.info(f'{request_type} completed: {response_data}')
                  elif request_type in del_events:
                      result = delete_event_notification(bucket)
                      response_data = {'Status': 'SUCCESS', 'Output': result}
                      logger.info(f'{request_type} completed: {response_data}')
                  else:
                      logger.info(f'Request type {request_type} not supported')
              except Exception as exe:
                  logger.info(f'Exception: {exe}')
                  response_data = {'Status': 'FAILED', 'Output': str(exe)}
              cfnresponse.send(event, context, response_data['Status'], response_data)

  rLambdaExecutionRole:
    Type: AWS::IAM::Role
    Metadata:
      cdk_nag:
        rules_to_suppress:
          - id: AwsSolutions-IAM5
            reason: These permissions are required for lambda to create log group/stream(s)
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: s3-event-notification-lambda-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutBucketNotification
                  - s3:GetBucketNotification
                Resource:
                  - !Sub arn:${AWS::Partition}:s3:::${pS3BucketName}
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:*

  rS3BucketNotification:
    Type: Custom::S3BucketNotification
    Properties:
      ServiceToken: !GetAtt rLambdaFunction.Arn
      S3BucketName: !Ref pS3BucketName
      QueueArn: !GetAtt rSQSQueue.Arn

  rSQSDeadLetterQueue:
    Type: AWS::SQS::Queue
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      KmsMasterKeyId: alias/aws/sqs
      VisibilityTimeout: 30
      SqsManagedSseEnabled: true
      MessageRetentionPeriod: 1209600

  rSQSQueue:
    Type: AWS::SQS::Queue
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      KmsMasterKeyId: alias/aws/sqs
      VisibilityTimeout: 60
      MessageRetentionPeriod: 345600
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt rSQSDeadLetterQueue.Arn
        maxReceiveCount: 5

  rSQSQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref rSQSQueue
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - s3.amazonaws.com
            Action:
              - sqs:SendMessage
            Resource: !GetAtt rSQSQueue.Arn
            Condition:
              ArnEquals:
                aws:SourceArn: !Sub arn:${AWS::Partition}:s3:::${pS3BucketName}
              Bool: 
                "aws:SecureTransport": "false"

  rCrossAccountIAMRole:
    Type: AWS::IAM::Role
    Metadata:
      cdk_nag:
        rules_to_suppress:
          - id: AwsSolutions-IAM5
            reason: These permissions are required to list objects in the bucket.
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: !If
                - cTargetedAccountId
                - !Ref pTargetedAccountId
                - !Ref AWS::AccountId
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: sqs-permissions
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sqs:ReceiveMessage
                  - sqs:DeleteMessage
                  - sqs:GetQueueAttributes
                Resource:
                  - !GetAtt rSQSQueue.Arn
        - PolicyName: s3-permissions
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:ListBucket
                Resource:
                  - !Sub arn:${AWS::Partition}:s3:::${pS3BucketName}
                  - !Sub arn:${AWS::Partition}:s3:::${pS3BucketName}/*
Outputs:
  oCrossAccountIAMRoleName:
    Description: Cross Account IAM Role Name
    Value: !Ref rCrossAccountIAMRole
  oCrossAccountIAMRoleArn:
    Description: Cross Account IAM Role Arn
    Value: !GetAtt rCrossAccountIAMRole.Arn
  oSQSQueueName:
    Description: SQS Queue Name
    Value: !GetAtt rSQSQueue.QueueName
  oSQSQueueUrl:
    Description: SQS Queue Url
    Value: !Ref rSQSQueue
  oSQSQueueArn:
    Description: SQS Queue Arn
    Value: !GetAtt rSQSQueue.Arn