Como faço para resolver o erro que recebo no CloudFormation quando tento publicar logs lentos no CloudWatch Logs?

6 minuto de leitura
0

Quero resolver o erro que recebo no AWS CloudFormation quando tento publicar logs lentos no Amazon CloudWatch Logs. O erro é: “A Política de acesso a recursos especificada para o grupo de logs do CloudWatch Logs /aws/aes/domains/search/search-logs não concede permissões suficientes para o Amazon Elasticsearch Service criar um fluxo de logs”.

Breve descrição

Para resolver esse erro, use uma política separada no nível do grupo de logs para permitir que o Amazon Elasticsearch Service (Amazon ES) envie logs para o CloudWatch Logs. Em seguida use AccessPolicies no recurso AWS::Elasticsearch::Domain para definir permissões para domínios do Amazon ES.

As etapas a seguir mostram como publicar logs lentos no CloudWatch com o CloudFormation usando um recurso personalizado com suporte do AWS Lambda criado no Python 3.6. O recurso personalizado aciona uma função do Lambda que aciona a API PutResourcePolicy para publicar logs lentos.

Observação: quando o CloudFormation permite a publicação de logs no CloudWatch, o recurso AWS::Logs::LogGroup não tem uma propriedade para atribuir uma política de acesso a recursos. A política de acesso a recursos especificada para o grupo de logs do CloudWatch Logs deve conceder permissões suficientes para que o Amazon ES publique o fluxo de logs. Você não pode criar uma permissão da política de acesso diretamente usando um recurso do CloudFormation. Isso ocorre porque a chamada da API PutResourcePolicy para o recurso AWS::Logs::LogGroup não é aceita pelo CloudFormation.

Solução

Observação: se receber erros ao executar comandos da AWS Command Line Interface (AWS CLI), verifique se você está usando sua versão mais recente.

O modelo do CloudFormation a seguir usa um recurso personalizado para obter o nome do grupo de logs. Em seguida, o modelo aplica a política que permite que o serviço do Amazon ES faça chamadas de API no grupo de logs.

1.    Crie um modelo do CloudFormation chamado 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

2.    Para iniciar uma pilha do CloudFormation com o arquivo ESlogsPermission.yaml, use o console do CloudFormation ou o seguinte comando da 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

Observação: substitua yourStackName, yourTemplateName, Your-LogGroup-Name, Your-ES-Name e yourRegion pelos seus valores.

O modelo do CloudFormation faz o seguinte por você:

1.    Cria um grupo de logs.

2.    Cria uma função do Lambda. A função do Lambda obtém o nome do grupo de logs na seção Parâmetros do modelo do CloudFormation usando um recurso personalizado. A função do Lambda chama a API PutResourcePolicy para o nome do grupo de logs. O grupo de logs deve ter uma política para permitir que o domínio do Amazon ES coloque os logs.

3.    Cria um recurso personalizado com suporte do Lambda para invocar a função do Lambda criada na etapa 2. O recurso personalizado ajuda a aplicar PutResourcePolicy no nome do recurso da Amazon (ARN) do grupo de logs para que o Amazon ES possa transmitir logs. No modelo, o CloudFormation usa um recurso personalizado para criar um domínio do Amazon ES com LogPublishingOption.


AWS OFICIAL
AWS OFICIALAtualizada há 3 anos