低速ログを CloudWatch Logs に公開しようとしたときに CloudFormation で表示されるエラーを解決するにはどうすればよいですか?

最終更新日: 2021 年 3 月 16 日

低速ログを Amazon CloudWatch Logs に公開しようとすると、AWS CloudFormation で表示されるエラーを解決したいです。エラーは次のとおりです。「CloudWatch Logs log group/aws/aes/domains/search/search-logs に指定されたリソースアクセスポリシーが、Amazon Elasticsearch Service にログストリームを作成するための十分なアクセス許可を付与していません。」

簡単な説明

このエラーを解決するには、ロググループレベルで別のポリシーを使用して、Amazon Elasticsearch Service (Amazon ES) が CloudWatch Logs にログをプッシュできるようにします。次に、AWS::Elasticsearch::Domain リソースの AccessPolicies を使用して、Amazon ES ドメインのアクセス許可を設定します。

次の手順は、Python 3.6 で作成した AWS Lambda がサポートするカスタムリソースを使用して、CloudFormation を使って低速ログを CloudWatch に発行する方法を示しています。カスタムリソースは Lambda 関数をトリガーし、PutResourcePolicy API を使用して、低速ログを公開します。

注: CloudFormation が CloudWatch へのログ公開を有効にしている場合、AWS::Logs::LogGroup リソースにはリソースアクセスポリシーを割り当てるためのプロパティがありません。CloudWatch Logs ロググループに指定されているリソースアクセスポリシーは、Amazon ES がログストリームを公開するための十分なアクセス許可を付与する必要があります。CloudFormation リソースを使用してアクセスポリシーのアクセス許可を直接作成することはできません。これは、AWS::Logs::LogGroup リソースに対する PutResourcePolicy API 呼び出しが CloudFormation でサポートされていないためです。

解決方法

注意: AWS コマンドラインインターフェイス (AWS CLI) コマンドの実行中にエラーが発生した場合は、AWS CLI の最新バージョンを使用していることを確認してください

次の CloudFormation テンプレートは、カスタムリソースを使用してロググループの名前を取得します。次に、テンプレートは、Amazon ES サービスがロググループで API 呼び出しを行うことを許可するポリシーを適用します。

1.    ESlogsPermission.yaml という CloudFormation テンプレートを作成します。

Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

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.

AWSTemplateFormatVersion: 2010-09-09
Description: AWS cloudFormation template to publish slow logs to Amazon CloudWatch Logs.
Parameters:
  LogGroupName:
    Type: String
    Description: Please don't change the log group name while updating
  ESDomainName:
    Description: A name for the Amazon Elastic Search domain
    Type: String
  LambdaFunctionName:
    Description: Lambda Function Name
    Type: String
Resources:
  AwsLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Ref LogGroupName
  LambdaLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Sub '/aws/lambda/${LambdaFunctionName}'
  LambdaExecutionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: root1
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: !Sub >-
                  arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}:log-stream:*
        - PolicyName: root2
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                Resource:
                  - !Sub >-
                    arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}
                  - !Sub >-
                    arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/${LogGroupName}
              - Effect: Allow
                Action:
                  - 'logs:PutResourcePolicy'
                  - 'logs:DeleteResourcePolicy'
                Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*'
  logGroupPolicyFunction:
    DependsOn: LambdaLogGroup
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: !Ref LambdaFunctionName
      Code:
        ZipFile: >
          import urllib3

          import json

          import boto3

          http = urllib3.PoolManager()

          SUCCESS = "SUCCESS"

          FAILED = "FAILED"

          def send(event, context, responseStatus, responseData,
          physicalResourceId=None, noEcho=False):
              responseUrl = event['ResponseURL']
              print(responseUrl)
              responseBody = {}
              responseBody['Status'] = responseStatus
              responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name
              responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name
              responseBody['StackId'] = event['StackId']
              responseBody['RequestId'] = event['RequestId']
              responseBody['LogicalResourceId'] = event['LogicalResourceId']
              responseBody['NoEcho'] = noEcho
              responseBody['Data'] = responseData
              json_responseBody = json.dumps(responseBody)
              print("Response body:\n" + json_responseBody)
              headers = {
                  'content-type' : '',
                  'content-length' : str(len(json_responseBody))
              }
              try:
                  response = http.request('PUT',responseUrl,body=json_responseBody.encode('utf-8'),headers=headers)
                  print("Status code: " + response.reason)
              except Exception as e:
                  print("send(..) failed executing requests.put(..): " + str(e))
          def handler(event, context):
              logsgroup_policy_name=event['ResourceProperties']['CWLOGS_NAME']
              cw_log_group_arn=event['ResourceProperties']['CWLOG_ARN']
              cwlogs = boto3.client('logs')
              loggroup_policy={
                  "Version": "2012-10-17",
                  "Statement": [{
                  "Sid": "",
                  "Effect": "Allow",
                  "Principal": { "Service": "es.amazonaws.com"},
                  "Action":[
                  "logs:PutLogEvents",
                  " logs:PutLogEventsBatch",
                  "logs:CreateLogStream"
                      ],
                  'Resource': f'{cw_log_group_arn}'
                  }]
                  }
              loggroup_policy = json.dumps(loggroup_policy)
              if(event['RequestType'] == 'Delete'):
                  print("Request Type:",event['RequestType'])
                  cwlogs.delete_resource_policy(
                  policyName=logsgroup_policy_name
                 )
                  responseData={}
                  send(event, context, SUCCESS, responseData)
              elif(event['RequestType'] == 'Create'):
                  try:
                      cwlogs.put_resource_policy(
                      policyName = logsgroup_policy_name,
                      policyDocument = loggroup_policy
                       )
                      responseData={}
                      print("Sending response to custom resource")
                      send(event, context, SUCCESS, responseData)
                  except Exception as  e:
                      print('Failed to process:', e)
                      send(event, context, FAILED, responseData)
              elif(event['RequestType'] == 'Update'):
                  try:
                      responseData={}
                      print("Update is not supported on this resource")
                      send(event, context, SUCCESS, responseData)
                  except Exception as  e:
                      print('Failed to process:', e)
                      send(event, context, FAILED, responseData)
      Handler: index.handler
      Role: !GetAtt 
        - LambdaExecutionRole
        - Arn
      Runtime: python3.6
  logGroupPolicycustomresource:
    Type: 'Custom::LogGroupPolicy'
    Properties:
      ServiceToken: !GetAtt 
        - logGroupPolicyFunction
        - Arn
      CWLOGS_NAME: !Ref LogGroupName
      CWLOG_ARN: !GetAtt 
        - AwsLogGroup
        - Arn
  ElasticsearchDomain:
    Type: 'AWS::Elasticsearch::Domain'
    DependsOn: logGroupPolicycustomresource
    Properties:
      DomainName: !Ref ESDomainName
      ElasticsearchVersion: '6.2'
      EBSOptions:
        EBSEnabled: true
        VolumeSize: 10
        VolumeType: gp2
      LogPublishingOptions:
        SEARCH_SLOW_LOGS:
          CloudWatchLogsLogGroupArn: !GetAtt 
            - AwsLogGroup
            - Arn
          Enabled: true

2.    ESlogsPermission.yaml ファイルを使用して CloudFormation スタックを起動するには、CloudFormation コンソールまたは次の AWS CLI コマンドを使用します。

aws cloudformation create-stack --stack-name yourStackName --template-body file://yourTemplateName --parameters ParameterKey=LogGroupName,ParameterValue=Your-LogGroup-Name, ParameterKey=ESDomainName,ParameterValue=Your-ES-Name --capabilities CAPABILITY_NAMED_IAM --region yourRegion

注: yourStackNameyourTemplateNameYour-LogGroup-NameYour-ES-Name、および yourRegion を値に置き換えてください。

CloudFormation テンプレートは、以下のことを行います。

1.    ロググループを作成します。

2.    Lambda 関数を作成します。Lambda 関数は、カスタムリソースを使用して、CloudFormation テンプレートの [Parameters (パラメータ)] セクションからロググループ名を取得します。Lambda 関数は、ロググループ名の PutResourcePolicy API を呼び出します。ロググループには、Amazon ES ドメインにログを配置できるよう許可するポリシーが必要です。

3.    ステップ 2 で作成した Lambda 関数を呼び出すために、Lambda でバックアップされたカスタムリソースを作成します。カスタムリソースは、Amazon ES がログをストリーミングできるように、ロググループ Amazon リソースネーム (ARN) に PutResourcePolicy を適用することができます。テンプレートでは、CloudFormation がカスタムリソースを使用して、LogPublishingOption で Amazon ES ドメインを作成します。


この記事は役に立ちましたか?


請求に関するサポートまたは技術サポートが必要ですか?