---
AWSTemplateFormatVersion: "2010-09-09"
Description: "CloudFormation Stack Drift Alarm - Detection & Notification **WARNING** This template creates AWS Config - ConfigRule, Amazon EventBridge - EventBusPolicy and Rule, Amazon SNS - Topic, TopicPolicy and Subscription, AWS KMS - Key and KeyPolicy, and AWS IAM - Role and Policy. You will be billed for the AWS resources used if you create a stack from this template."
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Management/Standalone Account specific Parameters (Skip this section for tenant accounts)"
        Parameters:
          - "SnsSubscriptionProtocol"
          - "SnsSubscriptionEndpoint"
          - "CustomerManagedKey"
          - "PrincipalOrgID"
      - Label:
          default: "Generic Parameters"
        Parameters:
          - "MaximumExecutionFrequency"
          - "ManagementAccountId"
    ParameterLabels:
      SnsSubscriptionProtocol:
        default: "SNS Subscription Protocol - Required"
      SnsSubscriptionEndpoint:
        default: "SNS Subscription Endpoint - Required"
      CustomerManagedKey:
        default: "KMS Customer Managed Key Id - Optional"
      PrincipalOrgID:
        default: "Principal Org ID - Optional"
      MaximumExecutionFrequency:
        default: "Maximum Execution Frequency - Required"
      ManagementAccountId:
        default: "Management Account Id - Optional"
Parameters:
  SnsSubscriptionProtocol:
    Description: "Provide the Protocol to be used to notify drift in CloudFormation stacks, Eg: email, sms, sqs, lambda, ..."
    Type: "String"
    AllowedValues: ["http","https","email","email-json","sms","sqs","application","lambda","firehose",""]
  SnsSubscriptionEndpoint:
    Description: "Provide the Endpoint to be used to notify drift in CloudFormation stacks, Eg: email-id if the chosen protocol is 'email'"
    Type: "String"
  CustomerManagedKey:
    Description: "Provide an existing Customer-Managed KMS Key Id, Leave it blank to automatically create and use it"
    Type: "String"
  PrincipalOrgID:
    Description: "Provide the Principal Org ID in case of multi-account setup (Management-Tenant) with AWS Organizations configured, Leave it blank for standalone account"
    Type: "String"
  MaximumExecutionFrequency:
    Description: "The maximum frequency with which drift in CloudFormation stacks need to be evaluated (default - One_Hour)"
    Type: "String"
    Default: "One_Hour"
    AllowedValues: ["One_Hour","Three_Hours","Six_Hours","Twelve_Hours","TwentyFour_Hours"]
  ManagementAccountId:
    Description: "Provide the Management AccountId in case of multi-account (Management-Tenant) setup, Leave it blank for standalone account"
    Type: "String"
Conditions:
  IsManagementAccount: !Equals [!Ref "ManagementAccountId", !Ref "AWS::AccountId"]
  IsStandaloneAccount: !Equals [!Ref "ManagementAccountId", ""]
  IsPrimaryAccount: !Or
    - !Condition "IsManagementAccount"
    - !Condition "IsStandaloneAccount"
  IsSecondaryAccount: !Not [!Condition "IsPrimaryAccount"]
  IfCustomerManagedKeyProvided: !Not [!Equals [!Ref "CustomerManagedKey", ""]]
  CreateCMK: !And
    - !Not [!Condition "IfCustomerManagedKeyProvided"]
    - !Condition "IsPrimaryAccount"
  IfPrincipalOrgIDProvided: !Not [!Equals [!Ref "PrincipalOrgID", ""]]
  IsAccessToExternalEventBusAllowed: !And
    - !Condition "IsManagementAccount"
    - !Condition "IfPrincipalOrgIDProvided"
Resources:
  CreateKmsCMK:
    Condition: "CreateCMK"
    Type: "AWS::KMS::Key"
    Properties:
      Description: "KMS Customer-Managed Key for CloudFormation Drift Alarm"
      Enabled: "true"
      EnableKeyRotation: "true"
      KeySpec: "SYMMETRIC_DEFAULT"
      KeyUsage: "ENCRYPT_DECRYPT"
      MultiRegion: "false"
      PendingWindowInDays: 30
      KeyPolicy:
        Version: "2012-10-17"
        Id: "__default_statement_ID"
        Statement:
          - Sid: "Enable IAM User Permissions"
            Effect: "Allow"
            Principal:
              AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
            Action:
              - "kms:*"
            Resource: "*"
          - Sid: "Allow EventBridge to use this key"
            Effect: "Allow"
            Principal:
              Service: "events.amazonaws.com"
            Action:
              - "kms:Decrypt"
              - "kms:GenerateDataKey"
            Resource: "*"
      Tags:
        - Key: "CreatedFor"
          Value: "CfnDriftAlarm"
  IamRoleForConfig:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "CfnDdaConfigRole-${AWS::Region}-${AWS::StackName}"
      Description: "IAM role for AWS Config to access CloudFormation drift detection"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "config.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Policies:
        - PolicyName: !Sub "CfnDdaConfigPolicy-${AWS::Region}-${AWS::StackName}"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "cloudformation:DetectStackResourceDrift"
                  - "cloudformation:DetectStackDrift"
                  - "cloudformation:DescribeStackDriftDetectionStatus"
                Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:*"
      Tags:
        - Key: "CreatedFor"
          Value: "CfnDriftAlarm"
  IamRoleForEventBridge:
    Condition: "IsSecondaryAccount"
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "CfnDdaEventRole-${AWS::Region}-${AWS::StackName}"
      Description: "IAM role for Amazon EventBridge to access Management account's eventbus (in case of tenant account)"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:  "events.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Policies:
        - PolicyName: !Sub "CfnDdaEventPolicy-${AWS::Region}-${AWS::StackName}"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "events:PutEvents"
                Resource: !Sub "arn:aws:events:${AWS::Region}:${ManagementAccountId}:event-bus/default"
      Tags:
        - Key: "CreatedFor"
          Value: "CfnDriftAlarm"
  EventBusPolicyToReceiveExternalEvents:
    Condition: "IsAccessToExternalEventBusAllowed"
    Type: "AWS::Events::EventBusPolicy"
    Properties:
      StatementId: "ReceiveEventsFromExternalAccounts"
      Statement:
        Effect: "Allow"
        Principal: "*"
        Action:
          - "events:PutEvents"
        Resource: !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default"
        Condition:
          StringEquals:
            aws:PrincipalOrgID: !Ref "PrincipalOrgID"
  SnsTopicToNotifyCfnDriftAlarm:
    Condition: "IsPrimaryAccount"
    Type: "AWS::SNS::Topic"
    Properties:
      TopicName: "CfnDriftAlarmSnsTopic"
      KmsMasterKeyId: !If
        - "IfCustomerManagedKeyProvided"
        - !Ref "CustomerManagedKey"
        - !Ref "CreateKmsCMK"
      Tags:
        - Key: "CreatedFor"
          Value: "CfnDriftAlarm"
  SnsTopicPolicy:
    Condition: "IsPrimaryAccount"
    Type: "AWS::SNS::TopicPolicy"
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: "__default_statement_ID"
            Effect: "Allow"
            Principal:
              AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
            Action:
              - "SNS:GetTopicAttributes"
              - "SNS:SetTopicAttributes"
              - "SNS:AddPermission"
              - "SNS:RemovePermission"
              - "SNS:DeleteTopic"
              - "SNS:Subscribe"
              - "SNS:ListSubscriptionsByTopic"
              - "SNS:Publish"
              - "SNS:Receive"
            Resource: !Ref "SnsTopicToNotifyCfnDriftAlarm"
            Condition:
              StringEquals:
                AWS:SourceOwner: !Ref "AWS::AccountId"
          - Sid: "TrustEventBridgeToPublishEventsToSnsTopic"
            Effect: "Allow"
            Principal:
              Service: "events.amazonaws.com"
            Action:
              - "sns:Publish"
            Resource: !Ref "SnsTopicToNotifyCfnDriftAlarm"
      Topics:
        - !Ref "SnsTopicToNotifyCfnDriftAlarm"
  SnsTopicSubscription:
    Condition: "IsPrimaryAccount"
    Type: "AWS::SNS::Subscription"
    Properties:
      Endpoint: !Ref "SnsSubscriptionEndpoint"
      Protocol: !Ref "SnsSubscriptionProtocol"
      TopicArn: !Ref "SnsTopicToNotifyCfnDriftAlarm"
  ConfigRuleToDetectCfnDrift:
    Type: "AWS::Config::ConfigRule"
    Properties:
      ConfigRuleName: "CfnDriftAlarmConfigRule"
      Description: "AWS Config rule to detect drift in CloudFormation Stack"
      Scope:
        ComplianceResourceTypes:
          - "AWS::CloudFormation::Stack"
      InputParameters:
        cloudformationRoleArn: !GetAtt "IamRoleForConfig.Arn"
      Source:
        Owner: "AWS"
        SourceIdentifier: "CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK"
      MaximumExecutionFrequency: !Ref "MaximumExecutionFrequency"
  EventbridgeRuleToDetectComplianceChange:
    Type: "AWS::Events::Rule"
    Properties:
      Name: "CfnDriftAlarmDetectConfigRuleComplianceChangeEvent"
      Description: "Amazon EventBridge rule to detect changes in Config Rule compliance status and notify"
      State: "ENABLED"
      EventPattern:
        detail-type:
            - "Config Rules Compliance Change"
        source:
            - "aws.config"
        detail:
          messageType:
            - "ComplianceChangeNotification"
          configRuleName:
            - "CfnDriftAlarmConfigRule"
          newEvaluationResult:
            complianceType:
              - "NON_COMPLIANT"
      Targets: !If
        - "IsPrimaryAccount"
        - - Arn:
              Ref: "SnsTopicToNotifyCfnDriftAlarm"
            Id: "TargetId-1"
            InputTransformer:
              InputPathsMap:
                resourceId: "$.detail.resourceId"
                account: "$.account"
                old-compliance-type: "$.detail.oldEvaluationResult.complianceType"
                new-compliance-type: "$.detail.newEvaluationResult.complianceType"
                configRuleName: "$.detail.configRuleName"
                notificationCreationTime: "$.detail.notificationCreationTime"
              InputTemplate: |
                "Hi Team,"
                ""
                "*** This is an automated notification to report a change in CloudFormation Stack compliance type. Please do not reply. ***"
                ""
                "Please find the event details below:"
                ""
                "Account: <account>"
                "CloudFormation Stack: <resourceId>"
                "Old-Compliance-Type: <old-compliance-type>"
                "New-Compliance-Type: <new-compliance-type>"
                "AWS Config rule: <configRuleName>"
                "CreationTime: <notificationCreationTime>"
                ""
                "Kindly take necessary action to remediate the stack if there is a drift."
        - - Arn: !Sub "arn:aws:events:${AWS::Region}:${ManagementAccountId}:event-bus/default"
            Id: "TargetId-1"
            RoleArn: !GetAtt "IamRoleForEventBridge.Arn"
Outputs:
  KmsCMK:
    Condition: "CreateCMK"
    Description: "Customer-Managed Key Id"
    Value: !Ref "CreateKmsCMK"
  RoleForConfig:
    Description: "Role name of the IAM role for AWS Config to access CloudFormation drift detection"
    Value: !Ref "IamRoleForConfig"
  RoleForEventBridge:
    Condition: "IsSecondaryAccount"
    Description: "Role name of the IAM role for Amazon EventBridge to access Management account's eventbus (in case of tenant account)"
    Value: !Ref "IamRoleForEventBridge"
  PolicyToReceiveExternalEvents:
    Condition: "IsAccessToExternalEventBusAllowed"
    Description: "Policy ID of the EventBusPolicy to receive events from external account's eventbus"
    Value: !Ref "EventBusPolicyToReceiveExternalEvents"
  TopicToNotifyCfnDriftAlarm:
    Condition: "IsPrimaryAccount"
    Description: "ARN of the SNS Topic to notify Drift Alarm"
    Value: !Ref "SnsTopicToNotifyCfnDriftAlarm"
  RuleToDetectCfnDrift:
    Description: "Rule name of the AWS Config rule to detect CloudFormation Stack Drift"
    Value: !Ref "ConfigRuleToDetectCfnDrift"
  RuleToDetectComplianceChange:
    Description: "Rule name of the Event rule to detect drift compliance change"
    Value: !Ref "EventbridgeRuleToDetectComplianceChange"