AWSTemplateFormatVersion: '2010-09-09'
Description: 'Grant cross-account access for DBAI to access RDS and CloudWatch (Development)'

Parameters:
  TrustedAccountId:
    Type: String
    Description: The AWS account ID of the application that needs access
    AllowedPattern: '[0-9]{12}'

  ExternalId:
    Type: String
    Description: External ID for enhanced security (provided by the application)
    MinLength: 12
    MaxLength: 1224
    AllowedPattern: '[A-Za-z0-9+=,.@\-_]+'

  EKSPodRoleName:
    Type: String
    Description: The name of the EKS pod role

  OrganizationId:
    Type: String
    Description: The organization ID identifier for the customer
    MinLength: 1

  SessionToken:
    Type: String
    Description: The JWT Session Token for the users dba.ai account
    NoEcho: true
    MinLength: 1

  SourceFunctionBucketName:
    Description: The name of the S3 bucket for the source functions
    AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$
    Default: cdb-plat-use1-prod-dbaai-prod-cf-templates
    Type: String

  SourceFunctionKeyPrefix:
    AllowedPattern: ^[0-9a-zA-Z-/]*$
    Default: lambda/
    Type: String

  DBABaseUrl:
    Type: String
    Description: The base URL for the DBA API
    Default: api.dba.ai

Resources:
  LambdaZipsBucket:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  CopyZipsFunction:
    Type: AWS::Lambda::Function
    Properties:
      Description: Copies objects from a source S3 bucket to a destination
      Handler: index.handler
      Runtime: python3.11
      Role: !GetAtt 'CopyZipsRole.Arn'
      Timeout: 240
      Code:
        ZipFile: |
          import json
          import logging
          import threading
          import boto3
          import cfnresponse


          def copy_objects(source_bucket, dest_bucket, prefix, objects):
              s3 = boto3.client('s3')
              for o in objects:
                  key = prefix + o
                  copy_source = {
                      'Bucket': source_bucket,
                      'Key': key
                  }
                  print(('copy_source: %s' % copy_source))
                  print(('dest_bucket = %s'%dest_bucket))
                  print(('key = %s' %key))
                  s3.copy_object(CopySource=copy_source, Bucket=dest_bucket,
                        Key=key)


          def delete_objects(bucket, prefix, objects):
              s3 = boto3.client('s3')
              objects = {'Objects': [{'Key': prefix + o} for o in objects]}
              s3.delete_objects(Bucket=bucket, Delete=objects)


          def timeout(event, context):
              logging.error('Execution is about to time out, sending failure response to CloudFormation')
              cfnresponse.send(event, context, cfnresponse.FAILED, {}, None)


          def handler(event, context):
              # make sure we send a failure to CloudFormation if the function
              # is going to timeout
              timer = threading.Timer((context.get_remaining_time_in_millis()
                        / 1000.00) - 0.5, timeout, args=[event, context])
              timer.start()

              print(('Received event: %s' % json.dumps(event)))
              status = cfnresponse.SUCCESS
              try:
                  source_bucket = event['ResourceProperties']['SourceBucket']
                  dest_bucket = event['ResourceProperties']['DestBucket']
                  prefix = event['ResourceProperties']['Prefix']
                  objects = event['ResourceProperties']['Objects']
                  if event['RequestType'] == 'Delete':
                      delete_objects(dest_bucket, prefix, objects)
                  else:
                      copy_objects(source_bucket, dest_bucket, prefix, objects)
              except Exception as e:
                  logging.error('Exception: %s' % e, exc_info=True)
                  status = cfnresponse.FAILED
              finally:
                  timer.cancel()
                  cfnresponse.send(event, context, status, {}, None)

  CopyZips:
    Type: Custom::CopyZips
    Properties:
      ServiceToken: !GetAtt 'CopyZipsFunction.Arn'
      DestBucket: !Ref 'LambdaZipsBucket'
      SourceBucket: !Ref 'SourceFunctionBucketName'
      Prefix: !Ref 'SourceFunctionKeyPrefix'
      Objects:
        - registration-lambda/bootstrap.zip

  CopyZipsRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Path: /
      Policies:
        - PolicyName: lambda-copier
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource:
                  - !Sub 'arn:aws:s3:::${SourceFunctionBucketName}/${SourceFunctionKeyPrefix}*'
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:DeleteObject
                Resource:
                  - !Sub 'arn:aws:s3:::${LambdaZipsBucket}/${SourceFunctionKeyPrefix}*'

  CrossAccountRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: dbaai-rds-monitoring-agent
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${TrustedAccountId}:role/${EKSPodRoleName}'
            Action: 'sts:AssumeRole'
            Condition:
              StringEquals:
                'sts:ExternalId': !Ref ExternalId
      Tags:
        - Key: Service
          Value: 'dba.ai'
        - Key: CreatedBy
          Value: 'CloudFormation'
        - Key: Purpose
          Value: 'RDS Monitoring'
        - Key: OrganizationID
          Value: !Ref OrganizationId

      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole'

      Policies:
        - PolicyName: RDSAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'rds:DownloadDBLogFilePortion'
                  - 'rds:Describe*'
                  - 'rds:List*'
                  - 'rds:Get*'
                Resource: '*'

        - PolicyName: OrganizationsAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'organizations:DescribeAccount'
                  - 'organizations:DescribeOrganization'
                  - 'organizations:ListAccounts'
                  - 'organizations:ListTagsForResource'
                  - 'organizations:ListAccountAliases'
                Resource: '*'

        - PolicyName: IAMAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'iam:ListAccountAliases'
                Resource: '*'

        - PolicyName: CloudWatchAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'cloudwatch:GetMetricData'
                  - 'cloudwatch:GetMetricStatistics'
                  - 'cloudwatch:ListMetrics'
                  - 'logs:GetLogEvents'
                  - 'logs:FilterLogEvents'
                  - 'logs:DescribeLogGroups'
                  - 'logs:DescribeLogStreams'
                Resource: '*'

        - PolicyName: SecretsManagerAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'secretsmanager:GetSecretValue'
                  - 'secretsmanager:DescribeSecret'
                Resource: '*'

        - PolicyName: LambdaAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'lambda:CreateFunction'
                  - 'lambda:GetFunction'
                  - 'lambda:UpdateFunctionCode'
                  - 'lambda:UpdateFunctionConfiguration'
                  - 'lambda:PublishVersion'
                  - 'lambda:CreateAlias'
                  - 'lambda:DeleteFunction'
                  - 'lambda:InvokeFunction'
                  - 'iam:PassRole'
                Resource: '*'

        - PolicyName: EC2SecurityGroupAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'ec2:DescribeSecurityGroups'
                  - 'ec2:DescribeSubnets'
                  - 'ec2:DescribeVpcs'
                Resource: '*'

  LambdaExRoleForDBAI:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: LambdaExRoleForDBAI
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Policies:
        - PolicyName: EC2NetworkAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'ec2:CreateNetworkInterface'
                  - 'ec2:DescribeNetworkInterfaces'
                  - 'ec2:DeleteNetworkInterface'
                Resource: '*'

  RegistrationFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  RegistrationFunction:
    DependsOn: CopyZips
    Type: AWS::Lambda::Function
    Properties:
      Description: The Rust Registration Handler for DBA
      Handler: bootstrap
      MemorySize: 128
      Runtime: provided.al2023
      Role: !GetAtt 'RegistrationFunctionRole.Arn'
      Architectures:
        - 'arm64'
      Environment:
        Variables:
          API_DOMAIN: !Ref DBABaseUrl
          API_PROTOCOL: https
      Timeout: 300
      Code:
        S3Bucket: !Ref 'LambdaZipsBucket'
        S3Key: !Sub '${SourceFunctionKeyPrefix}registration-lambda/bootstrap.zip'

  RegistrationResource:
    Type: Custom::Registration
    Properties:
      ServiceToken: !GetAtt RegistrationFunction.Arn
      external_id: !Ref ExternalId
      organization_id: !Ref OrganizationId
      session_token: !Ref SessionToken
      role_arn: !GetAtt CrossAccountRole.Arn

Outputs:
  RoleARN:
    Description: 'ARN of the created IAM role'
    Value: !GetAtt CrossAccountRole.Arn
