Wie behebe ich den Fehler, den ich in CloudFormation erhalte, wenn ich versuche, langsame Protokolle in CloudWatch Logs zu veröffentlichen?

Lesedauer: 6 Minute
0

Ich möchte den Fehler beheben, der in AWS CloudFormation auftritt, wenn ich versuche, langsame Protokolle in Amazon CloudWatch Logs zu veröffentlichen. Der Fehler ist: „Die für die CloudWatch Logs-Protokollgruppe /aws/aes/domains/search/search-logs angegebene Ressourcenzugriffsrichtlinie gewährt Amazon Elasticsearch Service keine ausreichenden Berechtigungen, um einen Log-Stream zu erstellen.“

Kurzbeschreibung

Um diesen Fehler zu beheben, verwenden Sie eine separate Richtlinie auf Protokollgruppenebene, damit Amazon Elasticsearch Service (Amazon ES) Protokolle an CloudWatch Logs weiterleiten kann. Verwenden Sie dann AccessPolicies in der Ressource AWS::Elasticsearch::Domain, um Berechtigungen für Amazon ES-Domains festzulegen.

Die folgenden Schritte zeigen Ihnen, wie Sie mit CloudFormation langsame Protokolle auf CloudWatch veröffentlichen, indem Sie eine von AWS Lambda unterstützte benutzerdefinierte Ressource verwenden, die in Python 3.6 erstellt wurde. Die benutzerdefinierte Ressource löst eine Lambda-Funktion aus, die die PutResourcePolicy-API dazu veranlasst, langsame Protokolle zu veröffentlichen.

Hinweis: Wenn CloudFormation die Veröffentlichung von Protokollen in CloudWatch aktiviert, verfügt die Ressource AWS::Logs::LogGroup nicht über eine Eigenschaft, mit der eine Ressourcenzugriffsrichtlinie zugewiesen werden kann. Die für die CloudWatch Logs-Protokollgruppe angegebene Ressourcenzugriffsrichtlinie sollte Amazon ES ausreichende Berechtigungen gewähren, um den Protokollstream zu veröffentlichen. Sie können eine Zugriffsrichtlinienberechtigung nicht direkt mithilfe einer CloudFormation-Ressource erstellen. Dies liegt daran, dass der PutResourcePolicy-API-Aufruf für die Ressource AWS::Logs::LogGroup von CloudFormation nicht unterstützt wird.

Behebung

Hinweis: Wenn Sie beim Ausführen von Befehlen der AWS Command Line Interface (AWS CLI) Fehler erhalten, vergewissern Sie sich, dass Sie die neueste AWS CLI-Version verwenden.

Die folgende CloudFormation-Vorlage verwendet eine benutzerdefinierte Ressource, um den Namen der Protokollgruppe abzurufen. Anschließend wendet die Vorlage die Richtlinie an, die es dem Amazon ES-Dienst ermöglicht, API-Aufrufe für die Protokollgruppe durchzuführen.

  1. Erstellen Sie eine CloudFormation-Vorlage mit dem Namen ESlogsPermission.yaml:
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
  1. Verwenden Sie die CloudFormation-Konsole oder den folgenden AWS-CLI-Befehl, um einen CloudFormation-Stack mit der Datei ESlogsPermission.yaml zu starten:
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

Hinweis: Ersetzen yourStackName, yourTemplateName, Your-LogGroup-Name, Your-ES-Name und yourRegion durch Ihre Werte.

Die CloudFormation-Vorlage erledigt Folgendes für Sie:

  1. Erzeugt eine Protokollgruppe.

  2. Erzeugt eine Lambda-Funktion. Die Lambda-Funktion ruft mithilfe einer benutzerdefinierten Ressource den Namen der Protokollgruppe aus dem Abschnitt Parameters der CloudFormation-Vorlage ab. Die Lambda-Funktion ruft die PutResourcePolicy-API für den Namen der Protokollgruppe auf. Die Protokollgruppe muss über eine Richtlinie verfügen, die es der Amazon ES-Domain ermöglicht, die Protokolle zu speichern.

  3. Erstellt eine von Lambda unterstützte benutzerdefinierte Ressource, um die in Schritt 2 erstellte Lambda-Funktion aufzurufen. Die benutzerdefinierte Ressource hilft dabei, PutResourcePolicy auf die Protokollgruppe Amazon Resource Name (ARN) anzuwenden, sodass Amazon ES Protokolle streamen kann. In der Vorlage verwendet CloudFormation eine benutzerdefinierte Ressource, um eine Amazon ES-Domain mit der LogPublishingOption zu erstellen.


AWS OFFICIAL
AWS OFFICIALAktualisiert vor 3 Jahren