Search code examples
amazon-web-servicesamazon-elastic-beanstalkaws-cloudformationamazon-waf

AWS::WAFv2::WebACLAssociation ResourceArn for Application Load Balancer in CloudFormation


I have a CloudFormation template which creates an ElasticBeanstalk environment like this:

        "ApplicationEnvironment": {
            "Type": "AWS::ElasticBeanstalk::Environment",
            "Properties": {
                "ApplicationName": {
                    "Ref": "Application"
                },
                "SolutionStackName": "64bit Amazon Linux 2018.03 v2.11.2 running Java 8",
                "VersionLabel": { 
                    "Ref": "AppVersion"
                },
                "Tier": {
                    "Name": "WebServer",
                    "Type": "Standard"
                },
                "OptionSettings": [
                    ...
                    {
                        "Namespace": "aws:elasticbeanstalk:environment",
                        "OptionName": "EnvironmentType",
                        "Value": "LoadBalanced"
                    },
                    {
                        "Namespace": "aws:elasticbeanstalk:environment",
                        "OptionName": "LoadBalancerType",
                        "Value": "application"
                    },
                    ...

---
        "WAF": {
            "Type": "AWS::WAFv2::WebACL",
            "Properties": {
                "DefaultAction": {
                    "Type": "BLOCK"
                },              
                "Scope": "REGIONAL",
                "VisibilityConfig": {
                    "CloudWatchMetricsEnabled": "false",
                    "MetricName": { "Fn::Join": [ "", [ { "Ref": "AWS::StackName" }, "metric-waf" ] ] },
                    "SampledRequestsEnabled": "false"
                },
                "Rules": [
                    {
                        "Action" : {
                          "Type" : "BLOCK"
                        },
                        "Priority" : 0,
                        "Statement" : {
                            "ManagedRuleGroupStatement": {
                                "VendorName": "AWS",
                                "Name": "AWSManagedRulesCommonRuleSet"
                            }
                        }
                    }
                ]
            }
        },
        "WAFAssociation": {
            "Type" : "AWS::WAFv2::WebACLAssociation",
            "Properties" : {
                "ResourceArn" : ???,
                "WebACLArn" : { "Ref": "WAF" }
            }
        }

I intend to associate the Beanstalk ALB with the WebACL but have no idea how to refer to the application load balancer ARN that the template creates. I cannot just put a hardcoded ARN in since it always changes based on what the template creates.

Is there some way I can refer to the ALB ARN in the ResourceArn field? Or do I need to apply the WebACL somewhere in the Beanstalk Option Settings?


Solution

  • I think the only way would be through a custom resource which takes EB env name, uses describe_environment_resources API call to get the EB env info (including LA arn), and returns back to your stuck.

    Below is a working example of such a resource which you could add to your template:

      LambdaBasicExecutionRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Statement:
            - Effect: Allow
              Principal:
                Service: lambda.amazonaws.com
              Action: sts:AssumeRole
          Path: /
          ManagedPolicyArns:
            - arn:aws:iam::aws:policy/AmazonEC2FullAccess
            - arn:aws:iam::aws:policy/AWSElasticBeanstalkFullAccess
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    
      MyCustomResource:
        Type: Custom::GetEBLoadBalancerArn
        Properties:
          ServiceToken: !GetAtt 'MyCustomFunction.Arn'
          EBEnvName: !Ref MyEnv
    
      MyCustomFunction:
        Type: AWS::Lambda::Function
        Properties:
          Handler: index.lambda_handler
          Description: "Get ARN of EB Load balancer"
          Timeout: 30
          Role: !GetAtt 'LambdaBasicExecutionRole.Arn'
          Runtime: python3.7
          Code:
            ZipFile: |
              import json
              import logging
              import cfnresponse
              import boto3
    
              logger = logging.getLogger()
              logger.setLevel(logging.INFO)
    
              eb = boto3.client('elasticbeanstalk')
              ec2 = boto3.client('ec2')
    
              def lambda_handler(event, context):
                logger.info('got event {}'.format(event))  
                try:
    
                  responseData = {}
    
                  if event['RequestType'] in ["Create"]:                      
    
                    eb_env_name = event['ResourceProperties']['EBEnvName']
    
                    response = eb.describe_environment_resources(
                        EnvironmentName=eb_env_name
                    )
    
                    lb_arn = response['EnvironmentResources']['LoadBalancers'][0]['Name']
    
                    logger.info(str(response['EnvironmentResources']['LoadBalancers'][0]['Name']))
    
                    responseData = {
                      "LBArn": lb_arn
                    }
    
                    cfnresponse.send(event, context, 
                                     cfnresponse.SUCCESS, responseData)
    
                  else:
                    logger.info('Unexpected RequestType!') 
                    cfnresponse.send(event, context, 
                                      cfnresponse.SUCCESS, responseData)
    
                except Exception as err:
    
                  logger.error(err)
                  responseData = {"Data": str(err)}
                  cfnresponse.send(event,context, 
                                   cfnresponse.FAILED,responseData)
                return    
    

    Having the resource you would just use:

            "WAFAssociation": {
                "Type" : "AWS::WAFv2::WebACLAssociation",
                "Properties" : {
                    "ResourceArn" : { "GetAtt": ["MyCustomResource", "LBArn"] },
                    "WebACLArn" : { "Ref": "WAF" }
                }
            }