aws-cloudformation / aws-cloudformation/cloudformation-coverage-roadmap

AWS::CloudFormation::CustomResource - Lambda Backed CustomResource's ResourceProperties converts int and boolean properties to strings

Open
#1,037 8 comments 19 reactions 0 assignees View on GitHub
bug
Dominant language
No language data
Stars
1.1k
Forks
62
PR merge metrics
No merged PRs in 30d

Description

### Name of the resource

AWS::CloudFormation::CustomResource

### Resource Name

AWS::CloudFormation::CustomResource

### Issue Description

Created a Custom Resource with boolean and int properties. When these properties are accessed in the Lambda function, they are all converted to string.
```yaml
S3CustomResource:
Type: Custom::S3CustomResource
Properties:
ServiceToken: !GetAtt AWSLambdaFunction.Arn
bucket_name: ferdous-sample-bucket-name-5 ## Additional parameter here
boolean_property: true ## Additional parameter here
integer_property: 14 ## Additional parameter here
```

### Expected Behavior

Although the AWS Cloud formation docs although mentions that the `ResourceProperties` is a JSON Object. And JSON object can contain Integers and Booleans.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html

### Observed Behavior

Conducted a short experiment to verify if the `ResourceProperties` in `event` of the custom resources are passed with the correct data type. Used the following Custom Resource configuration

```yaml
Resources:
SampleS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
DeletionPolicy: Delete

S3CustomResource:
Type: Custom::S3CustomResource
Properties:
ServiceToken: !GetAtt AWSLambdaFunction.Arn
bucket_name:
boolean_property: true
integer_property: 14

AWSLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Description: "Empty an S3 bucket!"
FunctionName: !Sub '${AWS::StackName}-${AWS::Region}-lambda'
Handler: index.handler
Role: !GetAtt AWSLambdaExecutionRole.Arn
Timeout: 360
Runtime: python3.8
Code:
ZipFile: |
import boto3
import cfnresponse

def handler(event, context):
# Get request type
the_event = event['RequestType']
print("The event is: ", str(the_event))

response_data = {}
s3 = boto3.client('s3')

# Retrieve parameters (bucket name)
bucket_name = event['ResourceProperties']['bucket_name']

response_data["bucket_name"] = str(type(event['ResourceProperties']['bucket_name']))
response_data["boolean_property"] = str(type(event['ResourceProperties']['boolean_property']))
response_data["integer_property"] = str(type(event['ResourceProperties']['integer_property']))

try:
cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)

except Exception as e:
print("Execution failed...")
print(str(e))
response_data['Data'] = str(e)
cfnresponse.send(event, context, cfnresponse.FAILED, response_data)

AWSLambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Version: '2012-10-17'
Path: "/"
Policies:
- PolicyDocument:
Statement:
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource: arn:aws:logs:*:*:*
Version: '2012-10-17'
PolicyName: !Sub ${AWS::StackName}-${AWS::Region}-AWSLambda-CW
- PolicyDocument:
Statement:
- Action:
- s3:PutObject
- s3:DeleteObject
- s3:List*
Effect: Allow
Resource:
- !Sub arn:aws:s3:::${SampleS3Bucket}
- !Sub arn:aws:s3:::${SampleS3Bucket}/*
Version: '2012-10-17'
PolicyName: !Sub ${AWS::StackName}-${AWS::Region}-AWSLambda-S3
RoleName: !Sub ${AWS::StackName}-${AWS::Region}-AWSLambdaExecutionRole
```

After applying it, the following logs appear
```
{
"Status": "SUCCESS",
"Reason": "See the details in CloudWatch Log Stream: ...",
"PhysicalResourceId": "...",
"StackId": "...",
"RequestId": "4fe1e63e-dc02-4087-a1e8-541efe0ad011",
"LogicalResourceId": "S3CustomResource",
"NoEcho": false,
"Data": {
"bucket_name": "",
"boolean_property": "",
"integer_property": ""
}
}
```

The properties are always returning String even though they have been passed as String, Boolean and Integer.

### Test Cases

Pass any integer or boolean key value pair in the `Properties` section and you can see that the properties received in the `event['ResourceProperties']` object are all in string format.
```
S3CustomResource:
Type: Custom::S3CustomResource
Properties:
ServiceToken: !GetAtt AWSLambdaFunction.Arn
bucket_name:
boolean_property: true
integer_property: 14
```

### Other Details

Conducted a short experiment to verify if the `properties` of the custom resources are passed with the correct data type if the Custom Resource is applied from local machine, instead of using AILM. Used the following Custom Resource configuration in a test account to verify that.

```yaml
Resources:
SampleS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ferdous-sample-bucket-name-5
DeletionPolicy: Delete

S3CustomResource:
Type: Custom::S3CustomResource
Properties:
ServiceToken: !GetAtt AWSLambdaFunction.Arn
bucket_name: ferdous-sample-bucket-name-5 ## Additional parameter here
boolean_property: true ## Additional parameter here
integer_property: 14 ## Additional parameter here

AWSLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Description: "Empty an S3 bucket!"
FunctionName: !Sub '${AWS::StackName}-${AWS::Region}-lambda'
Handler: index.handler
Role: !GetAtt AWSLambdaExecutionRole.Arn
Timeout: 360
Runtime: python3.8
Code:
ZipFile: |
import boto3
import cfnresponse

def handler(event, context):
# Get request type
the_event = event['RequestType']
print("The event is: ", str(the_event))

response_data = {}
s3 = boto3.client('s3')

# Retrieve parameters (bucket name)
bucket_name = event['ResourceProperties']['bucket_name']

response_data["bucket_name"] = str(type(event['ResourceProperties']['bucket_name']))
response_data["boolean_property"] = str(type(event['ResourceProperties']['boolean_property']))
response_data["integer_property"] = str(type(event['ResourceProperties']['integer_property']))

try:
cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)

except Exception as e:
print("Execution failed...")
print(str(e))
response_data['Data'] = str(e)
cfnresponse.send(event, context, cfnresponse.FAILED, response_data)

AWSLambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Version: '2012-10-17'
Path: "/"
Policies:
- PolicyDocument:
Statement:
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource: arn:aws:logs:*:*:*
Version: '2012-10-17'
PolicyName: !Sub ${AWS::StackName}-${AWS::Region}-AWSLambda-CW
- PolicyDocument:
Statement:
- Action:
- s3:PutObject
- s3:DeleteObject
- s3:List*
Effect: Allow
Resource:
- !Sub arn:aws:s3:::${SampleS3Bucket}
- !Sub arn:aws:s3:::${SampleS3Bucket}/*
Version: '2012-10-17'
PolicyName: !Sub ${AWS::StackName}-${AWS::Region}-AWSLambda-S3
RoleName: !Sub ${AWS::StackName}-${AWS::Region}-AWSLambdaExecutionRole
```

After applying it from a local setup to the `testaccount-ferdous`, the following logs appear
```
{
"Status": "SUCCESS",
"Reason": "See the details in CloudWatch Log Stream: 2022/01/18/[$LATEST]b02a0c9fdb9a4737b2c37f8752eb5590",
"PhysicalResourceId": "2022/01/18/[$LATEST]b02a0c9fdb9a4737b2c37f8752eb5590",
"StackId": "arn:aws:cloudformation:eu-west-1:438171217252:stack/custom-resource-test-5/a0404ef0-783d-11ec-99aa-06a8de4b0d85",
"RequestId": "4fe1e63e-dc02-4087-a1e8-541efe0ad011",
"LogicalResourceId": "S3CustomResource",
"NoEcho": false,
"Data": {
"bucket_name": "",
"boolean_property": "",
"integer_property": ""
}
}
```

The properties are always returning String even though they have been passed as String, Boolean and Integer.
```
S3CustomResource:
Type: Custom::S3CustomResource
Properties:
ServiceToken: !GetAtt AWSLambdaFunction.Arn
bucket_name: ferdous-sample-bucket-name-5 ## Additional parameter here
boolean_property: true ## Additional parameter here
integer_property: 14 ## Additional parameter here
```
Although the AWS Cloud formation docs although mentions that the `ResourceProperties` is a JSON Object. And JSON object can contain Integers and Booleans.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html

The next course of action is to look into the issues of CloudFormation to see if it's already reported. If not, then create an issue and follow up on that. And for the time being, keep using the manual type mapping that we have been doing in the CustomResource Lambda Functions.

Contributor guide

Open the contributing guide

Research direction

Start with the AWS CloudFormation custom-resource request documentation and reproduce the issue using the YAML template and Python Lambda handler shown here. Compare the types in event['ResourceProperties'] with the declared boolean and integer values; done would require determining whether this is an AWS service behavior and documenting or escalating the result, since no repository file or test is identified.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, python
Domain
cloud, infrastructure
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.