# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

AWSTemplateFormatVersion: 2010-09-09
Description: Creates an SNS topic and Lambda function to enable SecurityHub in the Security account.
Parameters:
  SecurityAccountId:
    Type: String
    Description: Which account will be the SecurityHub Admin account?  Enter the AWS account ID. (This is generally the AWS Control Tower Audit account)
    AllowedPattern: '^[0-9]{12}$'
    ConstraintDescription: The Security Account ID must be a 12 character string.
    MinLength: 12
    MaxLength: 12
  OrganizationId:
    Type: String
    Description: AWS Organizations ID for the Control Tower. This is used to restrict permissions to least privilege.
    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
    MinLength: 12
    MaxLength: 12
  RegionFilter:
    Type: String
    Description: Should Security Hub be enabled for all Security Hub supported regions, or only Control Tower supported regions
    Default: ControlTower
    AllowedValues: 
      - SecurityHub
      - ControlTower
  OUFilter:
    Type: String
    Description: Should Security Hub be enabled for all accounts, or only accounts Control Tower Managed OUs?
    Default: All
    AllowedValues:
      - All
      - ControlTower
  S3SourceBucket:
    Type: String
    Description: S3 bucket containing SecurityHubEnabler Lambda deployment package
    Default: ""
  S3SourceKey:
    Type: String
    Description: S3 object key for SecurityHubEnabler Lambda deployment package
    Default: securityhub_enabler.zip
  ComplianceFrequency:
    Type: Number
    Description: Frequency (in days between 1 and 30, default is 7) to check organizational compliance
    Default: 7
    ConstraintDescription: Compliance Frequency must be a number between 1 and 30, inclusive.
    MinValue: 1
    MaxValue: 30
  RoleToAssume:
    Type: String
    Description: IAM role to be assumed in child accounts to enable SecurityHub. The default is AWSControlTowerExecution for a Control Tower environment.
    Default: 'AWSControlTowerExecution'
  AWSStandard:
    Type: String
    Description: Should Security Hub enable the AWS Foundational Security Best Practices v1.0.0 Security Standard?
    Default: "Yes"
    AllowedValues:
      - "Yes"
      - "No"
  CIS120Standard:
    Type: String
    Description: Should Security Hub enable the CIS AWS Foundations Benchmark v1.2.0 Security Standard?
    Default: "Yes"
    AllowedValues:
      - "Yes"
      - "No"
  CIS140Standard:
    Type: String
    Description: Should Security Hub enable the CIS AWS Foundations Benchmark v1.4.0 Security Standard?
    Default: "No"
    AllowedValues:
      - "Yes"
      - "No"
  PCIStandard:
    Type: String
    Description: Should Security Hub enable the PCI DSS v3.2.1 Security Standard?
    Default: "No"
    AllowedValues:
      - "Yes"
      - "No"
  NISTStandard:
    Type: String
    Description: Should Security Hub enable the NIST SP 800-53 Rev5 Security Standard?
    Default: "No"
    AllowedValues:
      - "Yes"
      - "No"

Conditions:
  ComplianceFrequencySingleDay: !Equals
    - !Ref 'ComplianceFrequency'
    - 1

Resources:
  SecurityHubEnablerRole: 
    Type: "AWS::IAM::Role"
    Properties: 
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - 
            Effect: "Allow"
            Principal: 
              Service: 
                - "lambda.amazonaws.com"
            Action: 
              - "sts:AssumeRole"
      Path: "/"
      Policies: 
      - PolicyName: SecurityHubEnablerPolicy
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Action:
            - organizations:ListAccounts
            - organizations:DescribeAccount
            - organizations:ListPoliciesForTarget
            - organizations:ListParents
            Resource: '*'
            Condition:
              StringEquals:
                "aws:PrincipalOrgId": !Ref OrganizationId
          - Effect: Allow
            Action:
            - sts:AssumeRole
            Resource: !Sub 'arn:aws:iam::*:role/${RoleToAssume}'
            Condition:
              StringEquals:
                "aws:PrincipalOrgId": !Ref OrganizationId
          - Effect: Allow
            Action:
            - sns:Publish
            Resource: !Ref SecurityHubEnablerTopic
          - Effect: Allow
            Action:
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
            Resource: 
              -  !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*'
          - Effect: Allow
            Action:
              - 'sts:AssumeRole'
            Resource: !Sub 'arn:aws:iam::*:role/${RoleToAssume}'
          - Effect: Allow
            Action:
              - 'CloudFormation:ListStackInstances'
            Resource: !Sub 'arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stackset/AWSControlTowerBP-BASELINE-CLOUDWATCH:*'
          - Effect: Allow
            Action:
              - 'iam:CreateServiceLinkedRole'
              - 'ec2:describeRegions'
              - 'securityhub:AcceptInvitation'
              - 'securityhub:AcceptAdministratorInvitation'
              - 'securityhub:BatchEnableStandards'
              - 'securityhub:BatchDisableStandards'
              - 'securityhub:CreateMembers'
              - 'securityhub:DisassociateFromAdministratorAccount'
              - 'securityhub:DisassociateMembers'
              - 'securityhub:DisableSecurityHub'
              - 'securityhub:DeleteMembers'
              - 'securityhub:EnableSecurityHub'
              - 'securityhub:GetEnabledStandards'
              - 'securityhub:GetFindings'
              - 'securityhub:GetMasterAccount'
              - 'securityhub:InviteMembers'
              - 'securityhub:ListInvitations'
              - 'securityhub:ListMembers'
            Resource: '*'
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W11
            reason: "Organizations doesn't have arns, so we have to use an asterisk in the policy"

  SecurityHubEnablerLambda: 
    Type: "AWS::Lambda::Function"
    DependsOn: 
      - SecurityHubEnablerRole
    Properties: 
      Handler: "securityhub_enabler.lambda_handler"
      Role: !Sub "arn:aws:iam::${AWS::AccountId}:role/${SecurityHubEnablerRole}"
      Code: 
        S3Bucket: !Ref S3SourceBucket
        S3Key: !Ref S3SourceKey
      Runtime: "python3.10"
      MemorySize: 256
      Timeout: 900
      # ###################################################################
      ReservedConcurrentExecutions: 2
      # ###################################################################
      Environment:
        Variables:
            ou_filter: !Ref OUFilter
            region_filter: !Ref RegionFilter
            assume_role: !Sub ${RoleToAssume}
            ct_admin_account: !Sub ${AWS::AccountId}
            sh_admin_account: !Sub ${SecurityAccountId}
            topic: !Ref SecurityHubEnablerTopic
            aws_standard: !Ref AWSStandard
            cis_standard: !Ref CIS120Standard
            cis_140_standard: !Ref CIS140Standard
            nist_standard: !Ref NISTStandard
            pci_standard: !Ref PCIStandard
            log_level: "ERROR"

  SecurityHubEnablerTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: SecurityHub_Enabler
      TopicName: SecurityHubEnablerTopic
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W47
            reason: "Not sensitive data, doesn't need encryption with kms"

  SecurityHubEnablerTopicLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt SecurityHubEnablerLambda.Arn
      Principal: sns.amazonaws.com
      SourceArn: !Ref SecurityHubEnablerTopic

  SecurityHubEnablerSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      Endpoint: !GetAtt SecurityHubEnablerLambda.Arn
      Protocol: lambda
      TopicArn: !Ref SecurityHubEnablerTopic

  ScheduledRule: 
    Type: AWS::Events::Rule
    Properties: 
      Description: "SecurityHubScheduledComplianceTrigger"
      ScheduleExpression: !If
        - ComplianceFrequencySingleDay
        - !Sub "rate(${ComplianceFrequency} day)"
        - !Sub "rate(${ComplianceFrequency} days)"
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt SecurityHubEnablerLambda.Arn
          Id: "DailyInvite"

  LifeCycleRule: 
    Type: AWS::Events::Rule
    Properties: 
      Description: "SecurityHubLifeCycleTrigger"
      EventPattern:
        source: 
          - "aws.controltower"
        detail-type:
          - "AWS Service Event via CloudTrail"
        detail:
          eventName:
            - "CreateManagedAccount"
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt SecurityHubEnablerLambda.Arn
          Id: "DailyInvite"

  PermissionForSchedEventToInvokeLambda: 
    Type: AWS::Lambda::Permission
    Properties: 
      FunctionName: !GetAtt SecurityHubEnablerLambda.Arn
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt ScheduledRule.Arn

  PermissionForCTEventToInvokeLambda: 
    Type: AWS::Lambda::Permission
    Properties: 
      FunctionName: !GetAtt SecurityHubEnablerLambda.Arn
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt LifeCycleRule.Arn

  FirstRun:
    Type: Custom::SecurityHubEnablerLambdaFirstRun
    DependsOn:
      - SecurityHubEnablerTopic
      - SecurityHubEnablerRole
      - SecurityHubEnablerTopicLambdaPermission
      - SecurityHubEnablerSubscription
    Properties:
      ServiceToken: !GetAtt SecurityHubEnablerLambda.Arn