Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Delivery Channel Configuration
        Parameters:
          - ConfigStorageBucketName
          - DeliveryChannelName
          - Frequency
      - Label:
          default: Tag Evaluator Configuration
        Parameters:
          - ResourceTypes
          - TagName
          - EmailAddress

Parameters:
  Frequency:
    Type: String
    Default: 24hours
    Description: The frequency with which AWS Config delivers configuration snapshots.
    AllowedValues:
      - 1hour
      - 3hours
      - 6hours
      - 12hours
      - 24hours

  ConfigStorageBucketName:
    Type: String
    Description: Name For Config Storage Bucket
  EmailAddress:
    Type: String
    Description: email address for notification
    Default: test@test.com
  TagName:
    Type: String
    Description: Tag to evaluate
    Default: Service
  ResourceTypes:
    Type: CommaDelimitedList
    Description: A list of valid AWS resource types to include in this recording group. EX AWS::EC2::Instance,AWS::EC2::EIP,AWS::CloudTrail::Trail
    Default:
      AWS::EC2::EIP,AWS::S3::Bucket
      # - AWS::AutoScaling::AutoScalingGroup
      # - AWS::DynamoDB::Table
      # - AWS::RDS::DBInstance
      # - AWS::RDS::DBSnapshot
      # - AWS::EC2::Instance
      # - AWS::CodeCommit::Repository
      # - AWS::EC2::Volume
      # - AWS::EC2::EIP
      # - AWS::CodeBuild::Project
      # - AWS::ElasticLoadBalancing::LoadBalancer
      # - AWS::ElasticLoadBalancingV2::LoadBalancer
      # - AWS::S3::Bucket
      # - AWS::SNS::Topic
      # - AWS::SQS::Queue
      # - AWS::Elasticsearch::Domain

Mappings:
  Settings:
    FrequencyMap:
      1hour: One_Hour
      3hours: Three_Hours
      6hours: Six_Hours
      12hours: Twelve_Hours
      24hours: TwentyFour_Hours
Resources:
  ConfigBucket:
    DeletionPolicy: Retain
    Type: AWS::S3::Bucket
    Properties:
      Tags:
        - Key: "Service"
          Value: "devops"
      BucketName: !Sub
        - ${Bucketname}-${AWS::Region}
        - { Bucketname: !Ref ConfigStorageBucketName }
  ConfigBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref ConfigBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: AWSConfigBucketPermissionsCheck
            Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action: s3:GetBucketAcl
            Resource:
              - !Sub "arn:${AWS::Partition}:s3:::${ConfigBucket}"
          - Sid: AWSConfigBucketDelivery
            Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action: s3:PutObject
            Resource:
              - !Sub "arn:${AWS::Partition}:s3:::${ConfigBucket}/AWSLogs/${AWS::AccountId}/*"
  ConfigRecorderRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWS_ConfigRole"
  ConfigRecorder:
    Type: AWS::Config::ConfigurationRecorder
    DependsOn:
      - ConfigBucketPolicy
    Properties:
      RoleARN: !GetAtt ConfigRecorderRole.Arn
      RecordingGroup:
        AllSupported: True
        IncludeGlobalResourceTypes: True
  ConfigDeliveryChannel:
    Type: AWS::Config::DeliveryChannel
    DependsOn:
      - ConfigBucketPolicy
    Properties:
      ConfigSnapshotDeliveryProperties:
        DeliveryFrequency: !FindInMap
          - Settings
          - FrequencyMap
          - !Ref Frequency
      S3BucketName: !Ref ConfigBucket

  EvaluateExcuteRole:
    Type: AWS::IAM::Role
    Properties:
      Tags:
        - Key: "Service"
          Value: "devops"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "config:PutEvaluations"
                Resource: "*"
              - Effect: Allow
                Action:
                  - logs:*
                Resource: arn:aws:logs:*:*:*
  EvaluateResourceLambda:
    Type: "AWS::Lambda::Function"
    DependsOn: EvaluateExcuteRole
    Properties:
      Tags:
        - Key: "Service"
          Value: "devops"
      Handler: "index.handler"
      Role:
        Fn::GetAtt:
          - "EvaluateExcuteRole"
          - "Arn"
      Runtime: "nodejs12.x"
      Timeout: 600
      Code:
        ZipFile: |
          'use strict';
          let aws = require('aws-sdk');
          let config = new aws.ConfigService();
          // Check whether the the resource has been deleted. If it has, then the evaluation is unnecessary.
          function isApplicable(configurationItem, event) {
              const status = configurationItem.configurationItemStatus;
              const eventLeftScope = event.eventLeftScope;
              return ('OK' === status || 'ResourceDiscovered' === status) && false === eventLeftScope;
          }
          function evaluateCompliance(configurationItem, ruleParameters) {
              console.log(configurationItem);
              console.log(configurationItem.tags);
              const tags = configurationItem.tags;
              let tagsToFind = []
              for (var property in ruleParameters) {
                  tagsToFind.push(ruleParameters[property])
              }
              console.log(tagsToFind);
              let found = true;
              tagsToFind.every(function (tag, index) {
                  found = (tags[tag] != undefined)
                  return (found);
              })
              return (found) ? 'COMPLIANT' : 'NON_COMPLIANT'
          }
          exports.handler = async (event, context) => {
              // console.log('Received event:' + JSON.stringify(event, null, 4));
              const invokingEvent = JSON.parse(event.invokingEvent);
              const ruleParameters = "ruleParameters" in event ? JSON.parse(event.ruleParameters) : {};
              const configurationItem = invokingEvent.configurationItem
              let compliance = 'NOT_APPLICABLE';
              if (isApplicable(configurationItem, event)) {
                  compliance = evaluateCompliance(configurationItem, ruleParameters);
              }
              const evaluation = {
                  ComplianceResourceType: configurationItem.resourceType,
                  ComplianceResourceId: configurationItem.resourceId,
                  ComplianceType: compliance,
                  OrderingTimestamp: configurationItem.configurationItemCaptureTime
              };
              const putEvaluationsRequest = {
                  Evaluations: [evaluation],
                  ResultToken: event.resultToken
              };
              await config.putEvaluations(putEvaluationsRequest).promise();
          }
  AWSConfigInvokeLambdaPermission:
    Type: AWS::Lambda::Permission
    DependsOn: EvaluateResourceLambda
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !GetAtt EvaluateResourceLambda.Arn
      Principal: "config.amazonaws.com"

  AWSConfigCustomRequiredTag:
    Type: AWS::Config::ConfigRule
    DependsOn: AWSConfigInvokeLambdaPermission
    Properties:
      InputParameters:
        tag1Key: !Ref TagName
      Scope:
        ComplianceResourceTypes: !Ref ResourceTypes

      Source:
        Owner: CUSTOM_LAMBDA
        SourceDetails:
          - EventSource: aws.config
            MessageType: ConfigurationItemChangeNotification
        SourceIdentifier: !GetAtt EvaluateResourceLambda.Arn

  CustomRequiredTagFailedEvent:
    Type: AWS::Events::Rule
    Properties:
      Description: CustomRequiredTagFailedEvent
      EventPattern:
        source:
          - aws.config
        detail-type:
          - Config Rules Compliance Change
        detail:
          messageType:
            - ComplianceChangeNotification
          configRuleName:
            - Ref: AWSConfigCustomRequiredTag
          newEvaluationResult:
            complianceType:
              - NON_COMPLIANT
      Targets:
        - Arn:
            Ref: AlarmTopic
          Id: AWSConfigCustomRequiredTag-failed
          InputTransformer:
            InputTemplate: |
              "AWS Config 규칙 <rule>이 <time>에 <awsAccountId> 어카운트의 <awsRegion> 리전에서 <resourceType> 타입의 <resourceId> 리소스의 규칙 준수가 <compliance> 상태인 것을 발견하였습니다. 자세한 사항은 다음 주소를 통해 콘솔에서 확인하십시오. https://console.aws.amazon.com/config/home?region=<awsRegion>#/timeline/<resourceType>/<resourceId>/configuration"
            InputPathsMap:
              awsRegion: "$.detail.awsRegion"
              resourceId: "$.detail.resourceId"
              awsAccountId: "$.detail.awsAccountId"
              compliance: "$.detail.newEvaluationResult.complianceType"
              rule: "$.detail.configRuleName"
              time: "$.detail.newEvaluationResult.resultRecordedTime"
              resourceType: "$.detail.resourceType"
  AlarmTopic:
    Type: AWS::SNS::Topic
    Properties:
      Tags:
        - Key: "Service"
          Value: "devops"
      Subscription:
        - Endpoint: !Ref EmailAddress
          Protocol: email
  AlarmTopicpolicy:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Id: AlarmTopicpolicy
        Version: "2012-10-17"
        Statement:
          - Sid: state1
            Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sns:Publish
            Resource: "*"
      Topics:
        - !Ref AlarmTopic
