2

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?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Steven
  • 3,844
  • 3
  • 32
  • 53
  • You might look at the answer to the https://stackoverflow.com/questions/57723251/how-to-get-the-arn-of-the-load-balancer-in-ebextensions – Milan Gatyás Jan 14 '21 at 15:53
  • I cant see to get that answer working as I cannot refer to the ALB from the ApplicationEnvironment resource: "AppAssociateWebACL": { "Type": "AWS::WAFv2::WebACLAssociation", "Properties": { "ResourceArn": { "Fn::GetAtt": [ "ApplicationEnvironment", "AWSEBV2LoadBalancer" ] Template error: resource ApplicationEnvironment does not support attribute type AWSEBV2LoadBalancer in Fn::GetAtt }, "WebACLArn": "..." } }, – Steven Jan 14 '21 at 23:37
  • Also, if I just use: "AppAssociateWebACL": { "Type": "AWS::WAFv2::WebACLAssociation", "Properties": { "ResourceArn": { "Ref" : "AWSEBV2LoadBalancer" }, "WebACLArn": "..." } }, I get: Template format error: Unresolved resource dependencies [AWSEBV2LoadBalancer] in the Resources block of the template – Steven Jan 14 '21 at 23:37

2 Answers2

1

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" }
            }
        }
Marcin
  • 215,873
  • 14
  • 235
  • 294
  • @Steven Hi. Have you had a chance to try the custom resource? – Marcin Jan 17 '21 at 04:55
  • I use json format and could not find a way to inline the code like this. I instead separated out the function into an s3 zip object, and also included the cfnresponse outlined in https://stackoverflow.com/questions/49885243/aws-lambda-no-module-named-cfnresponse – Steven Jan 18 '21 at 00:14
  • 1
    Thanks, Marcin! I had been struggling with this for days and finally was able to do it with your valuable answer. – Steven Jan 18 '21 at 00:14
  • 1
    @Steven Glad to hear it worked out. You may review the `LambdaBasicExecutionRole` permissions. For this answer I made them very permissive. To follow good practice, you would have to reduce them to just what is required to make lambda function work. – Marcin Jan 18 '21 at 00:19
  • 1
    I ended up with a policy applied and removed the reference to the beanstalk full access:"Policies": [{ "PolicyName": "DescribeEnvironment", "PolicyDocument": { "Version" : "2012-10-17", "Statement" : [ { "Effect": "Allow", "Action": [ "elasticbeanstalk:DescribeEnvironmentResources" ], "Resource": { "Fn::Sub": "arn:aws:elasticbeanstalk:${AWS::Region}:${AWS::AccountId}:environment/${Application}/${ApplicationEnvironment}" } } ] } }], – Steven Jan 18 '21 at 04:21
0

Update: This answer is wrong. The resources seem to be available in the other EB config files, but no the template itself


The Wrong Answer:

Looks like Cloudformation gives you access to the resources it creates, even though they're not defined directly in your template.

https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customize-containers-format-resources-eb.html

So to make the association to the load balancer just use "Ref": "AWSEBV2LoadBalancer" or "Ref: "AWSEBLoadBalancer" or whatever

        "WAFAssociation": {
            "Type" : "AWS::WAFv2::WebACLAssociation",
            "Properties" : {
                "Ref" : "AWSEBV2LoadBalancer",
                "WebACLArn" : { "Ref": "WAF" }
            }
        }
Jordan Shurmer
  • 946
  • 7
  • 21
  • My problem was that I had no reference to the ALB as it is automatically created by the beanstalk resource – Steven Apr 22 '22 at 08:51
  • @Steven what I'm saying is that they give you access to it for free. It's automatically created by the beanstalk resource, and it's automatically available in the CF template as well. – Jordan Shurmer Apr 22 '22 at 15:09
  • Is it not working as expected? I'll set up a simple test case today and verify myself – Jordan Shurmer Apr 22 '22 at 15:12
  • Thanks, I’d be keen to know if it worked for you – Steven Apr 23 '22 at 21:03
  • Yeah.. looks like I misunderstood their documentation :( I guess those resources are available in the other EB config files but no the template. Sorry for the completely incorrect answer – Jordan Shurmer Apr 25 '22 at 13:46
  • No worries. Thanks for trying nonetheless – Steven Apr 26 '22 at 20:48