Cloudformation: substitute variable in map key - aws-cloudformation

I have a role defined like this:
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
AWSAccountId:
Type: String
OidcProvider:
Type: String
AppNamespace:
Type: String
AppServiceAccountName:
Type: String
Resources:
CloudWatchRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Federated:
- !Join ["", [ "arn:aws:iam::", !Ref AWSAccountId, ":oidc-provider/", !Ref OidcProvider ] ]
Action:
- "sts:AssumeRoleWithWebIdentity"
Condition:
StringEquals:
!Sub ${OidcProvider}:sub: "system:serviceaccount:${AppNamespace}:${AppServiceAccountName}"
My challenge is how to substitute parameters in the StringEquals section. Everything works in the Federated block. But in the StringEquals block I couldn't get join or sub to work.
With the code as is, I get error message:
An error occurred (ValidationError) when calling the CreateStack operation:
Template format error[/Resources/CloudWatchRole/Properties/AssumeRolePolicyDocument/
Statement/0/Condition/StringEquals] map keys must be strings; received a map instead
So, I guess my issue is how to substitute variables in the keys of a map. UserData didn't help either.

You problem is on Federated not on StringEquals.
Federated value needs to be string but you define it as Map. Please remove - before !Join.
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
AWSAccountId:
Type: String
OidcProvider:
Type: String
AppNamespace:
Type: String
AppServiceAccountName:
Type: String
Resources:
CloudWatchRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument: !Sub
- |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "${IamOidcProviderArn}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${OidcProvider}:sub": "system:serviceaccount:${AppNamespace}:${AppServiceAccountName}"
}
}
}
]
}
- IamOidcProviderArn: !Join
- ''
- - 'arn:aws:iam::'
- !Ref AWSAccountId
- ':oidc-provider/'
- !Ref OidcProvider
OidcProvider: !Ref OidcProvider
AppNamespace: !Ref AppNamespace
AppServiceAccountName: !Ref AppServiceAccountName

Related

AWS Backupvault SNS notification

I have created a backup plan and configured SNS using cloud formation ( not CLI ), but backup job has running and completed successfully but not receiving emails notification ( already subscribe to the SNS topic ).
Backup vault section in cloudformation :
BackupVaultWithDailyBackupssns:
Type: "AWS::Backup::BackupVault"
Properties:
BackupVaultName: "Vault_name"
Notifications:
BackupVaultEvents:
- BACKUP_JOB_STARTED
- BACKUP_JOB_COMPLETED
- BACKUP_JOB_SUCCESSFUL
- BACKUP_JOB_FAILED
SNSTopicArn:
!Sub 'arn:aws:sns:${AWS::Region}:${AWS::AccountId}:My_topic'
SNS Policy :
{
"Version": "2008-10-17",
"Id": "__default_policy_ID",
"Statement": [
{
"Sid": "__default_statement_ID",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"SNS:GetTopicAttributes",
"SNS:SetTopicAttributes",
"SNS:AddPermission",
"SNS:RemovePermission",
"SNS:DeleteTopic",
"SNS:Subscribe",
"SNS:ListSubscriptionsByTopic",
"SNS:Publish",
"SNS:Receive"
],
"Resource": "arn:aws:sns:us-east-1:111111111111:My_topic",
"Condition": {
"StringEquals": {
"AWS:SourceOwner": "111111111"
}
}
},
{
"Sid": "My-statement-id",
"Effect": "Allow",
"Principal": {
"Service": "backup.amazonaws.com"
},
"Action": "SNS:Publish",
"Resource": "arn:aws:sns:us-east-1:111111111111:My_topic"
}
]
}
For anyone looking for an answer on this here I how I set mine up, this is working for all failed notifications.
Set up your vault referencing a topic
BackupVault:
Type: "AWS::Backup::BackupVault"
Properties:
BackupVaultName: Backup Vault Name
Notifications:
BackupVaultEvents:
- "BACKUP_JOB_EXPIRED"
- "BACKUP_JOB_FAILED"
SNSTopicArn: !Ref FailedBackupTopic
DependsOn:
- FailedBackupTopic
Set up a topic, topic policy and topic subscription
FailedBackupTopic:
Type: AWS::SNS::Topic
Properties:
DisplayName: "AWS Backup - Failed backup notification"
FailedBackupTopicPolicy:
Type: 'AWS::SNS::TopicPolicy'
Properties:
PolicyDocument:
Id: SNSPolicyV1
Version: '2012-10-17'
Statement:
- Sid: Sid1
Effect: Allow
Principal:
Service:
- 'backup.amazonaws.com'
Action: 'sns:Publish'
Resource: !Ref FailedBackupTopic
Topics:
- !Ref FailedBackupTopic
FailedBackupTopicSubscription:
Type: 'AWS::SNS::Subscription'
Properties:
Endpoint: "email#email.com"
Protocol: email
TopicArn: !Ref FailedBackupTopic

Policy to limit subnets or hosted zone CodeStar can use

Is there an IAM policy that can be created/attached to CodeStarWorker-*-CloudFormation that limits either the Subnets or HostedZoneIds the CodeStar worker can use?
Here's an example template.yml:
Resources:
# other resources
DevAlb:
Properties:
LoadBalancerAttributes: []
Name: !Sub '${ProjectId}-dev-alb'
Scheme: internal
SecurityGroups:
- !Ref AlbSecurityGroup
Subnets:
- !ImportValue PrivateSubnet1
- !ImportValue PrivateSubnet2
Tags:
- Key: Name
Value: !Sub '${ProjectId}-dev'
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
DevAlbDns:
Properties:
AliasTarget:
DNSName: !GetAtt
- AlbDev
- DNSName
HostedZoneId: !GetAtt
- AlbDev
- CanonicalHostedZoneID
HostedZoneId: !ImportValue InternalDomainDotCom
Name: !Sub '${ProjectId}.internal-domain.com'
Type: A
Type: 'AWS::Route53::RecordSet'
I don't want users with CodeStar access to import/use anything that would allow public internet access (without admin approval, anyway). How can I prevent someone from setting/importing PublicSubnet1 and PublicSubnet2 as one of the Subnets? Or prevent them from setting/import PublicDomainDotCom as the HostedZoneId?
I was able to do this by attaching the following policy to CodeStarWorker-app-CloudFormation!
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:GetChange",
"route53:GetHostedZone",
"route53:ListHostedZones",
"route53:ListHostedZonesByName",
"route53:ListResourceRecordSets",
"route53:GetHostedZoneCount",
"route53domains:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets",
"apigateway:GET"
],
"Resource": [
"arn:aws:route53:::hostedzone/REPLACE_WITH_HOSTED_ZONE_ID",
"arn:aws:apigateway:*::/domainnames"
]
}
]
}
This will only allow CodeStar's CloudFormation role to create a Route 53 record set in the hosted zone ID an admin has allowed.
I'm sure there are other ways to protect your infrastructure and data from bad actors with CodeStar roles. Feel free to share if you have any ideas (e.g., limiting EC2 VPCs/Subnets).

Two HTTP Methods for one AWS API Gateway Resource

There is this wicked post about configuring an API Gateway method for CORS through CloudFormation, and I'm giving it a go. I want to create the following endpoint with two methods, "options" and "post":
/image/submit
Here is my CF template snippet:
ApiDefault:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "Stash-Default"
FailOnWarnings: true
ApiDefaultDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- "ApiMethodImageSubmitPost"
- "ApiMethodImageSubmitOption"
Properties:
RestApiId: !Ref "ApiDefault"
StageName: "v1"
ApiResourceImage:
Type: "AWS::ApiGateway::Resource"
Properties:
ParentId: !GetAtt ["ApiDefault", "RootResourceId"]
PathPart: "image"
RestApiId: !Ref "ApiDefault"
ApiResourceImageSubmit:
Type: "AWS::ApiGateway::Resource"
Properties:
ParentId: !Ref "ApiResourceImage"
PathPart: "submit"
RestApiId: !Ref "ApiDefault"
ApiMethodImageSubmitPost:
Type: "AWS::ApiGateway::Method"
Properties:
HttpMethod: "POST"
AuthorizationType: "NONE"
MethodResponses:
- StatusCode: "200"
Integration:
IntegrationHttpMethod: "POST"
Type: "AWS_PROXY"
IntegrationResponses:
- StatusCode: "200"
Credentials: !GetAtt [ "ExecuteApiMethodImageSubmit", "Arn" ]
Uri: !Sub
- "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations"
- lambdaArn: !GetAtt [ "ImageReceive", "Arn" ]
RestApiId: !Ref "ApiDefault"
ResourceId: !Ref "ApiResourceImageSubmit"
ApiMethodImageSubmitOption:
Type: "AWS::ApiGateway::Method"
Properties:
HttpMethod: "OPTIONS"
AuthorizationType: "NONE"
Integration:
Type: "MOCK"
IntegrationResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: "'*'"
MethodResponses:
- StatusCode: "200"
ResponseModels:
application/json: "Empty"
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: false
method.response.header.Access-Control-Allow-Methods: false
method.response.header.Access-Control-Allow-Origin: false
RestApiId: !Ref "ApiDefault"
ResourceId: !Ref "ApiResourceImageSubmit"
It bombs saying ApiMethodImageSubmitPost:
Method already exists for this resource (Service: AmazonApiGateway;
Status Code: 409; Error Code: ConflictException; Request ID:
454cf46a-b434-4626-bd4b-b6d4fe21142c)
Can you create two http-methods for a single API resource in this fashion? I'm not having a ton of luck with AWS' docs on this one.

Create S3 only user

I am trying to create a S3 only user who will by definition have no access to any other resource. The user can upload and download files from S3. I have created a basic template that can be found here...
https://github.com/shantanuo/aws-cloudformation-templates/blob/master/aws/services/IAM/IAM_Users_Groups_and_Policies.yaml
But it is allowing access to cloudformation that is not necessary in my case. I have read the following pages, but do not know how to include them in my template.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
https://docs.aws.amazon.com/AWSCloudForation/latest/UserGuide/aws-properties-iam-group.html
What are the minimum parameters required to create a S3 only user?
Update:
Here is the cloudformation code that I tried and the error that I got:
error message: The following resource(s) failed to create: [CFNRole, CFNUser]. . Rollback requested by user.
The template:
Parameters:
NewUsername:
NoEcho: 'false'
Type: String
Description: New account username
MinLength: '1'
MaxLength: '41'
ConstraintDescription: the username must be between 1 and 41 characters
Password:
NoEcho: 'true'
Type: String
Description: New account password
MinLength: '1'
MaxLength: '41'
ConstraintDescription: the password must be between 1 and 41 characters
Resources:
CFNUser:
Type: AWS::IAM::User
Properties:
LoginProfile:
Password: !Ref 'Password'
UserName : !Ref 'NewUsername'
CFNRole:
Type: AWS::IAM::Role
Properties :
PermissionsBoundary : arn:aws:iam::aws:policy/AmazonS3FullAccess
RoleName : 'myPermissionBoundary'
CFNKeys:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref 'CFNUser'
Outputs:
AccessKey:
Value: !Ref 'CFNKeys'
Description: AWSAccessKeyId of new user
SecretKey:
Value: !GetAtt [CFNKeys, SecretAccessKey]
Description: AWSSecretAccessKey of new user
I have checked this link, but not sure how to include the policy in my current template.
https://aws.amazon.com/blogs/security/easier-way-to-control-access-to-aws-regions-using-iam-policies/
You are explicit giving your new user access to Cloudformation in your template.
You have this section:
CFNUserPolicies:
Type: AWS::IAM::Policy
Properties:
PolicyName: CFNUsers
PolicyDocument:
Statement:
- Effect: Allow
Action: ['cloudformation:Describe*', 'cloudformation:List*', 'cloudformation:Get*']
Resource: '*'
Groups: [!Ref 'CFNUserGroup']
CFNAdminPolicies:
Type: AWS::IAM::Policy
Properties:
PolicyName: CFNAdmins
PolicyDocument:
Statement:
- Effect: Allow
Action: cloudformation:*
Resource: '*'
Groups: [!Ref 'CFNAdminGroup']
In which your allow statements specifically provide access to Cloudformation. If you are trying to give access to s3 then why are you giving access to Cloudformation?
If you want a user to have access to s3 only, and going further one specific s3 bucket then your policy would look something like this (note this is in json):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:ListAllMyBuckets"
],
"Resource": "arn:aws:s3:::*"
},
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::YOUR-BUCKET",
"arn:aws:s3:::YOUR-BUCKET/*"
]
}
]
}
If you want your user to have access to all buckets, then your policy would look more like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1557048549844",
"Action": "s3:*",
"Effect": "Allow",
"Resource": "*"
}
]
}
See this source: https://objectivefs.com/howto/how-to-restrict-s3-bucket-policy-to-only-one-aws-s3-bucket

AWS API Gateway Method Response in CloudFormation

I am trying to set up my API Gateway so it has this simple method response:
And I am using CloudFormation and I keep running into errors. I believe this is pretty simple but I am stuck after spending hours reading docs. Here is my method resource (in YAML):
MyMethod:
Type: "AWS::ApiGateway::Method"
Properties:
AuthorizationType: "NONE"
HttpMethod: "GET"
Integration:
Type: AWS
Credentials: !GetAtt MyRole.Arn
IntegrationHttpMethod: "POST"
Uri:
Fn::Join: [ "", [ "arn:aws:apigateway:", Ref: "AWS::Region", ":states:action/SendTaskSuccess" ] ]
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
application/json: |
{
"output": "\"Approve link was clicked.\"",
"taskToken": "$input.params('taskToken')"
}
IntegrationResponses:
- StatusCode: 200
ResponseTemplates: {"application/json": "$input.json('$.body')"}
RequestParameters:
method.request.querystring.taskToken: false
OperationName: succeed
ResourceId: !Ref MyResource
RestApiId: !Ref MyApi
Do I need a MethodResponse property?
Ok it looks like I just had to add this:
MethodResponses:
- StatusCode: 200
ResponseModels: { "application/json": "Empty" }
ApiPATCH:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: APIGateway
ResourceId: ProxyResourceROOT
HttpMethod: PATCH
AuthorizationType: NONE
Integration:
Type: AWS
IntegrationHttpMethod: POST
Uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':lambda:path/2015-03-31/functions/'
- !GetAtt
- LambdaFunction
- Arn
- /invocations
IntegrationResponses:
- StatusCode: 200
MethodResponses:
- StatusCode: 200
ResponseModels:
application/json: 'Empty'
Yes that's right. You need to add the following:
MethodResponses:
StatusCode: 200
ResponseModels:
application/json: 'Empty'