join multiple resources in YAML cloudformation - amazon-web-services

Below is my Cloudformation template I want add multiple resources getting below error
Template contains errors.: Template format error: YAML not well-formed. (line 61, column 1)
AWSTemplateFormatVersion: 2010-09-09
Description: >-
This template creates IoT policy - attaches to a device certificate, IoT Topic
Rule- used to forward messages to sns based on service key, and creates
required IAM roles for these.
Parameters:
vpcname:
Type: String
Description: Enter vpcname
vpcnamefirstletterupper:
Type: String
Description: Enter vpcname with camelcase, ex- "Usdevms"
taaccountid:
Type: String
Description: Enter TA AccountID"
Resources:
IoTDaasDeviceRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Join ["",[IoTDaasDeviceRole.,!Ref vpcname]]
MaxSessionDuration : 43200
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
AWS: !Join ["",[!Sub 'arn:aws:iam::${AWS::AccountId}:role/Daas',!Ref vpcnamefirstletterupper,'IotCredentialLambda']]
Service: lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
IoTDaasDevicePolicy:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
Description: >-
This Policy will be attached to the device role and lists the
permissions given to device certificates
ManagedPolicyName: !Join
- ''
- - 'IoTDaasDeviceConnectPolicy.'
- !Ref vpcname
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: 'iot:Connect'
Resource: !Join
- ''
- - !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/'
- '*'
- Effect: Allow
Action: 'iot:Publish'
Resource: !Join
- ''
- - !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/rules/daas_device_events_rule_'
- !Ref vpcname
- '/*'
- Effect: Allow
Action: 'iot:StartNextPendingJobExecution'
Resource: {
!Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:things/','*']],
!Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/things/thingName/jobs/start-next/']],
!Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/$aws/things/thingName/jobs/start-next/accepted']],
!Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/$aws/things/thingName/jobs/start-next/rejected']]
}
- Effect: Allow
Action: 'iot:UpdateJobExecution'
Resource: !Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:things/','*']]
- Effect: Allow
Action: 'execute-api:Invoke'
Resource: !Join ['', [!Sub 'arn:aws:execute-api:${AWS::Region}:',!Ref taaccountid,':hpe5n6k1v8/Test/GET']]
Roles:
- Ref: IoTDaasDeviceRole

The following is incorrect:
Resource: {
!Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:things/','*']],
!Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/things/thingName/jobs/start-next/']],
!Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/$aws/things/thingName/jobs/start-next/accepted']],
!Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/$aws/things/thingName/jobs/start-next/rejected']] }
as it creates a map, but you need a list:
Resource:
- !Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:things/','*']]
- !Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/things/thingName/jobs/start-next/']]
- !Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/$aws/things/thingName/jobs/start-next/accepted']]
- !Join ["",[!Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/$aws/things/thingName/jobs/start-next/rejected']]
Please note that there still can be issues in your template, which are not apparent yet.

You can use the aws cloudformation command line to validate your template, here's the output when running it on the file you provided:
$ aws cloudformation validate-template --template-body file://test.template
An error occurred (ValidationError) when calling the ValidateTemplate operation: Template format error: [/Resources/IoTDaasDevicePolicy/Type/PolicyDocument/Statement/2/Resource] map keys must be strings; received a map instead
The validation succeeded when I corrected the faulty bloc using Marcin's answer:
{
"Parameters": [
{
"ParameterKey": "vpcname",
"NoEcho": false,
"Description": "Enter vpcname"
},
{
"ParameterKey": "taaccountid",
"NoEcho": false,
"Description": "Enter TA AccountID\""
},
{
"ParameterKey": "vpcnamefirstletterupper",
"NoEcho": false,
"Description": "Enter vpcname with camelcase, ex- \"Usdevms\""
}
],
"Description": "This template creates IoT policy - attaches to a device certificate, IoT Topic Rule- used to forward messages to sns based on service key, and creates required IAM roles for these.",
"Capabilities": [
"CAPABILITY_NAMED_IAM"
],
"CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]"
}

Related

ARN as a parameter in Cloud Formation Stack

I wanted to use the ARN as parameter input to cloudformation stack resources EventRuleRegion1 - Target as well as EventBridgeIAMrole , but it is not working. when i call with Ref function
Original ARN
arn:aws:events:ap-southeast-2:123456789123:event-bus/central-eventbus-sydney
When i give the arn directly in code its working fine.
Code
AWSTemplateFormatVersion: 2010-09-09
Parameters:
EventBridgeName:
Description: Enter the Event Bridge Name
Type: String
Default: ec2-lifecycle-events
EventBusName:
Description: Enter the Central Event Bus Name
Type: String
Default: central-eventbus-sydney
EventBusArn:
Description: Enter the ARN of Central Event Bus
Type: String
Default: arn:aws:events:ap-southeast-2:123456789123:event-bus/central-eventbus-sydney
Monitoringaccount:
Description: Enter the Monitoring AWS account number
Type: String
Default: 123456789123
Resources:
EventRuleRegion1:
Type: AWS::Events::Rule
Properties:
Description: Event rule to send events to monitoring account event bus
EventBusName: default
EventPattern:
source:
- aws.ec2
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- "running"
- "stopped"
- "terminated"
Name: !Ref EventBridgeName
State: ENABLED
Targets:
- Arn: >-
- !Join [ "", [ !Sub "arn:aws:events:${AWS::Region}:123456789123:event-bus/",!Ref EventBusName ] ]
Id: !Ref EventBusName
RoleArn: !GetAtt
- EventBridgeIAMrole
- Arn
EventBridgeIAMrole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: !Sub events.amazonaws.com
Action: 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: PutEventsDestinationBus
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'events:PutEvents'
Resource:
- >-
- !Join [ "", [ !Sub "arn:aws:events:${AWS::Region}:123456789123:event-bus/",!Ref EventBusName ] ]
Error
Parameter - !Join [ "", [ !Sub "arn:aws:events:${AWS::Region}:123456789123:event-bus/",!Ref EventBusName ] ] is not valid. Reason: Provided Arn is not in correct format. (Service: AmazonCloudWatchEvents; Status Code: 400; Error Code: ValidationException; Request ID: 0d52a1d6-095e-44f7-9455-b7481dc4fb8d; Proxy: null)
The use of >- will result in literal strings, not evaluation of your CFN functions (join, ref). It should be:
Targets:
- Arn: !Join [ "", [ !Sub "arn:aws:events:${AWS::Region}:123456789123:event-bus/",!Ref EventBusName ] ]

AWS CFT How to append list parameter to list of static string options

Trying to create a bucket policy which will allow same account roles and cross account roles.
Here CicdDeploymentRoleArn is a list of cross account role arns.
Parameters:
CicdDeploymentRoleArn:
Type: CommaDelimitedList
Description: >-
The ARN of the CICD deployment role that will need access to the S3
Default: >-
arn:aws:iam::xxx:role/preprod,arn:aws:iam::xxx:role/prod
InfraBucketPolicy:
Properties:
Bucket: !Ref InfraBucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- "s3:*"
Effect: Allow
Principal:
AWS:
- !Sub "arn:aws:iam::${AWS::AccountId}:role/human-role/PowerUser2"
- !Ref CicdDeploymentRoleArn # How to refer list here ?
Resource:
- !Join [ "", [ !GetAtt InfraBucket.Arn, "/*" ] ]
Type: "AWS::S3::BucketPolicy"
You can do this with combination of Split and Join (have to be careful about indentation):
InfraBucketPolicy:
Properties:
Bucket: !Ref InfraBucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- "s3:*"
Effect: Allow
Principal:
AWS:
!Split
- ","
- !Join
- ","
- - !Sub "arn:aws:iam::${AWS::AccountId}:role/human-role/PowerUser2"
- !Join [",", !Ref CicdDeploymentRoleArn]
Resource:
- !Join [ "", [ !GetAtt InfraBucket.Arn, "/*" ] ]
Type: "AWS::S3::BucketPolicy"

Cloudformation condition on parameter in Role policy

I am creating iam role and its managed policy. In managed policy I want to give allow access to invoke API(Gateway). This template is going to execute on at least 6 environments I want to use condition based approach to assign API gateway url in resource so that template will be easy to maintain and execute.
Based on type of environment API url is going to change.
AWSTemplateFormatVersion: 2010-09-09
Description: >-
This template creates Role and policy
Parameters:
envname:
Type: String
Description: Enter envname
AllowedValues:
- dev
- dev2
- sit
- uat
- prf
- prod
prodaccountid:
Type: String
Description: Enter Prod AccountID
Resources:
DeviceRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: 'DeviceRole'
MaxSessionDuration : 43200
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
AWS: !Join ["",[!Sub 'arn:aws:iam::${AWS::AccountId}:role/',!Ref envname,'_Role']]
Service: lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
- Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:user/test-user'
Service: lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
DevicePolicy:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
Description: >-
This Policy will be attached to the device role
ManagedPolicyName: !Join
- ''
- - 'DeviceConnectPolicy'
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: 'execute-api:Invoke'
**Resource:
- !Join ["", [!Sub 'arn:aws:execute-api:${AWS::Region}:',!Ref prodaccountid,':aaaaaaaaaa/Test/PUT']]
- !Join ["", [!Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:bbbbbbbbbb/*/PUT/*/*/*/v2']]
- !Join ["", [!Sub 'arn:aws:execute-api:${AWS::Region}:',!Ref prodaccountid,':cccccccccc/*/PUT/v2/s']]
- !Join ["", [!Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:dddddddddd/*/PUT/*/*/*/v2']]**
Roles:
- Ref: DeviceRole
I want to use conditions in Resource.
Thanks in advance

Can't link lambda with cloudwatch event trigger

I'm creating an ASG group which has a lifecyclehook for termination:
LifecycleHook:
Type: AWS::AutoScaling::LifecycleHook
Properties:
AutoScalingGroupName: !Ref NodeGroup
DefaultResult: CONTINUE
HeartbeatTimeout: 60
LifecycleHookName: !Sub "${AWS::StackName}-lifecycle-hook"
LifecycleTransition: autoscaling:EC2_INSTANCE_TERMINATING
Now I create a lambda function as well:
LambdaCreation:
Type: "AWS::Lambda::Function"
Properties:
Handler: "lambda_function.lambda_handler"
Environment:
Variables:
aws_region : !Ref AWSRegion
Role: !GetAtt LambdaExecutionRole.Arn
Code:
S3Bucket: !Ref LambdaCodeBucket
S3Key: "lambda-functions/function.zip"
Runtime: "python3.6"
Timeout: 60
On cloudwatch events, i created a rule for said event:
CloudwatchEvent:
Type: AWS::Events::Rule
Properties:
Description: ASG scale-in event to lambda
EventPattern: {
"source": [
"aws.autoscaling"
],
"detail-type": [
"EC2 Instance-terminate Lifecycle Action"
],
"detail": {
"AutoScalingGroupName":
[
{
"Fn::ImportValue" :
{
"Fn::Sub" : "${RootStackName}-nodes-asg-name"
}
}
]
}
}
State: ENABLED
Targets:
-
Arn:
!GetAtt LambdaCreation.Arn
Id:
!Ref LambdaCreation
But the lambda is never triggered.
Now, on AWS console I don't see a trigger on the designer. But if i add manually a cloudwatch trigger for the created rule, it starts working...
Why is the trigger on the lambda side not created? What am I missing?
Thanks all!
I faced the exact same frustration. Only difference is that I was using terraform but that's irrelavant.
You are missing this:
{
"Type" : "AWS::Lambda::Permission",
"Properties" : {
"Action" : String,
"EventSourceToken" : String,
"FunctionName" : String,
"Principal" : String,
"SourceAccount" : String,
"SourceArn" : String
}
}
The reason the "manual way" works because it creates the trigger AND the permission. When you provision stuff using IaC tools like Cloudformation/terraform, you need to explicitly specify this Lambda permission object.
The below code snippet creates a lambda function and creates a cloudwatch event to trigger the lambda function with necessary privileges.
LambdaExecutionRole:
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:*:*:*
- Effect: Allow
Action:
- s3:ListBucket
Resource: !Join [ '', [ 'arn:aws:s3:::', !Ref LambdaS3Bucket ] ]
- Effect: Allow
Action:
- s3:GetObject
Resource: !Join [ '', [ 'arn:aws:s3:::', !Ref LambdaS3Bucket, '/*' ] ]
- Effect: Allow
Action:
- sts:GetCallerIdentity
Resource: '*'
LambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Description: "Lambda function"
FunctionName: !Ref LambdaFunctionName
Handler: !Ref LambdaHandler
Runtime: !Ref LambdaRuntime
Timeout: !Ref LambdaTimeout
MemorySize: !Ref LambdaMemorysize
Role: !GetAtt LambdaExecutionRole.Arn
Code:
S3Bucket: !Ref LambdaS3Bucket
S3Key: !Ref LambdaS3BucketKey
Environment:
Variables:
time_interval_in_hours: !Ref TimeIntervalInHours
DependsOn: LambdaExecutionRole
CleanupEventRule:
Type: AWS::Events::Rule
Properties:
Description: "Cloudwatch Rule"
ScheduleExpression: !Ref CloudwatchScheduleExpression
State: !Ref CloudWatchEventState
Targets:
- Arn: !Sub ${LambdaFunction.Arn}
Id: "CleanupEventRule"
DependsOn: LambdaFunction
LambdaSchedulePermission:
Type: AWS::Lambda::Permission
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !Sub ${LambdaFunction.Arn}
Principal: 'events.amazonaws.com'
SourceArn: !Sub ${CleanupEventRule.Arn}
DependsOn: LambdaFunction

AWS - Assign an IAM profile based on a Mapping lookup in CF template

In my Cloud Formation template I have IAM Mappings for different environments:
Mappings:
EnvironmentToIAMInstanceProfileARN:
dev:
Profile: [ "arn:aws:iam::0000000000:role/AnInstanceProfile" ]
test:
Profile: [ "arn:aws:iam::0000000001:role/AppServerInstanceProfile",
"arn:aws:iam::0000000001:role/AppProvisioningRole"]
I'm creating an S3 bucket and need to provide the Principal the IAM Profile:
AppS3BucketPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref S3NameParam
PolicyDocument:
Statement:
- Sid: 'Restrict access to the IAM Instance ARN'
Effect: Allow
Principal: '*' # !FindInMap [EnvironmentToIAMInstanceProfileARN, !Ref 'EnvType', Profile]
Action:
- 's3:GetBucketAcl'
- 's3:GetBucketLocation'
- 's3:GetObject'
- 's3:ListBucket'
- 's3:PutObject'
Resource:
- !Join
- ''
- - 'arn:aws:s3:::'
- !Ref S3NameParam
- ''
- !Join
- ''
- - 'arn:aws:s3:::'
- !Ref S3NameParam
- /*
If I assign '*' to Prinicpal it works, however I'm trying to look up the mapping:
Principal: !FindInMap [EnvironmentToIAMInstanceProfileARN, !Ref 'EnvType', Profile]
This doesn't work and results in error:
Invalid bucket policy syntax. (Service: Amazon S3; Status Code: 400;
Error Code: MalformedPolicy;
Does anyone know how I can do this, or why it fails?
ps The EnvType Parameter does exist:
Parameters:
EnvType:
Description: Environment Name
Default: test
Type: String
AllowedValues: [dev, test, prod]
According to this article the syntax needs to have Service.
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html
JSON:
"Principal": {
"Service": [
"elasticmapreduce.amazonaws.com",
"datapipeline.amazonaws.com"
]
However, following some other documentation I worked out its AWS not Service:
JSON:
"Principal": {
"AWS": [
"elasticmapreduce.amazonaws.com",
"datapipeline.amazonaws.com"
]
Solution in YAML:
Principal:
AWS:
!FindInMap [EnvironmentToIAMInstanceProfileARN, !Ref 'EnvType', Profile]