CloudFormation create Stack only if Condition is valid and show custom error message if not valid - amazon-web-services

I am trying to create CF template that will ask users if RDS Instance and SecurityGroup exists and if they select Yes, then create the stack. If not, warn user that to create RDS Instance and SecurityGroup before creating the EC2 Stack.
Parameters:
IsRDSCreated:
Description: Ensure that the RDS Instance is already created
Default: No
Type: String
AllowedValues:
- Yes
- No
IsRDSSGCreated:
Description: Ensure that the RDS Security Group exists
Default: No
Type: String
AllowedValues:
- Yes
- No
Conditions:
ShouldCreateEC2Resource: !And
- !Equals [!Ref IsRDSCreated, Yes]
- !Equals [!Ref IsRDSSGCreated, Yes]
Resources:
EC2Instance:
Type: AWS::EC2::Instance
Condition: ShouldCreateEC2Resource
.....
.....
.....
.....
At the moment, when I try to create after both the parameter is set to False, I get Template validation error: Template format error: Unresolved resource dependencies [EC2Instance] in the Resources block of the template.
How can I notify users with some kind of error/message that when they select False and run this, to make sure both RDS instance and RDS SG present before creating this stack.
Please suggest if there any other ways or methods of accomplishing this stack.

Related

ALB Cloudformation multiple conditions with AND in Listener rule

I am trying to create a cloudformation template to create ALB with listener rules with below conditions:
However, I am getting invalid parameter error when template adds these rules, I can add them in the UI but not via cloudformation.
Error:
Properties validation failed for resource ALBListenerRule2 with message: #/Conditions/1: extraneous key [sourceIpConfig] is not permitted
template:
`ALBListenerRule2:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref TargetGroupWebserver
Type: forward
Conditions:
- Field: path-pattern
PathPatternConfig:
Values:
- /user/signin
- /user/signup
- /flight
- /user/gift2
- Field: source-ip
sourceIpConfig:
Values:
- !GetAtt My.PublicIp
ListenerArn: !Ref ALBListener
Priority: 1`
I couldn't find a document to add if/or/and conditions in the template.
I couldn't find a document to add if/or/and conditions in the template. template works when using the single condition in the template./
Your error is because you have sourceIpConfig, instead of SourceIpConfig.

Catch event when an SSM-agent becomes active

I want to trigger a lambda whenever a new EC2 instance is registred in SSM's Fleet Manager (meaning the instance can be connected to using SSM), however I can't find what pattern to use in EventBridge.
Within EventBridge, I tried using the following pattern I found in the docs (so far its looks like the closest thing to my goal):
{
"source": ["aws.ssm"],
"detail-type": ["Inventory Resource State Change"]
}
However when I create a new EC2 and wait for its SSM agent to become active, it still doesn't trigger the above pattern.
Any idea how to catch this kind of event?
I think you have to go through CloudTrail API call.
Please find below a CloudFormation template I used in the past that was working. Please note that it just provides the SSM resources. You need to add your own SQS queue as well (see SQS.ARN) and I've used the association with the tag registration set to enabled. So that if you have a lambda function connected, you can set it to false so if the instance connect again, it won't go to the same process again.
AWSTemplateFormatVersion: "2010-09-09"
Description: >
SSM Registration event
# Description of the resources to be created.
Resources:
RegistrationDocument:
Type: AWS::SSM::Document
Properties:
DocumentType: Command
Content:
schemaVersion: "2.2"
description: >
An Automation Document ran by registered instances that gathers their software inventory
and automatically updates their AWS SSM Agent to the latest version.
mainSteps:
- name: GatherSoftware
action: aws:softwareInventory
- name: Sleep
action: aws:runShellScript
inputs:
runCommand:
- sleep 20 || true
- name: UpdateAgent
action: aws:updateSsmAgent
inputs:
agentName: amazon-ssm-agent
source: https://s3.{Region}.amazonaws.com/amazon-ssm-{Region}/ssm-agent-manifest.json
allowDowngrade: "false"
RegistrationDocumentAssociation:
Type: AWS::SSM::Association
Properties:
AssociationName: !Sub registration-association-${AWS::StackName}
Name: !Ref RegistrationDocument
Targets:
- Key: tag:registration
Values:
- enabled
RegistrationEventRule:
Type: AWS::Events::Rule
Properties:
Description: >
Events Rule that monitors registration of AWS SSM instances
and logs them to an SQS queue.
EventPattern:
source:
- aws.ssm
detail-type:
- AWS API Call via CloudTrail
detail:
eventName:
- UpdateInstanceAssociationStatus
requestParameters:
associationId:
- !Ref RegistrationDocumentAssociation
executionResult:
status:
- Success
State: ENABLED
Targets:
- Arn: SQS.ARN
Id: SqsRegistrationSubscription
SqsParameters:
MessageGroupId: registration.events

Overwritten Parameters for AWS Stack Set Instances Are Not Applied

The below CF template is indented to create a stack set containing one role with specific parameter values depending on the OU where the account is located. So if the account is located / created in OU A the trust relationship of the debug role must point to account "111111111111".
AWSTemplateFormatVersion: "2010-09-09"
Description: Debug Role
Resources:
StackSet:
Type: AWS::CloudFormation::StackSet
Properties:
Description: Debug Role Stack Set
PermissionModel: SERVICE_MANAGED
Capabilities:
- CAPABILITY_NAMED_IAM
AutoDeployment:
Enabled: true
RetainStacksOnAccountRemoval: false
Parameters:
- ParameterKey: TrustAccountId
ParameterValue: "123456789123" # default value
OperationPreferences:
RegionConcurrencyType: PARALLEL
FailureToleranceCount: 0
MaxConcurrentPercentage: 100
StackInstancesGroup:
- DeploymentTargets:
OrganizationalUnitIds:
- ou-aaaa # A
ParameterOverrides:
- ParameterKey: "TrustAccountId"
ParameterValue: "111111111111"
Regions:
- 'eu-central-1'
- DeploymentTargets:
OrganizationalUnitIds:
- ou-bbbb # B
ParameterOverrides:
- ParameterKey: "TrustAccountId"
ParameterValue: "222222222222"
Regions:
- 'eu-central-1'
StackSetName: debug-role-stack-set
TemplateBody: |
AWSTemplateFormatVersion: 2010-09-09
Description: Debug Role
Parameters:
TrustAccountId:
Description: Trust to account
Type: String
AllowedPattern: \d{12}
Resources:
DebugRole:
Type: AWS::IAM::Role
Description: "Role for debugging accounts"
Properties:
RoleName: DebugRole
Path: /
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS:
- Ref: TrustAccountId
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AdministratorAccess
Now the issue is that all account within the stack instances group, regardless if existing or new account, get the stack applied with the default parameter value "123456789123". So all accounts get the role having the incorrect trust relationship to "123456789123".
The only way to apply the correct value based on the OU is to update the stack instances with the CLI.
aws cloudformation update-stack-instances --stack-set-name debug-role-stack-set --deployment-targets OrganizationalUnitIds=ou-aaaa --regions eu-central-1 --operation-preferences FailureToleranceCount=0,MaxConcurrentCount=5 --parameter-overrides Pa
rameterKey=TrustAccountId,ParameterValue=111111111111
Is there any way to apply the correct parameter values during the stack creation without updating them afterwards? Because for every new account all stack set instances needs to be updated.
Since the stack instances have not been executed with the correct parameter value, I ended up having one stack set for each OU. Of course, this solution is only feasible for static environments. So in my case, if new OUs pop up new stack sets must be created.

How to assign multiple Targets to a AWS Loadbalancer TargetGroup Target in a single line?

How can I pass a parameter (of type CommaDelimitedList or String) to a single TargetGroup's Target: -Id: property and have it iterate through and assign each instance in the list?
Currently right now I am able to pass one or more Target-Servers to a Target-Group by having each Target as it's own -Id: property. Code block #1 below works, how can I make code block #2 work?
MyTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
#[All the Target Group properties that aren't in question]
Targets:
- Id: "i-000AWS001"
Or
- Id: !Ref WebServer1
#Two Files:
# resource.yml - Cloudformation yaml file
# conf_resource.json - json paramaters file
# Parameter value in the Conf file
{
"WebServers":"i-000AWS001,i-000AWS002,i-000AWS003,i-000AWS004,i-000AWS005"
}
# ..
#Target Group Resource
Parameters:
WebServers:
Type: CommaDelimitedList
Default: "###WebServers###"
Description: " A comma delimited list of AWS Ec2 Webserver instances"
Resources:
MyAlb:
ALBProperties:
#[All the Application Load balancer properties that aren't in question]
MyTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
#[All the Target Group properties that aren't in question]
Targets:
- Id: !Split[ "," , !Ref WebServers ]
#Also tried - Id: !Ref [ !Split[ "," , !Ref WebServers ] ]
AWS TargetGroup Documentation:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-targetgroup.html#cfn-elasticloadbalancingv2-targetgroup-targets
You can't do this, such list operations are not supported in CloudFormation.
But, normally what you do is to place your instances in AutoScaling Group. This way, the group will be automatically registering instances with your balancer, and you don't have to worry about individual instance ids.

Cloudformation & Parameter Store: How to select parameter for the environment

I want to read the URL of my database from parameter store in my CloudFormation template. This is easy enough for a single URL, but I can't figure out how to change the URL with different environments.
I have four environments (development, integration, pre-production and production) and their details are stored on four different paths in Parameter Store:
/database/dev/url
/database/int/url
/database/ppe/url
/database/prod/url
I now want to pick the correct Database URL when deploying via CloudFormation. How can I do this?
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- int
- ppe
- prod
DatabaseUrl:
Type: 'AWS::SSM::Parameter::Value<String>'
# Obviously the '+' operator here won't work - so what do I do?
Default: '/database/' + Environment + '/url'
This feature isn't as neat as one would wish. You have to actually pass name/path of each parameter that you want to look up from the parameter store.
Template:
AWSTemplateFormatVersion: 2010-09-09
Description: Example
Parameters:
BucketNameSuffix:
Type: AWS::SSM::Parameter::Value<String>
Default: /example/dev/BucketNameSuffix
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub parameter-store-example-${BucketNameSuffix}
If you don't pass any parameters to the template, BucketNameSuffix will be populated with the value stored at /example/dev/BucketNameSuffix. If you want to use, say, a prod value (pointed to by /example/prod/BucketNameSuffix), then you should specify value for parameter BucketNameSuffix, but instead of passing the actual value you should pass the alternative name of the parameter to use, so you'd pass /example/prod/BucketNameSuffix.
aws cloudformation update-stack --stack-name example-dev \
--template-body file://./example-stack.yml
aws cloudformation update-stack --stack-name example-prod \
--template-body file://./example-stack.yml \
--parameters ParameterKey=BucketNameSuffix,ParameterValue=/example/prod/BucketNameSuffix
A not so great AWS blog post on this: https://aws.amazon.com/blogs/mt/integrating-aws-cloudformation-with-aws-systems-manager-parameter-store/
Because passing million meaningless parameters seems stupid, I might actually generate an environment-specific template and set the right Default: in the generated template so for prod environment Default would be /example/prod/BucketNameSuffix and then I can update the prod stack without passing any parameters.
You can populate CloudFormation templates with parameters stored in AWS Systems Manager Parameter Store using Dynamic References
In this contrived example we make two lookups using resolve:ssm and replace the environment using !Join and !Sub
AWSTemplateFormatVersion: 2010-09-09
Parameters:
Environment:
Type: String
Default: prod
AllowedValues:
- prod
- staging
Resources:
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
ContainerDefinitions:
Image: !Join
- ''
- - 'docker.io/bitnami/redis#'
- !Sub '{{resolve:ssm:/app/${Environment}/digest-sha/redis}}'
Environment:
- Name: DB_HOST
Value: !Sub '{{resolve:ssm:/app/${Environment}/db-host}}'
You can make use of Fn::Join here.
Here is some pseudo code.
You will have to take Environment as an parameter, which you are already doing.
Creating the required string in the resources where DatabaseUrl is required.
Resources :
Instance :
Type : 'AWS::Some::Resource'
Properties :
DatabaseURL : !Join [ "", [ "/database/", !Ref "Environment" , "/url ] ]
Hope this helps.
Note: You cannot assign value to a Parameter dynamically using some computation logic. All the values for defined parameters should be given as Input.
I like Fn::Sub, which is much cleaner and easy to read.
!Sub "/database/${Environment}/url"
I am stuck into the same problem, below are my findings:
We can compose values and descriptions while writing into the SSM Parameter Store from CloudFormation like below :
LambdaARN:
Type: AWS::SSM::Parameter
Properties:
Type: String
Description: !Sub "Lambda ARN from ${AWS::StackName}"
Name: !Sub "/secure/${InstallationId}/${AWS::StackName}/lambda-function-arn"
Value: !GetAtt LambdaFunction.Arn
We can not compose values/defaults to looking for in SSM Parameter Store. Like below:
Parameters:
...
LambdaARN:
Type: Type: AWS::SSM::Parameter::Value<String>
Value: !Sub "/secure/${InstallationId}/teststack/lambda-function-arn"
This is not allowed as per AWS Documentation[1]. Both(Sub/Join) Functions won't work. Following is the error which I was getting:
An error occurred (ValidationError) when calling the CreateChangeSet
operation: Template format error: Every Default member must be a
string.
Composing and passing values while creating stacks can be done like this:
Parameters:
...
LambdaARN:
Type: Type: AWS::SSM::Parameter::Value<String>
....
$ aws cloudformation deploy --template-file cfn.yml --stack-name mystack --parameter-overrides 'LambdaARN=/secure/devtest/teststack/lambda_function-arn'
If you add your custom tags while putting the values in the Parameter Store, it will overwrite the default tags added by CFN.
Default Tags:
- aws:cloudformation:stack-id
- aws:cloudformation:stack-name
- aws:cloudformation:logical-id
Every time we update the values in parameter store, it creates a new version, which is beneficial when we are using DynamicResolvers, this can serve as a solution to the problem in the question like
{{ resolve:ssm:/my/value:1}}
The last field is the version. Where different versions can point to different environments.
We are using versions with the parameters, and adding the labels to them[2], this can't be done via CFN[3], only possible way via CLI or AWS Console. This is AWS's way of handling multiple environments.
[1] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
[2] https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-labels.html
[3] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-parameter.html