# @format

Description: >
  Deploy AWS Config Rule "IAM_USER_UNUSED_CREDENTIALS_CHECK" and apply remediation to the rule.
  Create AWS Systems Manager Automation Document.
  The document retrieves the information of the IAM user
  and sends a notification email to AWS Systems Manager Automation.

Parameters:
  #=====1. Config Rule=====#
  ConfigRuleName:
    Type: String
    Default: IAM_USER_UNUSED_CREDENTIALS_CHECK
    Description: The name that you assign to the AWS Config rule.
    MinLength: "1"
    ConstraintDescription: This parameter is required.
  MaximumExecutionFrequency:
    Type: String
    Default: TwentyFour_Hours
    Description:
      The frequency that you want AWS Config to run evaluations for the
      rule.
    MinLength: "1"
    ConstraintDescription: This parameter is required.
    AllowedValues:
      - One_Hour
      - Three_Hours
      - Six_Hours
      - Twelve_Hours
      - TwentyFour_Hours
  maxCredentialUsageAge:
    Type: String
    Default: "90"
    Description:
      Maximum number of days a credential cannot be used. The default value
      is 90 days.
    MinLength: "1"
    ConstraintDescription: This parameter is required.

  #=====2. SSM Automation Document=====#
  AutomationDocName:
    Type: String
    Default: CustomConfigRemediation-SendMailToUnusedIAMUser
    Description: The name that you assign to the AWS System Managers Automation Document.
  SenderEmail:
    Type: String
    Default: cloudengineer@gmail.com
    Description: >
      The email address that you want to send notification mail. This specifies an identity for using within SES.
      When you verify an email address, SES sends an email to the address.
  SenderName:
    Type: String
    Default: AWS 운영자
    Description: The name of email sender.

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Config Rule
        Parameters:
          - ConfigRuleName
          - MaximumExecutionFrequency
          - maxCredentialUsageAge
      - Label:
          default: SSM Automation Document
        Parameters:
          - AutomationDocName
      - Label:
          default: Email address
        Parameters:
          - SenderEmail

Conditions:
  maxCredentialUsageAge:
    Fn::Not:
      - Fn::Equals:
          - ""
          - Ref: maxCredentialUsageAge

Resources:
  #=====1. IAM Role=====#
  AutomationExecutionRole:
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ssm.amazonaws.com
        Version: "2012-10-17"
      Policies:
        - PolicyName: !Sub "${AWS::StackName}Policy"
          PolicyDocument:
            Statement:
              - Action:
                  - iam:ListUsers
                  - iam:ListUserTags
                  - ses:SendEmail
                  - ses:SendRawEmail
                Effect: Allow
                Resource: "*"
            Version: "2012-10-17"

      RoleName: !Sub "${AWS::StackName}Role"
    Type: AWS::IAM::Role

  #=====2. Config Rule=====#
  AWSConfigRule:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName:
        Ref: ConfigRuleName
      Description:
        Checks whether your AWS Identity and Access Management (IAM) users
        have passwords or active access keys that have not been used within the specified
        number of days you provided.
      InputParameters:
        maxCredentialUsageAge:
          Fn::If:
            - maxCredentialUsageAge
            - Ref: maxCredentialUsageAge
            - Ref: AWS::NoValue
      Scope: {}
      Source:
        Owner: AWS
        SourceIdentifier: IAM_USER_UNUSED_CREDENTIALS_CHECK
      MaximumExecutionFrequency:
        Ref: MaximumExecutionFrequency
  AWSConfigRemediation:
    Type: AWS::Config::RemediationConfiguration
    Properties:
      Automatic: true
      ConfigRuleName: !Ref ConfigRuleName
      MaximumAutomaticAttempts: 3
      RetryAttemptSeconds: 60
      Parameters:
        AutomationAssumeRole:
          StaticValue:
            Values:
              - !GetAtt AutomationExecutionRole.Arn
        IAMUserId:
          ResourceValue:
            Value: "RESOURCE_ID"
        SenderEmail:
          StaticValue:
            Values:
              - !Ref SenderEmail
        SenderName:
          StaticValue:
            Values:
              - !Ref SenderName
      TargetType: SSM_DOCUMENT
      TargetId: !Ref SSMAutomationDoc

  #=====3. SSM Automation Document=====#
  SSMAutomationDoc:
    Type: AWS::SSM::Document
    Properties:
      DocumentFormat: YAML
      DocumentType: Automation
      Name: !Ref AutomationDocName
      Content:
        description: |-
          ### Document name - CustomConfigRemediation-SendMailToUnusedIAMUser

          ## What does this document do?
          This document sends mail to AWS Identity and Access Management (IAM) users who have passwords or active access keys that have not been used within the specified number of days you provided.

          ## Input Parameters
          * AutomationAssumeRole: (Required) The Amazon Resource Name (ARN) of the AWS Identity and Access Management (IAM) role that allows Systems Manager Automation to perform the actions on your behalf.
          * IAMUserId: (Required) The ID of the IAM user you want to delete.
          * SenderEmail: (Required) The email address to send notificaiton mail. This email address must be verified on AWS SES.
          * SenderName: (Required) The name of email sender.

          ## Output Parameters
          * None
        schemaVersion: "0.3"
        assumeRole: "{{ AutomationAssumeRole }}"
        parameters:
          AutomationAssumeRole:
            type: "AWS::IAM::Role::Arn"
            description: The Amazon Resource Name (ARN) of the AWS Identity and Access Management (IAM) role that allows Systems Manager Automation to perform the actions on your behalf.
          IAMUserId:
            type: String
            description: The ID of the IAM user you want to know.
          SenderEmail:
            type: String
            description: The email address to send notificaiton mail. This email address must be verified on AWS SES.
          SenderName:
            type: String
            description: The name of email sender.
        mainSteps:
          - name: GetUserInfo
            action: "aws:executeScript"
            inputs:
              Runtime: python3.7
              Handler: handler
              Script: |-
                from time import sleep
                import boto3

                THROTTLE_PERIOD = 0.05


                def get_username(iam_client, iam_user_id):
                  paginator = iam_client.get_paginator("list_users")
                  page_iterator = paginator.paginate()
                  for page in page_iterator:
                    for user in page["Users"]:
                      if user["UserId"] == iam_user_id:
                        return user["UserName"]
                    sleep(THROTTLE_PERIOD)


                def get_user_email_tag(iam_client, iam_username):
                    tags = iam_client.list_user_tags(UserName=iam_username)
                    email_tag = list(filter(lambda tag: tag['Key'].lower() == 'email', tags["Tags"]))
                    return email_tag


                def handler(event, context):
                  iam_client = boto3.client("iam")
                  iam_user_id = event["IAMUserId"]
                  
                  iam_username = get_username(iam_client, iam_user_id)

                  if iam_username is None:
                    error_message = f"AWS IAM USER ID, {iam_user_id} DOES NOT EXIST."
                    raise Exception(error_message)
                  
                  email_tag = get_user_email_tag(iam_client, iam_username)
                  
                  if len(email_tag) == 1:
                    return {"UserName":iam_username, "UserEmail": email_tag[0]['Value']}
                  else:
                    error_message = f"AWS IAM User {iam_username} DOES NOT HAVE AN EMAIL-RELATED TAGS."
                    raise Exception(error_message)

              InputPayload:
                IAMUserId: "{{ IAMUserId }}"
            outputs:
              - Name: UserName
                Selector: $.Payload.UserName
                Type: String
              - Name: UserEmail
                Selector: $.Payload.UserEmail
                Type: String
            description: Gathers the user name amd email of the IAM user you specify in the IAMUserId parameter.
          - name: SendEmail
            action: "aws:executeScript"
            inputs:
              Runtime: python3.7
              Handler: handler
              Script: |-
                import json
                import boto3
                import base64
                from botocore.exceptions import ClientError

                def sendEmail(sender_email, sender_name, user_email, user_name):
                  CHARSET = "utf-8"
                  sendername_bytes = sender_name.encode(CHARSET)
                  sendername_b64 = base64.b64encode(sendername_bytes)
                  sendername_b64_str = str(sendername_b64)
                  SENDER_NAME = f"=?{CHARSET}?B?{sendername_b64_str[2:-1]}?="
                  SENDER = f"{SENDER_NAME} <{sender_email}>"
                  RECIPIENT = user_email
                  AWS_REGION = "ap-northeast-2"
                  SUBJECT = "AWS IAM 조치 바랍니다."
                  BODY_TEXT = (f"AWS IAM 유저 {user_name}에 90일 동안 사용되지 않은 암호 또는 활성 액세스 키가 있습니다. 조치 바랍니다.\n"
                              "문의 사항이 있는 경우 회신 부탁드립니다.")
                  BODY_HTML = f"""<html>
                  <head></head>
                  <body>
                    <p>AWS IAM 유저 {user_name}에 90일 동안 사용되지 않은 암호 또는 활성 액세스 키가 있습니다. 조치 바랍니다.<br/>
                    문의 사항이 있는 경우 회신 부탁드립니다.</p>
                  </body>
                  </html>"""
                  
                  client = boto3.client('ses',region_name=AWS_REGION)
                  
                  try:
                    response = client.send_email(
                      Destination={
                        'ToAddresses': [
                          RECIPIENT,
                        ],
                      },
                      Message={
                        'Body': {
                          'Html': {
                            'Charset': CHARSET,
                            'Data': BODY_HTML,
                          },
                          'Text': {
                            'Charset': CHARSET,
                            'Data': BODY_TEXT,
                          },
                        },
                        'Subject': {
                          'Charset': CHARSET,
                          'Data': SUBJECT,
                        },
                      },
                      Source=SENDER
                    )
                  except ClientError as e:
                    print(e.response['Error']['Message'])
                    raise e
                  else:
                    print("Email sent! Message ID:"),
                    print(json.dumps(response, indent=2))
                  

                def handler(event, context):
                  sender_email = event["SenderEmail"]
                  sender_name = event["SenderName"]
                  user_email = event["UserEmail"]
                  user_name = event["UserName"]
                  sendEmail(sender_email, sender_name, user_email, user_name)
              InputPayload:
                UserName: "{{ GetUserInfo.UserName }}"
                UserEmail: "{{ GetUserInfo.UserEmail }}"
                SenderName: "{{ SenderName }}"
                SenderEmail: "{{ SenderEmail }}"
