AWSTemplateFormatVersion: 2010-09-09
Description: FlowLog - Infrastructure at the spoke account for VPC Flow Log automation
Parameters:
  StackSetName:
    Type: String
    Description: Name of the StackSet
  OrgId:
    Type: String
    Description: The Amazon Organizations ID
    MinLength: 12
    MaxLength: 12
    AllowedPattern: '^[o][\-][a-z0-9]{10}$'
    ConstraintDescription: The Org Id must be a 12 character string starting with o- and followed by 10 lower case alphanumeric characters
  MasterAccount:
    Type: String
    Description: AWS Account ID where the Control Tower deployed
    AllowedPattern: '^[0-9]{12}$'
    MinLength: 12
    MaxLength: 12
  MasterRegion:
    Type: String
    Description: Region where the Control Tower deployed    
    Default: 'us-east-1'
  FlowLogDestinationAccount:
    Type: String
    Description: AWS Account ID where the VPC Flow Log bucket will be created
    AllowedPattern: '^[0-9]{12}$'
    MinLength: 12
    MaxLength: 12
  FlowLogBucketName:
    Type: String
    MinLength: 3
    MaxLength: 63
    AllowedPattern: '[a-zA-Z0-9-.]*'
    Description: Unique name for the S3 bucket in the destination account
  EventBusDestinationAccount:
    Type: String
    Description: AWS Account ID where the dedicated Event bus will be created
    AllowedPattern: '^[0-9]{12}$'
    MinLength: 12
    MaxLength: 12
  EventBusName:
    Type: String
    Description: Select name of the dedicated event bus that will be created at the Hub account
    Default: FlowLog-EventBus
  ComplianceFrequency:
    Type: Number
    Default: "24"
    Description: Frequency (in hours between 2 and 168, default is 24) to check Flow Logs compliance
    MinValue: 2
    MaxValue: 168
    ConstraintDescription: Compliance Frequency must be a number between 2 and 168, inclusive.

Mappings:
  LambdaVariable:
    Tag:
      KeyList: ["flowlog", "flow-log", "flow_log", "FlowLog", "Flow-Log", "Flow_Log"]
      Key: "flowlog, flow-log, flow_log, FlowLog, Flow-Log, Flow_Log"
      All: "all, full, enable, active, true, yes"
      Accept: "accept, pass, allow"
      Reject: "reject, deny, block"
    Role:
      Hub: FlowLogHubRole
      Spoke: FlowLogHubAssumeRole
  SourceCode:
    Key:
      Activator: "ct_flowlog_activator.zip"
      LifeCycle: "ct_flowlog_lifecycle.zip"
  S3perRegion:
    us-east-1:
      NAME: marketplace-sa-resources-ct-us-east-1
    us-east-2:
      NAME: marketplace-sa-resources-ct-us-east-2
    us-west-2:
      NAME: marketplace-sa-resources-ct-us-west-2
    eu-west-1:
      NAME: marketplace-sa-resources-ct-eu-west-1
    ap-southeast-2:
      NAME: marketplace-sa-resources-ct-ap-southeast-2

Conditions:
  CreateS3Bucket: !And 
    - !Equals
      - !Ref FlowLogDestinationAccount
      - !Ref AWS::AccountId
    - !Equals 
      - !Ref MasterRegion
      - !Ref AWS::Region

  OriginRegion: !Equals 
    - !Ref MasterRegion
    - !Ref AWS::Region

  CreateEventBus: !Equals
    - !Ref EventBusDestinationAccount
    - !Ref AWS::AccountId

  CreateEventBusOriginRegion: !And
    - !Equals
      - !Ref EventBusDestinationAccount
      - !Ref AWS::AccountId
    - !Equals 
      - !Ref MasterRegion
      - !Ref AWS::Region

  NonEventBus: !Not
    - !Equals
      - !Ref EventBusDestinationAccount
      - !Ref AWS::AccountId

Resources:
  S3Bucket:
    Condition: CreateS3Bucket
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Ref FlowLogBucketName
      BucketEncryption:
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault: 
              SSEAlgorithm: AES256
      VersioningConfiguration:
        Status: Enabled
      OwnershipControls: 
        Rules: 
          - ObjectOwnership: BucketOwnerEnforced
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W35
            reason: "Supress bucket access logging requirement"

  BucketPolicy:
    Condition: CreateS3Bucket
    Type: AWS::S3::BucketPolicy
    Properties:
      PolicyDocument:
        Id: S3BucketPolicy
        Version: 2012-10-17
        Statement:
          - Sid: AWSLogDeliveryWrite
            Effect: Allow
            Principal:
              Service: 'delivery.logs.amazonaws.com'
            Action:
              - 's3:PutObject'
            Resource:
              - !Join ['',['arn:aws:s3:::',!Ref S3Bucket, '/*']]
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control
          - Sid: AWSLogDeliveryAclCheck
            Effect: Allow
            Principal:
              Service: 'delivery.logs.amazonaws.com'
            Action:
              - 's3:GetBucketAcl'
            Resource:
              - !Join ['',['arn:aws:s3:::',!Ref S3Bucket]]
          - Sid: AllowSSLRequestsOnly
            Effect: Deny
            Principal: '*'
            Action: 's3:*'
            Resource:
              - !Join ['',['arn:aws:s3:::',!Ref S3Bucket]]
              - !Join ['',['arn:aws:s3:::',!Ref S3Bucket, /*]]
            Condition:
              Bool:
                aws:SecureTransport: false
      Bucket: !Ref S3Bucket

  FlowLogEventBus:
    Condition: CreateEventBus
    Type: AWS::Events::EventBus
    Properties:
      Name: !Ref EventBusName

  FlowLogEventBusPolicy:
    Condition: CreateEventBus
    Type: AWS::Events::EventBusPolicy
    Properties:
        Action: "events:PutEvents"
        Principal: "*"
        StatementId: "AllowSpokeAccountPutEventsToHubAccount"
        EventBusName: !Ref FlowLogEventBus
        Condition:
            Type: "StringEquals"
            Key: "aws:PrincipalOrgID"
            Value: !Ref OrgId

  FlowLogActivatorRole:
    Condition: CreateEventBusOriginRegion
    Type: AWS::IAM::Role
    Properties:
      RoleName: FlowLogActivatorRole
      Description: FlowLog - Role used by Lambda in Hub Account to enable VPC Flow Log
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
      - PolicyName: FlowLogActivator
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Resource: 
              - !Join ['', ['arn:aws:iam::', '*', ':role/', !FindInMap [LambdaVariable,Role, Spoke]]]
              - !Join ['', ['arn:aws:iam::', !Ref MasterAccount, ':role/', !FindInMap [LambdaVariable,Role, Hub]]]
            Condition:
              StringEquals:
                "sts:ExternalId": !Ref OrgId
          - Effect: Allow
            Action:
              - lambda:InvokeFunction
            Resource: !Sub 'arn:aws:lambda:*:${AWS::AccountId}:function:${AWS::AccountId}-FlowLogActivator'
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource:
              -  !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*'
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W28
            reason: "Explicit role name required for reference on other resources" 

  FlowLogActivator:
    Condition: CreateEventBus
    Type: "AWS::Lambda::Function"
    Properties:
      FunctionName: !Sub ${AWS::AccountId}-FlowLogActivator
      Description: FlowLog - Function to handle incoming events and activate VPC Flow Log in spoke account
      Handler: "ct_flowlog_activator.lambda_handler"
      Role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/FlowLogActivatorRole'
      Code:
        S3Bucket: !FindInMap [ S3perRegion, !Ref "AWS::Region", NAME ]
        S3Key: !Join ["/", [!FindInMap ["SourceCode", "Key", "Activator"]]]
      Runtime: "python3.7"
      MemorySize: 128
      Timeout: 300
      ReservedConcurrentExecutions: 500
      Environment:
        Variables:
            assume_role: !FindInMap [LambdaVariable,Role, Spoke]
            org_id: !Ref OrgId
            s3bucket: !Ref FlowLogBucketName
            master_account: !Ref MasterAccount
            master_role: !FindInMap [LambdaVariable,Role, Hub]
            stackset_name: !Ref StackSetName
            stackset_region: !Ref MasterRegion
            tag_keys: !FindInMap [LambdaVariable,Tag, Key]
            tag_all_values: !FindInMap [LambdaVariable,Tag, All]
            tag_accept_values: !FindInMap [LambdaVariable,Tag, Accept]
            tag_reject_values: !FindInMap [LambdaVariable,Tag, Reject]
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W58
            reason: "Supress false warning since the Role is set using !Sub"

  FlowLogTimerCheck:
    Condition: CreateEventBus
    Type: "AWS::Events::Rule"
    Properties:
      Name: FlowLogTag-TimerRule
      Description: FlowLog - Periodic check to trigger FlowLogActivator Lambda
      ScheduleExpression: !Sub "rate(${ComplianceFrequency} hours)"
      State: ENABLED
      Targets:
        - Arn: !GetAtt FlowLogActivator.Arn
          Id: TargetFunction

  FlowLogTimerCheckEventPermission:
    Condition: CreateEventBus
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref FlowLogActivator
      Principal: events.amazonaws.com
      Action: lambda:InvokeFunction
      SourceArn: !GetAtt FlowLogTimerCheck.Arn

  FlowLogTagHubRule:
    Condition: CreateEventBus
    DependsOn:
      - FlowLogEventBus
    Type: AWS::Events::Rule
    Properties:
      Name: FlowLogTag-HubRule
      Description: FlowLog - Trigger for create/update tag from spoke account to hub account via dedicated Event Bus
      EventBusName: !Ref EventBusName
      EventPattern:
        {
          "source": [
            "aws.tag"
          ],
          "detail-type": [
            "Tag Change on Resource"
          ],
          "detail": {
            "changed-tag-keys": !FindInMap [LambdaVariable, Tag, KeyList],
            "service": [
              "ec2"
            ],
            "resource-type": [
              "subnet",
              "vpc"
            ]
          }
        }
      State: ENABLED
      Targets:
        - Arn: !GetAtt FlowLogActivator.Arn
          Id: "TagCreateUpdateHubTrigger"

  FlowLogTagHubRulePermission:
    Condition: CreateEventBus
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref FlowLogActivator
      Principal: events.amazonaws.com
      Action: lambda:InvokeFunction
      SourceArn: !GetAtt FlowLogTagHubRule.Arn

  FlowLogTagLocalRule:
    Condition: CreateEventBus
    DependsOn:
      - FlowLogEventBus
    Type: AWS::Events::Rule
    Properties:
      Name: FlowLogTag-LocalRule
      Description: FlowLog - Trigger for create/update tag from local account via Default Event Bus
      EventPattern:
        {
          "account": [
            !Ref "AWS::AccountId"
          ],
          "source": [
            "aws.tag"
          ],
          "detail-type": [
            "Tag Change on Resource"
          ],
          "detail": {
            "changed-tag-keys": !FindInMap [LambdaVariable, Tag, KeyList],
            "service": [
              "ec2"
            ],
            "resource-type": [
              "subnet",
              "vpc"
            ]
          }
        }
      State: ENABLED
      Targets:
        - Arn: !GetAtt FlowLogActivator.Arn
          Id: "TagCreateUpdateLocalTrigger"

  FlowLogTagLocalRulePermission:
    Condition: CreateEventBus
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref FlowLogActivator
      Principal: events.amazonaws.com
      Action: lambda:InvokeFunction
      SourceArn: !GetAtt FlowLogTagLocalRule.Arn

  FlowLogHubAssumeRole:
    Type: AWS::IAM::Role
    Condition: OriginRegion
    Properties:
      RoleName: !FindInMap [LambdaVariable,Role, Spoke]
      Description: FlowLog - Role assumed by FlowLogActivator Lambda to access each linked/spoke account
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Join
                - ''
                - - 'arn:aws:iam::'
                  - !Ref EventBusDestinationAccount
                  - ':root'
            Action:
              - sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId: !Ref OrgId
      Path: "/"
      Policies:
        - PolicyName: VPCFlowLogEnablerPolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - ec2:DescribeFlowLogs
                  - ec2:DescribeVpcs
                  - ec2:DescribeSubnets
                  - ec2:CreateFlowLogs
                  - ec2:DeleteFlowLogs
                  - logs:CreateLogDelivery
                  - logs:DeleteLogDelivery
                Resource: '*'
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W11
            reason: "Describe and Create Flow Logs require resource types = *"
          - id: W28
            reason: "Explicit role name required for reference on other resources"

  FlowLogTagSpokeRule:
    Condition: NonEventBus
    Type: AWS::Events::Rule
    Properties:
      Name: FlowLogTag-SpokeRule
      Description: FlowLog - Trigger for create/update tag from spoke account via dedicated Event Bus
      EventPattern:
        {
          "account": [
            !Ref "AWS::AccountId"
          ],
          "source": [
            "aws.tag"
          ],
          "detail-type": [
            "Tag Change on Resource"
          ],
          "detail": {
            "changed-tag-keys": !FindInMap [LambdaVariable, Tag, KeyList],
            "service": [
              "ec2"
            ],
            "resource-type": [
              "subnet",
              "vpc"
            ]
          }
        }
      State: ENABLED
      Targets:
        - Arn: !Sub arn:aws:events:${AWS::Region}:${EventBusDestinationAccount}:event-bus/${EventBusName}
          Id: "TagCreateUpdateTrigger"
          RoleArn: !GetAtt FlowLogTagSpokeRuleDeliveryRole.Arn

  FlowLogTagSpokeRuleDeliveryRole:
    Condition: NonEventBus
    Type: AWS::IAM::Role
    Properties:
      Description: FlowLog - Role to send event from Spoke account to the Hub account event buses
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
                Service: events.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: VPCTagEventBusDeliveryRolePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - events:PutEvents
                Resource: !Sub arn:aws:events:${AWS::Region}:${EventBusDestinationAccount}:event-bus/${EventBusName}