Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to transform CommaDelimitedList parameter to build ARNs in CloudFormation

I have a input parameter that is a list of role names:

Parameters:
  UserRoles:
    Type: CommaDelimitedList
    Default: ""

Now I want to use these roles in a policy document principal. If it would be only 1 Role, I would do:

        Principal:
          AWS:
            - !Join
              - ''
              - - 'arn:aws:iam::'
                - !Ref 'AWS::AccountId'
                - ':role/'
                - !Ref UserRole

But now I want to do that for a variable number of roles. So I need some sort of "Fn::Map" function on the list of strings allowing me to transform the role names into Arns.

Is that possible?

like image 404
Nathan Avatar asked Nov 17 '22 11:11

Nathan


1 Answers

There's no immediate solution using CloudFormation native constructs, however you can build one by using a macro.

You can find a full example below. In summary, the solution has two components:

  1. A CloudFormation stack which creates a macro for the string manipulation you require
  2. A CloudFormation stack which deploys your resources. This stack consumes the macro deployed by the first stack

The example below addresses the direct question being asked here, however it should be useful to the general reader who wants to do custom string manipulation.


Building a custom macro for string manipulation

The following template creates a macro backed by a lambda function. When the macro is invoked, the lambda function is executed.

The macro (and thus the lambda), takes as inputs the list of comma separated roles (e.g. role1,role2) and the AWS account ID, and returns the formatted IAM roles (e.g. [arn:aws:iam::12345678910:role/role1,arn:aws:iam::12345678910:role/role2]).

This is the full template:

AWSTemplateFormatVersion: 2010-09-09
Resources:
  TransformExecutionRole:
    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: ['logs:*']
                Resource: 'arn:aws:logs:*:*:*'
  TransformFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import traceback
          def handler(event, context):
              response = {
                  "requestId": event["requestId"],
                  "status": "success"
              }
              try:
                  role_names = event["params"]["RoleNames"]
                  account_id = str(event["params"]["AccountId"])

                  role_formatter = lambda role : "arn:aws:iam::" + account_id + ":role/" + role
                  formatted_roles = list(map(role_formatter,role_names))

                  response["fragment"] = formatted_roles

              except Exception as e:
                  traceback.print_exc()
                  response["status"] = "failure"
                  response["errorMessage"] = str(e)
              return response
      Handler: index.handler
      Runtime: python3.6
      Role: !GetAtt TransformExecutionRole.Arn
  TransformFunctionPermissions:
    Type: AWS::Lambda::Permission
    Properties:
      Action: 'lambda:InvokeFunction'
      FunctionName: !GetAtt TransformFunction.Arn
      Principal: 'cloudformation.amazonaws.com'
  Transform:
    Type: AWS::CloudFormation::Macro
    Properties:
      Name: 'FormatIamRoles'
      Description: Provides various string processing functions
      FunctionName: !GetAtt TransformFunction.Arn

Consuming a custom macro for string manipulation

The following template illustrates how the custom macro previously deployed can be used for creating a policy for an S3 bucket.

The template takes as inputs the name of an S3 bucket, and a comma separated list of IAM role names.

The macro (see usage of 'Fn::Transform'), takes as inputs the comma separated list of IAM role names and the AWS account id. It returns the formatted list of IAM roles and uses it to create the policy for the specified S3 bucket.

Parameters:
  UserRoles:
    Type: CommaDelimitedList
    Default: "role1,role2"
  MyBucket:
    Type: String
    Default: my-bucket
Resources:
  MyBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref MyBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: MyRoleAllow
            Effect: Allow
            Principal: 
              AWS: 
                'Fn::Transform':
                  - Name: 'FormatIamRoles'
                    Parameters:
                      RoleNames: !Ref UserRoles
                      AccountId: !Ref 'AWS::AccountId'
            Action:
              - s3:*
            Resource: !Sub arn:aws:s3:::${MyBucket}/*

on completion of the stack deployment, you'll find that the IAM roles were added to the bucket:

enter image description here

like image 88
Paolo Avatar answered Dec 20 '22 20:12

Paolo