Como faço para corrigir a dependência circular entre uma permissão do AWS Lambda e os recursos do grupo de destino no AWS CloudFormation?
Quero corrigir a dependência circular entre uma permissão do AWS Lambda (AWS::Lambda::Permission) e os recursos do grupo de destino (AWS::ElasticLoadBalancingV2::TargetGroup) no AWS CloudFormation.
Breve descrição
Você obtém uma dependência circular se fornecer AWS::Lambda::Permission ao AWS::ElasticLoadBalancingV2::TargetGroup em vez de LoadBalancer para restringir o acesso à sua função do Lambda. Isso ocorre porque a permissão do Lambda deve existir antes de você associar a função do Lambda ao grupo de destino que você deseja criar. Ao criar a permissão do Lambda, você deve associar a permissão a um uma Entidade principal da AWS e SourceARN (nesse caso, um grupo de destino). É por isso que o grupo de destino deve existir antes de criar a permissão do Lambda. Para corrigir essa dependência circular, você pode usar um recurso personalizado baseado em Lambda.
Resolução
1. Crie um arquivo chamado index.py que inclua o seguinte:
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. def handler(event, context): return { "statusCode": 200, "statusDescription": "HTTP OK", "isBase64Encoded": False, "headers": { "Content-Type": "text/html" }, "body": "<h1>Hello from Lambda!</h1>" }
Observação: a função do AWS Lambda em index.py exibe uma página HTML com a mensagem “Olá do Lambda!” quando a função é invocada.
2. Adicione o arquivo index.py a um arquivo chamado website.zip.
3. Para criar um arquivo.zip de index.py, execute o comando a seguir:
$zip website.zip index.py
4. Crie um arquivo chamado cfnresponse.py com base no modelo do AWS CloudFormation no AWS GitHub.
5. Crie um preenchimento chamado custom.py com base no seguinte:
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. import logging import boto3, json, botocore import cfnresponse #Define logging properties for 'logging' log = logging.getLogger() log.setLevel(logging.INFO) #Main Lambda function to be executed def lambda_handler(event, context): try: if event['RequestType'] == "Create" or event['RequestType'] == "Update": # print the event type sent from cloudformation log.info ("'" + str(event['RequestType']) + "' event was sent from CFN") # Imput Parameters TargetGroupARN = event['ResourceProperties']['TargetGroupARN'] LambdaFunctionARN = event['ResourceProperties']['LambdaFunctionARN'] log.info("TargetGroup ARN value is:" + TargetGroupARN) log.info("Lambda Function ARN value is:" + LambdaFunctionARN) responseData = {} # ELBV2 initilize client = boto3.client('elbv2') # Initilize Vars response = '' error_msg = '' # Make the 1st API call to get the Lambda policy and extract SID of the initial permissions that were created by the CFN template. try: response = client.register_targets( TargetGroupArn=TargetGroupARN, Targets=[ { 'Id': LambdaFunctionARN }, ] ) except botocore.exceptions.ClientError as e: error_msg = str(e) if error_msg: log.info("Error Occured:" + error_msg) response_msg = error_msg # TODO: SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal FAILURE back to cloudformation.") responseData = {"Message" : response_msg, "Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.FAILED, responseData) else: response_msg = "Successfully Added Lambda(" + LambdaFunctionARN + ") as Target" log.info(response_msg) # TODO: SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal SUCCESS back to cloudformation.") responseData = {"Message" : response_msg, "Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) # log the end of the create event log.info ("End of '" + str(event['RequestType']) + "' Event") elif "Delete" in str(event['RequestType']): # print the event type sent from cloudformation log.info ("'Delete' event was sent from CFN") # TODO: DELETE THINGS log.info("TODO: DELETE THINGS") # TODO: SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal SUCCESS back to cloudformation.") responseData = {"Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) # log the end of the Delete event log.info ("End of 'Delete' Event") else: log.info ("RequestType sent from cloudformation is invalid.") log.info ("Was expecting 'Create', 'Update' or 'Delete' RequestType(s).") log.info ("The detected RequestType is : '" + str(event['RequestType']) + "'") #TODO: SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal FAILURE back to cloudformation due to invalid request type.") responseData={"Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.FAILED, responseData) except Exception as e: log.info ("Function failed due to the following error:") print (e) #TODO: SIGNAL BACK TO CLOUDFORMATION log.info("Trying to signal FAILURE back to cloudformation due to the error.") responseData={"Function" : context.log_stream_name} cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
Observação: a função do Lambda em custom.py executa a chamada de API RegisterTargets e sinaliza o AWS CloudFormation ao concluir essa chamada de API.
6. Adicione os arquivos cfnresponse.py e custom.py a um arquivo chamado custom.zip. Por exemplo:
zip custom.zip cfnresponse.py custom.py
Observação: o arquivo custom.py usa a chamada de API do Boto3 RegisterTargets. Essa chamada de API registra os destinos especificados com o grupo de destino especificado. Para mais informações, consulte o trecho de Python register_targets do site do Boto3. O arquivo cfnresponse.py contém uma função que envia logs e notificações para o Amazon CloudWatch e o AWS CloudFormation.
7. Crie um modelo do AWS CloudFormation usando o seguinte modelo 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: HelloWorld Lambda function template for Application Load Balancer Lambda as target Parameters: # VPC in which the LoadBalancer and the LoadBalancer SecurityGroup will be created VpcId: Type: AWS::EC2::VPC::Id # Subnets in which the LoadBalancer will be created. Subnets: Type: List<AWS::EC2::Subnet::Id> # Name of the TargetGroup TargetGroupName: Type: String Default: 'MyTargets' # Name of the S3 bucket where custom.zip and website.zip are uploaded to S3BucketName: Type: String Default: you-s3-bucket-name Resources: 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: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - '*' Resource: '*' # Lambda function which displays an HTML page with "Hello from Lambda!" message upon invocation HelloWorldFunction1234: Type: 'AWS::Lambda::Function' Properties: Code: S3Bucket: !Ref S3BucketName S3Key: website.zip FunctionName: testLambda MemorySize: 128 Handler: index.handler Timeout: 30 Runtime: python3.7 Role: !GetAtt LambdaExecutionRole.Arn LoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Scheme: internet-facing Subnets: !Ref Subnets SecurityGroups: - !Ref LoadBalancerSecurityGroup HttpListener: Type: 'AWS::ElasticLoadBalancingV2::Listener' Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup Type: forward LoadBalancerArn: !Ref LoadBalancer Port: 80 Protocol: HTTP LoadBalancerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow http to client host VpcId: !Ref VpcId SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 HelloWorldFunctionInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt HelloWorldFunction1234.Arn Action: 'lambda:InvokeFunction' Principal: elasticloadbalancing.amazonaws.com SourceArn: !Sub - >- arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:${TGFullName} - TGFullName: !GetAtt TargetGroup.TargetGroupFullName TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Ref TargetGroupName TargetType: lambda # Custom resource for Lambda function - "HelloWorldFunction1234" HelloWorld: DependsOn: [TargetGroup, HelloWorldFunctionInvokePermission] Type: Custom::HelloWorld Properties: ServiceToken: !GetAtt TestFunction.Arn # Input parameters for the Lambda function LambdaFunctionARN: !GetAtt HelloWorldFunction1234.Arn TargetGroupARN: !Sub - >- arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:${TGFullName} - TGFullName: !GetAtt TargetGroup.TargetGroupFullName # Lambda function that performs RegisterTargets API call TestFunction: Type: AWS::Lambda::Function Properties: Code: S3Bucket: !Ref S3BucketName S3Key: custom.zip Handler: custom.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Runtime: python3.7 Timeout: '5' Outputs: Message: Description: Message returned from Lambda Value: !GetAtt - HelloWorld - Message
8. Passe seus valores para os parâmetros S3BucketName, Sub-rede, VpcId e TargetGroupName para a pilha do AWS CloudFormation que você criou usando o modelo na etapa 7.
9. Crie a pilha.
10. Depois que todas as chamadas de API forem concluídas, acesse a seção Saídas do console do AWS CloudFormation e procure a seguinte mensagem:
Successfully Added Lambda(arn:aws:lambda:us-east-1:123456789:function:testLambda) as Target

Conteúdo relevante
- AWS OFICIALAtualizada há 2 anos
- AWS OFICIALAtualizada há um ano