AWS CloudFormation에서 AWS Lambda 권한과 대상 그룹 리소스 간의 순환 종속성을 수정하려면 어떻게 해야 합니까?

최종 업데이트 날짜: 2021년 2월 2일

AWS CloudFormation에서 AWS Lambda 권한(AWS::Lambda::Permission)과 대상 그룹 리소스(AWS::ElasticLoadBalancingV2::TargetGroup) 간의 순환 종속성을 수정하려고 합니다.

간략한 설명

Lambda 함수에 대한 액세스를 제한하기 위해 LoadBalancer 대신 AWS::Lambda::PermissionAWS::ElasticLoadBalancingV2::TargetGroup에 제공하는 경우 순환 종속성이 발생합니다. 이는 생성하려는 대상 그룹에 Lambda 함수를 연결하기 전에 Lambda 권한이 존재해야 하기 때문입니다. Lambda 권한을 생성할 때에는 권한을 AWS 보안 주체SourceARN(이 경우 대상 그룹)에 연결해야 합니다. 그렇기 때문에 Lambda 권한을 생성하기 전에 대상 그룹이 존재해야 하는 것입니다. 이 순환 종속성을 수정하려면 Lambda 지원 사용자 지정 리소스를 사용할 수 있습니다.

해결 방법

1.    다음을 포함하는 index.py라는 이름의 파일을 생성합니다.

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>"
    }

참고: index.py의 AWS Lambda 함수를 호출하면 "Hello from Lambda!"라는 메시지의 HTML 페이지가 표시됩니다.

2.    index.py 파일을 website.zip이라는 이름의 아카이브 파일에 추가합니다.

3.    index.py의 .zip 파일을 생성하려면 다음 명령을 실행합니다.

$zip website.zip index.py

4.    AWS GitHub의 AWS CloudFormation 템플릿을 기반으로 cfnresponse.py라는 이름의 파일을 생성합니다.

5.    다음을 기반으로 채워진 custom.py라는 파일을 생성합니다.

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)

참고: custom.py 의 Lambda 함수는 RegisterTargets API 호출을 수행하고 해당 API 호출이 완료되면 AWS CloudFormation에 알립니다.

6.    cfnresponse.pycustom.py 파일을 custom.zip라는 이름의 아카이브 파일에 추가합니다. 예를 들어 다음과 같습니다.

zip custom.zip cfnresponse.py custom.py

참고: custom.py 파일은 Boto3 API 호출 RegisterTargets를 사용합니다. 이 API 호출은 지정된 대상을 지정된 대상 그룹에 등록합니다. 자세한 내용은 Boto3 웹 사이트의 register_targets Python 스니펫을 참조하십시오. cfnresponse.py 파일에는 Amazon CloudWatch 및 AWS CloudFormation에 로그 및 알림을 푸시하는 함수가 포함되어 있습니다.

7.    다음 YAML 템플릿을 사용하여 AWS 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: 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.    S3BucketName, Subnets, VpcIdTargetGroupName 파라미터를 대한 값을 7단계에서 템플릿을 사용하여 생성한 AWS CloudFormation 스택에 전달합니다.

9.    스택을 생성합니다.

10.    모든 API 호출이 완료되면 AWS CloudFormation 콘솔의 출력(Outputs) 섹션으로 이동한 후 다음 메시지를 찾습니다.

Successfully Added Lambda(arn:aws:lambda:us-east-1:123456789:function:testLambda) as Target

이 문서가 도움이 되었나요?


결제 또는 기술 지원이 필요합니까?