AWS cloud formation (Fn::ForEach - AWS CloudFormation ) how to use with Parameter store

0

I am working to build CFT where I will pass IP addresses in the code (yaml) to build resolver rules. I have stored IPs in aws parameter store as comma separated strings as 10.10.0.200,10.10.0.201. Now I am trying to refer these IPs one by one in my code . How can i do that ?

Code snippet given below but when i am using this - IP: "{{resolve:ssm:/core/bmas-ip-address:1}}", Its actually fetching the whole string value as IP address and cft ,and of-course failing, My question is can i run it in loop with ForEach CFN function ? Does anyone has any similar solution developed ? Please let me know if any questions.

(BindResolverRule: Type: AWS::Route53Resolver::ResolverRule Properties: DomainName: !Ref DomainName Name: !Ref OutBoundRuleName1 ResolverEndpointId: !Ref OutboundResolverEp RuleType: !Ref RuleType Tags: - Key: Name Value: core-network-forward-all - TargetIp - IP: "{{resolve:ssm:/core/bmas-ip-address:1}}" Port: 53)

asked 2 months ago318 views
1 Answer
1
Accepted Answer

You can use a Lambda-backed custom resource. During the Stack creation, the Lambda goes to the Parameter Store, gets a list of strings, parses it and returns back to the CloudFormation

Parameters:
  ParameterPath:
    Type: String
    Default: /core/bmas-ip-address
    Description: Parameter path containing comma-separated list of IP addresses

Resources:
  CustomResolverRuleUpdaterFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |

          import json
          import logging
          import signal
          import urllib3
          import boto3
          
          LOGGER = logging.getLogger()
          LOGGER.setLevel(logging.INFO)
          
          def handler(event, context):
              try:
                  LOGGER.info('REQUEST RECEIVED:\n %s', event)
                  LOGGER.info('REQUEST RECEIVED:\n %s', context)
                  if event['RequestType'] == 'Create':
                      LOGGER.info('CREATE!')
          
                      ssm_parameter_path = event['ResourceProperties']['ParameterPath']
                      ssm_client = boto3.client('ssm')
                      response = ssm_client.get_parameter(Name=ssm_parameter_path, WithDecryption=True)
                      ip_list = response['Parameter']['Value'].split(',')
                        
                      response_data = {
                          'TargetIps': [{'Ip': ip, 'Port': "53"} for ip in ip_list]
                      }    
          
                      send_response(event, context, "SUCCESS", response_data)
          
          
                  elif event['RequestType'] == 'Update':
                      LOGGER.info('UPDATE!')
                      send_response(event, context, "SUCCESS",
                                    {"Message": "Resource update successful!"})
                  elif event['RequestType'] == 'Delete':
                      LOGGER.info('DELETE!')
                      send_response(event, context, "SUCCESS",
                                    {"Message": "Resource deletion successful!"})
                  else:
                      LOGGER.info('FAILED!')
                      send_response(event, context, "FAILED",
                                    {"Message": "Unexpected event received from CloudFormation"})
              except Exception as e:
                  LOGGER.info('FAILED!')
                  send_response(event, context, "FAILED", {"Message": "Exception during processing: {}".format(str(e))})
          
          
          def send_response(event, context, response_status, response_data):
              '''Send a resource manipulation status response to CloudFormation'''
              response_body = json.dumps({
                  "Status": response_status,
                  "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": response_data
              })
          
              LOGGER.info('ResponseURL: %s', event['ResponseURL'])
              LOGGER.info('ResponseBody: %s', response_body)
          
              http = urllib3.PoolManager()
              response = http.request('PUT', event['ResponseURL'], body=response_body, headers={'Content-Type': ''})
              LOGGER.info("Status code: %s", response.status)
              LOGGER.info("Status message: %s", response.reason)
          
          
          def timeout_handler(_signal, _frame):
              '''Handle SIGALRM'''
              raise Exception('Time exceeded')
          
          
          signal.signal(signal.SIGALRM, timeout_handler)

      Handler: index.handler
      Role: !GetAtt CustomResolverRuleUpdaterRole.Arn
      Runtime: python3.8

  CustomResolverRuleUpdaterRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CustomResolverRuleTargetIpPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ssm:GetParameter
                Resource: "*"
        - PolicyName: WriteLogs
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                - logs:*
                Resource: arn:aws:logs:*:*:*

  CustomResolverRuleUpdater:
    Type: Custom::ResolverRuleUpdater
    Properties:
      ServiceToken: !GetAtt CustomResolverRuleUpdaterFunction.Arn
      ParameterPath: !Ref ParameterPath

  MyResolverRule:
    Type: AWS::Route53Resolver::ResolverRule
    Properties:
      DomainName: demo1.com
      RuleType: FORWARD
      ResolverEndpointId: rslvr-out-ce*****4ca
      TargetIps: !GetAtt CustomResolverRuleUpdater.TargetIps

Outputs:
  ResolverRuleId:
    Value: !GetAtt MyResolverRule.ResolverRuleId
profile picture
EXPERT
answered 2 months ago
profile picture
EXPERT
Artem
reviewed a month 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