

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# Lambda 함수를 사용하여 이벤트에 응답
<a name="event-lambda-response"></a>

이 절차에서는를 사용하여 Amazon EventBridge에서 수신 AWS Lambda 대기하고, Amazon Simple Notification Service(SNS)를 사용하여 알림을 생성하고 AWS Security Hub CSPM, 결과를 게시하여 관리자와 보안 팀에 가시성을 제공하는 방법을 보여줍니다.<a name="lambda-setup"></a>

**Lambda 함수 및 IAM 역할을 설정하려면**

1. 먼저 AWS Identity and Access Management (IAM) 역할을 구성하고 Lambda 함수에 필요한 권한을 정의합니다. 이 보안 모범 사례를 통해 유연하게 함수를 호출할 권한이 있는 사용자를 지정하고 해당 사용자에게 부여된 권한을 제한할 수 있습니다. 대부분의 AWS 작업은 사용자 계정에서 직접 실행하지 않는 것이 좋습니다. 특히 관리자 계정에서는 실행하지 않는 것이 좋습니다.

   IAM 콘솔([https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/))을 엽니다.

1. JSON 정책 편집기를 사용하여 아래 템플릿에 정의된 정책을 생성합니다. 자체 리전 및 AWS 계정 세부 정보를 제공합니다. 자세한 내용은 [JSON 탭에서 정책 생성](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create-console.html#access_policies_create-json-editor)을 참조하세요.

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Sid": "LambdaCertificateExpiryPolicy1",
               "Effect": "Allow",
               "Action": "logs:CreateLogGroup",
               "Resource": "arn:aws:logs:us-east-1:123456789012:*"
           },
           {
               "Sid": "LambdaCertificateExpiryPolicy2",
               "Effect": "Allow",
               "Action": [
                   "logs:CreateLogStream",
                   "logs:PutLogEvents"
               ],
               "Resource": [
                   "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/handle-expiring-certificates:*"
               ]
           },
           {
               "Sid": "LambdaCertificateExpiryPolicy3",
               "Effect": "Allow",
               "Action": [
                   "acm:DescribeCertificate",
                   "acm:GetCertificate",
                   "acm:ListCertificates",
                   "acm:ListTagsForCertificate"
               ],
               "Resource": "*"
           },
           {
               "Sid": "LambdaCertificateExpiryPolicy4",
               "Effect": "Allow",
               "Action": "SNS:Publish",
               "Resource": "*"
           },
           {
               "Sid": "LambdaCertificateExpiryPolicy5",
               "Effect": "Allow",
               "Action": [
                   "SecurityHub:BatchImportFindings",
                   "SecurityHub:BatchUpdateFindings",
                   "SecurityHub:DescribeHub"
               ],
               "Resource": "*"
           },
           {
               "Sid": "LambdaCertificateExpiryPolicy6",
               "Effect": "Allow",
               "Action": "cloudwatch:ListMetrics",
               "Resource": "*"
           }
       ]
   }
   ```

------

1. IAM 역할을 생성하여 여기에 새 정책을 연결합니다. IAM 역할 생성 및 정책 연결에 대한 자세한 내용은 [AWS 서비스에 대한 역할 생성(콘솔)을 참조하세요](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console).

1. [https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/) AWS Lambda 콘솔을 엽니다.

1. Lambda 함수를 생성합니다. 자세한 내용은 [콘솔로 Lambda 함수 생성](https://docs.aws.amazon.com/lambda/latest/dg/getting-started-create-function.html)을 참조하세요. 다음 단계를 완료합니다.

   1. [**함수 생성(Create function)**] 페이지에서 [**새로 작성(Author from scratch)**] 옵션을 선택하여 함수를 생성합니다.

   1. ‘handle-expiring-certificates’와 같은 이름을 [**함수 이름(Function name)**] 필드에 지정합니다.

   1. [**런타임(Runtime)**] 목록에서 Python 3.8을 선택합니다.

   1. [**기본 실행 역할 변경(Change default execution role)**]을 선택하고 [**기존 역할 사용(Use an existing role)**]을 선택합니다.

   1. [**기존 역할(Existing role)**] 목록에서 앞서 생성한 역할을 선택합니다.

   1. **함수 생성**을 선택합니다.

   1. [**함수 코드(Function code)**] 아래에 다음 코드를 삽입합니다.

      ```
      # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
      # SPDX-License-Identifier: MIT-0
      #
      # Permission is hereby granted, free of charge, to any person obtaining a copy of this
      # software and associated documentation files (the "Software"), to deal in the Software
      # without restriction, including without limitation the rights to use, copy, modify,
      # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
      # permit persons to whom the Software is furnished to do so.
      #
      # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
      # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
      # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
      # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
      # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
      # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
      
      import json
      import boto3
      import os
      from datetime import datetime, timedelta, timezone
      # -------------------------------------------
      # setup global data
      # -------------------------------------------
      utc = timezone.utc
      # make today timezone aware
      today = datetime.now().replace(tzinfo=utc)
      # set up time window for alert - default to 45 if its missing
      if os.environ.get('EXPIRY_DAYS') is None:
          expiry_days = 45
      else:
          expiry_days = int(os.environ['EXPIRY_DAYS'])
      expiry_window = today + timedelta(days = expiry_days)
      def lambda_handler(event, context):
          # if this is coming from the ACM event, its for a single certificate
          if (event['detail-type'] == "ACM Certificate Approaching Expiration"):
              response = handle_single_cert(event, context.invoked_function_arn)
          return {
              'statusCode': 200,
              'body': response 
          }
      def handle_single_cert(event, context_arn):
          cert_client = boto3.client('acm')
          cert_details = cert_client.describe_certificate(CertificateArn=event['resources'][0])
          result = 'The following certificate is expiring within ' + str(expiry_days) + ' days: ' + cert_details['Certificate']['DomainName']
          # check the expiry window before logging to Security Hub and sending an SNS
          if cert_details['Certificate']['NotAfter'] < expiry_window:
              # This call is the text going into the SNS notification
              result = result + ' (' + cert_details['Certificate']['CertificateArn'] + ') '
              # this call is publishing to SH
              result = result + ' - ' + log_finding_to_sh(event, cert_details, context_arn)
              # if there's an SNS topic, publish a notification to it
              if os.environ.get('SNS_TOPIC_ARN') is None:
                  response = result
              else:
                  sns_client = boto3.client('sns')
                  response = sns_client.publish(TopicArn=os.environ['SNS_TOPIC_ARN'], Message=result, Subject='Certificate Expiration Notification')
          return result
      def log_finding_to_sh(event, cert_details, context_arn):
          # setup for security hub
          sh_region = get_sh_region(event['region'])
          sh_hub_arn = "arn:aws:securityhub:{0}:{1}:hub/default".format(sh_region, event['account'])
          sh_product_arn = "arn:aws:securityhub:{0}:{1}:product/{1}/default".format(sh_region, event['account'])
          # check if security hub is enabled, and if the hub arn exists
          sh_client = boto3.client('securityhub', region_name = sh_region)
          try:
              sh_enabled = sh_client.describe_hub(HubArn = sh_hub_arn)
          # the previous command throws an error indicating the hub doesn't exist or lambda doesn't have rights to it so we'll stop attempting to use it
          except Exception as error:
              sh_enabled = None
              print ('Default Security Hub product doesn\'t exist')
              response = 'Security Hub disabled'
          # This is used to generate the URL to the cert in the Security Hub Findings to link directly to it
          cert_id = right(cert_details['Certificate']['CertificateArn'], 36)
          if sh_enabled:
              # set up a new findings list
              new_findings = []
                  # add expiring certificate to the new findings list
              new_findings.append({
                  "SchemaVersion": "2018-10-08",
                  "Id": cert_id,
                  "ProductArn": sh_product_arn,
                  "GeneratorId": context_arn,
                  "AwsAccountId": event['account'],
                  "Types": [
                      "Software and Configuration Checks/AWS Config Analysis"
                  ],
                  "CreatedAt": event['time'],
                  "UpdatedAt": event['time'],
                  "Severity": {
                      "Original": '89.0',
                      "Label": 'HIGH'
                  },
                  "Title": 'Certificate expiration',
                  "Description": 'cert expiry',
                  'Remediation': {
                      'Recommendation': {
                          'Text': 'A new certificate for ' + cert_details['Certificate']['DomainName'] + ' should be imported to replace the existing imported certificate before expiration',
                          'Url': "https://console.aws.amazon.com/acm/home?region=" + event['region'] + "#/?id=" + cert_id
                      }
                  },
                  'Resources': [
                      {
                          'Id': event['id'],
                          'Type': 'ACM Certificate',
                          'Partition': 'aws',
                          'Region': event['region']
                      }
                  ],
                  'Compliance': {'Status': 'WARNING'}
              })
              # push any new findings to security hub
              if new_findings:
                  try:
                      response = sh_client.batch_import_findings(Findings=new_findings)
                      if response['FailedCount'] > 0:
                          print("Failed to import {} findings".format(response['FailedCount']))
                  except Exception as error:
                      print("Error: ", error)
                      raise
          return json.dumps(response)
      # function to setup the sh region    
      def get_sh_region(event_region):
          # security hub findings may need to go to a different region so set that here
          if os.environ.get('SECURITY_HUB_REGION') is None:
              sh_region_local = event_region
          else:
              sh_region_local = os.environ['SECURITY_HUB_REGION']
          return sh_region_local
      # quick function to trim off right side of a string
      def right(value, count):
          # To get right part of string, use negative first index in slice.
          return value[-count:]
      ```

   1. [**환경 변수(Environment variables)**]에서 [**편집(Edit)**]을 선택하고 선택적으로 다음 변수를 추가합니다.
      + (선택 사항) EXPIRY\$1DAYS

        인증서 만료 알림을 보내기 전의 리드 타임(일)을 지정합니다. 이 함수의 기본값은 45일이지만 사용자 지정 값을 지정할 수 있습니다.
      + (선택 사항) SNS\$1TOPIC\$1ARN

        Amazon SNS의 ARN을 지정합니다. arn:aws:sns:*<region>*:*<account-number>*:*<topic-name>* 형식으로 전체 ARN을 제공합니다.
      + (선택 사항) SECURITY\$1HUB\$1REGION

        다른 리전 AWS Security Hub CSPM 에서를 지정합니다. 이 값을 지정하지 않으면 실행 중인 Lambda 함수의 리전이 사용됩니다. 함수가 여러 리전에서 실행되는 경우 모든 인증서 메시지가 단일 리전의 Security Hub CSPM으로 전달되도록 하는 것이 좋습니다.

   1. [**기본 설정(Basic settings)**]에서 [**제한 시간(Timeout)**]을 30초로 설정합니다.

   1. 페이지 상단에서 [**배포(Deploy)**]를 선택합니다.

다음 절차의 작업을 완료하여 이 솔루션의 사용을 시작합니다.

**만료 이메일 알림을 자동화하려면**

이 예에서는 Amazon EventBridge를 통해 이벤트가 발생하는 순간, 만료되는 각 인증서마다 하나의 이메일을 제공합니다. 기본적으로 ACM은 만료일이 45일 이하로 남은 인증서에 대해 매일 이벤트를 발생시킵니다. (이 기간은 ACM API의 [PutAccountConfiguration](https://docs.aws.amazon.com/acm/latest/APIReference/API_PutAccountConfiguration.html) 작업을 사용하여 사용자 지정할 수 있습니다.) 이러한 각 이벤트는 다음과 같은 일련의 자동화된 작업을 트리거합니다.

```
ACM raises Amazon EventBridge event → 
>>>>>>> events

          Event matches Amazon EventBridge rule → 

                    Rule calls Lambda function → 

                              Function sends SNS email and logs a Finding in Security Hub CSPM
```

1. Lambda 함수를 생성하고 권한을 구성합니다. (이미 완료됨 - [Lambda 함수 및 IAM 역할을 설정하려면](#lambda-setup) 참조).

1. 알림을 보내는 데 사용할 Lambda 함수의 *표준* SNS 주제입니다. 자세한 내용은 [Amazon SNS 주제 생성](https://docs.aws.amazon.com/sns/latest/dg/sns-create-topic.html)을 참조하세요.

1. 관심있는 당사자가 새로운 SNS 주제를 구독하도록 설정합니다. 자세한 내용은 [Amazon SNS 주제에 구독 설정](https://docs.aws.amazon.com/sns/latest/dg/sns-create-subscribe-endpoint-to-topic.html)을 참조하세요.

1. Lambda 함수를 트리거하는 Amazon EventBridge 규칙을 생성합니다. 자세한 내용은 [이벤트에 응답하는 Amazon EventBridge 규칙 생성](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule.html)을 참조하세요.

   Amazon EventBridge 콘솔([https://console.aws.amazon.com/events/](https://console.aws.amazon.com/events/))에서 **Events**(이벤트) > **Rules**(규칙) 페이지로 이동하여 **Create rule**(규칙 생성)을 선택합니다. [**서비스 이름(Service Name)**], [**이벤트 유형(Event Type)**] 및 [**Lambda 함수(Lambda function)**]를 지정합니다. [**이벤트 패턴 미리 보기(Event Pattern preview)**] 편집기에서 다음 코드를 붙여 넣습니다.

   ```
   {
     "source": [
       "aws.acm"
     ],
     "detail-type": [
       "ACM Certificate Approaching Expiration"
     ]
   }
   ```

   Lambda 수신과 같은 이벤트가 [**샘플 이벤트 표시(Show sample event(s))**] 아래에 표시됩니다.

   ```
   {
     "version": "0",
     "id": "9c95e8e4-96a4-ef3f-b739-b6aa5b193afb",
     "detail-type": "ACM Certificate Approaching Expiration",
     "source": "aws.acm",
     "account": "123456789012",
     "time": "2020-09-30T06:51:08Z",
     "region": "us-east-1",
     "resources": [
       "arn:aws:acm:us-east-1:123456789012:certificate/61f50cd4-45b9-4259-b049-d0a53682fa4b"
     ],
     "detail": {
       "DaysToExpiry": 31,
       "CommonName": "My Awesome Service"
     }
   }
   ```

**정리하려면**

구성 예 또는 다른 어떤 구성이 더 이상 필요하지 않은 경우, 보안 문제 및 예기치 않은 향후 요금 발생을 방지하기 위해 모든 트레이스를 제거하는 것이 좋습니다.
+ IAM 정책 및 역할
+ Lambda 함수
+ CloudWatch Events 규칙
+ Lambda와 연결된 CloudWatch Logs
+ SNS 주제