How to get Subnet's associated Route Table ID in Cloud​Formation?

0

My CloudFormation template includes creation of an S3 AWS::EC2::VPCEndpoint. In parameters, users select an already-created VPC, Subnet, and AZ. One of the required parameters for S3 endpoint is Route Table Id. For now, I have it set to request a String of Route Table ID ("rtb-xxxxxx") but would like to simplify it for the user by getting it programmatically.

The question is, how can I instead get a Route Table ID based on selected Subnet ID? AWS::EC2::Subnet does not support return value for Route Table ID associated with it. I know that I could also do that by calling a Lambda but I was hoping to find a more native solution.

The Route Table ID I am looking for is NOT VPC's Main route table.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html

1 Answer
0
Accepted Answer

Custom Resource (or Macro) is the only way to go. Not only does AWS::EC2::Subnet not support it as a return value as you mentioned, but you're also trying to reference an existing resource not created by your template.


Quick sample but do note - DescribeRouteTables will only work if the subnet has been explicitly associated and not for a default route table. That will require extra coding/logic.

AWSTemplateFormatVersion: "2010-09-09"
Description: Subnet Custom Resource
Parameters: 
  TheSubnet:
    Description: Subnet ID
    Type: AWS::EC2::Subnet::Id
    Default: subnet-32432943294

Resources:
  CustomFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.lambda_handler
      Timeout: 30
      Role: !GetAtt 'CustomResourceRole.Arn'
      Runtime: python3.7
      Code:
        ZipFile: |
          import boto3
          import json
          import urllib

          def sendResponse(event, context, responseData, responseStatus="FAILED"):
            response_body = json.dumps({
              "Status": responseStatus,
              "Reason": "See the details in CloudWatch Log Stream: " + context.log_stream_name,
              "PhysicalResourceId": context.log_stream_name,
              "StackId": event["StackId"],
              "RequestId": event["RequestId"],
              "LogicalResourceId": event["LogicalResourceId"],
              "Data":responseData
            })

            # Not using the old .put (deprecated)
            enc_body = response_body.encode('utf-8')
            opener = urllib.request.build_opener(urllib.request.HTTPHandler)
            request = urllib.request.Request(event['ResponseURL'], data=enc_body)
            request.add_header('Content-Type', '')
            request.add_header('Content-Length', len(response_body))
            request.get_method = lambda: 'PUT'
            response = opener.open(request)
            print("RESPONSE {}: {}".format(response.getcode(), response.msg))


          def lambda_handler(event, context):
            # Delete...
            if event['RequestType'] == 'Delete':
              sendResponse(event, context, {"Message": "DELETE"}, "SUCCESS")
              return '{}'
            
            try:
              ec2 = boto3.client('ec2')
              subnet = event["ResourceProperties"].get("SubnetId")
              resp = ec2.describe_route_tables(
                Filters=
                  [
                    {
                      'Name': 'association.subnet-id',
                      'Values': [subnet]
                    }
                  ]
              )
              print(resp['RouteTables'][0])
              sendResponse(event, context, resp['RouteTables'][0], "SUCCESS")
            except:
              print("ERROR")
              sendResponse(event, context, {"Value": "ERROR"})

  CustomResourceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
          Condition: {}
      Path: /
      Policies:
        - PolicyName: QuerySubnet
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - ec2:DescribeSubnets
                  - ec2:DescribeRouteTables
                Resource: '*'
  
  SubnetQuery:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt CustomFunction.Arn
      SubnetId: !Ref TheSubnet

Outputs:
  RouteTableId:
    Description: Discovered Route Table ID
    Value: !GetAtt SubnetQuery.RouteTableId
AWS
EXPERT
Raphael
answered 4 years ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions