CloudFormation claims KMS policy statement principals are invalid - amazon-web-services

I have the following SAM template:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
test lambda
Globals:
Function:
Timeout: 3
Tracing: Active
Api:
TracingEnabled: True
Resources:
NotesFunction:
Type: AWS::Serverless::Function
Properties:
PackageType: Zip
CodeUri: notes/
Handler: app.lambdaHandler
Runtime: nodejs18.x
Policies:
- AmazonDynamoDBFullAccess
Architectures:
- x86_64
Events:
FetchNotes:
Type: Api
Properties:
Path: /notes
Method: get
GiveNotes:
Type: Api
Properties:
Path: /notes
Method: post
Users:
Type: Api
Properties:
Path: /notes/users
Method: get
Metadata:
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: "es2020"
Sourcemap: true
EntryPoints:
- app.ts
KmsKey:
Type: AWS::KMS::Key
Properties:
Description: CMK for encrypting and decrypting
KeyPolicy:
Version: '2012-10-17'
Id: key-default-1
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
Action: kms:*
Resource: '*'
- Sid: Allow administration of the key
Effect: Allow
Principal:
AWS: arn:aws:iam::<MY_ACCOUNT>:role/aws-service-role/cks.kms.amazonaws.com/KMSKeyAdminRole
Action:
- kms:Create*
- kms:Describe*
- kms:Enable*
- kms:List*
- kms:Put*
- kms:Update*
- kms:Revoke*
- kms:Disable*
- kms:Get*
- kms:Delete*
- kms:ScheduleKeyDeletion
- kms:CancelKeyDeletion
Resource: '*'
- Sid: Allow use of the key
Effect: Allow
Principal:
AWS: !Ref NotesFunctionRole
Action:
- kms:DescribeKey
- kms:Encrypt
- kms:Decrypt
- kms:ReEncrypt*
- kms:GenerateDataKey
- kms:GenerateDataKeyWithoutPlaintext
Resource: '*'
NotesDynamoDB:
Type: AWS::DynamoDB::Table
Properties:
TableName: experimental-notes
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
StreamSpecification:
StreamViewType: NEW_IMAGE
Outputs:
NotesApi:
Description: "API Gateway endpoint URL for dev stage for Notes function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/notes/"
NotesFunction:
Description: "Notes Lambda Function ARN"
Value: !GetAtt NotesFunction.Arn
NotesFunctionIamRole:
Description: "Implicit IAM Role created for Notes function"
Value: !GetAtt NotesFunctionRole.Arn
NotesDynamoDB:
Description: "DynamoDB table backing the Lambda"
Value: !GetAtt NotesDynamoDB.Arn
When I build + deploy this template I get the following CloudFormation errors:
Resource handler returned message: "Policy contains a statement with one or more invalid principals....
Obviously I have redacted my actual account ID and replaced it with <MY_ACCOUNT> (!).
But it doesn't say what's "invalid" about which principals. The idea is that the 2nd policy statement gets applied/hardcoded to an existing role (KMSKeyAdminRole). And that the 3rd Statement gets applied to the role of the NotesFunction Lambda created up above.
Can anyone spot where I'm going awry?

This ended up working perfectly and fixing the CF error:
- Sid: Allow use of the key
Effect: Allow
Principal:
AWS: !GetAtt FeedbackFunctionRole.Arn
Action:
- kms:DescribeKey
- kms:Encrypt
- kms:Decrypt
- kms:ReEncrypt*
- kms:GenerateDataKey
- kms:GenerateDataKeyWithoutPlaintext
Resource: '*'

Use this https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_testing-policies.html to test if your policies are valid

Related

Cloudformation error when deploying serverless.yml

I am following a serverless tutorial and I am trying to send a notification every time an image is uploaded to the s3 bucket. I've created a sendUploadNotifications function under functions and instead of adding an event to the function I've set up the "NotificationsConfiguration" under the AttachmentsBucket, as well as created a new sendUploadNotificationsPermission Resource under resources.
But when I deploy the app I get the following error when I try to deploy my serverless app:
Error: The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource sendUploadNotificationsLambdaFunction
The Error seems to stem from the way that I am referencing the FunctionName under the sendUploadNotificationsPermission resource.
I've tried different ways of referencing the function name, but to no avail. I still get the same error.
My serverless.yml file
service: serverless-udagram2
frameworkVersion: '2'
provider:
name: aws
runtime: nodejs12.x
lambdaHashingVersion: 20201221
stage: ${opt:stage, 'dev'}
region: ${opt:region, 'ap-southeast-1'}
environment:
GROUPS_TABLE: groups-${self:provider.stage}
IMAGES_TABLE: images-${self:provider.stage}
IMAGE_ID_INDEX: ImageIdIndex
IMAGES_S3_BUCKET: branded-serverless-udagram-images-${self:provider.stage}
SIGNED_URL_EXPIRATION: 300
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Scan
- dynamodb:PutItem
- dynamodb:GetItem
- dynamodb:Query
Resource: arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.GROUPS_TABLE}
- Effect: Allow
Action:
- dynamodb:PutItem
- dynamodb:Query
Resource: arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.IMAGES_TABLE}
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:PutItem
Resource: arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.IMAGES_TABLE}/index/${self:provider.environment.IMAGE_ID_INDEX}
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
Resource: arn:aws:s3:::${self:provider.environment.IMAGES_S3_BUCKET}/*
functions:
getGroups:
handler: src/lambda/http/getGroups.handler
events:
- http:
path: groups
method: get
cors: true
createGroup:
handler: src/lambda/http/createGroup.handler
events:
- http:
path: groups
method: post
cors: true
request:
schema:
application/json: ${file(models/create-group-request.json)}
getImages:
handler: src/lambda/http/getImages.handler
events:
- http:
path: groups/{groupId}/images
method: get
cors: true
getImage:
handler: src/lambda/http/getImage.handler
events:
- http:
path: images/{imageId}
method: get
cors: true
createImage:
handler: src/lambda/http/createImage.handler
events:
- http:
path: groups/{groupId}/images
method: post
cors: true
request:
schema:
application/json: ${file(models/create-image-request.json)}
sendUploadNotifications:
handler: src/lambda/s3/sendNotifications.handler
resources:
Resources:
# API gateway validates the request in accordance with json schemas that are identified in the function section under schema
RequestBodyValidator:
Type: AWS::ApiGateway::RequestValidator
Properties:
Name: 'request-body-validator'
RestApiId:
Ref: ApiGatewayRestApi
ValidateRequestBody: true
ValidateRequestParameters: true
GroupsDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
TableName: ${self:provider.environment.GROUPS_TABLE}
ImagesDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: groupId
AttributeType: S
- AttributeName: timestamp
AttributeType: S
- AttributeName: imageId
AttributeType: S
KeySchema:
- AttributeName: groupId
KeyType: HASH #partition key
- AttributeName: timestamp
KeyType: RANGE #sort key
GlobalSecondaryIndexes:
- IndexName: ${self:provider.environment.IMAGE_ID_INDEX}
KeySchema:
- AttributeName: imageId
KeyType: HASH
Projection:
ProjectionType: ALL
BillingMode: PAY_PER_REQUEST
TableName: ${self:provider.environment.IMAGES_TABLE}
# Bucket for file uploads
AttachmentsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:provider.environment.IMAGES_S3_BUCKET}
NotificationConfiguration: # Sends notification when image has been uploaded
LambdaConfigurations: #
- Event: s3:ObjectCreated:*
Function: !GetAtt sendUploadNotificationsLambdaFunction.Arn
CorsConfiguration:
CorsRules:
-
AllowedOrigins:
- "*"
AllowedHeaders:
- "*"
AllowedMethods:
- 'GET'
- 'PUT'
- 'POST'
- 'DELETE'
- 'HEAD'
MaxAge: 3000
sendUploadNotificationsPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt sendUploadNotificationsLambdaFunction.Arn
Action: lambda:InvokeFunction
Principal: s3.amazonaws.com
SourceAccount: !Ref AWS::AccountId #!Ref
SourceArn: arn:aws:s3:::${self:provider.environment.IMAGES_S3_BUCKET}
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
PolicyDocument:
Id: MyPolicy
Version: "2012-10-17"
Statement:
- Sid: PublicReadForGetBucketObjects
Effect: Allow
Principal: '*'
Action: 's3:GetObject'
Resource: 'arn:aws:s3:::${self:provider.environment.IMAGES_S3_BUCKET}/*'
Bucket:
Ref: AttachmentsBucket
I've tried changing the name of the function in both the sendUploadNotificationsPermission and the AttachmentsBucket by appending LamdaFunction to the end of the function name, but still getting the same error.
Any help with this error would be appreciated.
You are trying to reference something which doesn't exist in the template at in the CloudFormation section Resource.
sendUploadNotificationsLambdaFunction
In case you want to reference any of the function you have defined named
sendUploadNotifications
you need to construct the ARN inside the Resources section.
To generate Logical ID for CloudFormation, the plugin transform the specified name in serverless.yml based on the following scheme.
Transform a leading character into uppercase
Transform - into Dash
Transform _ into Underscore
SendUploadNotificationsLambdaFunction in your case.
There are now two ways:
You reference this inside Resource section of the template:
sendUploadNotificationsPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt SendUploadNotificationsLambdaFunction.Arn
Action: lambda:InvokeFunction
Principal: s3.amazonaws.com
SourceAccount: !Ref AWS::AccountId #!Ref
SourceArn: arn:aws:s3:::${self:provider.environment.IMAGES_S3_BUCKET}
You construct the ARN using Fn::Join.
sendUploadNotificationsPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Join [":", ['arn:aws:lambda', !Ref 'AWS::Region', !Ref AWS::AccountId, '${self:service}-${self:provider.stage}-sendUploadNotifications']]
Action: lambda:InvokeFunction
Principal: s3.amazonaws.com
SourceAccount: !Ref AWS::AccountId #!Ref
SourceArn: arn:aws:s3:::${self:provider.environment.IMAGES_S3_BUCKET}
Solved it. The 'references undefined resource error' was caused by the fact that after the serverless.yml file compiles it capitalizes the function name.
sendUploadNotifications becomes SendUploadNotificationsLambdaFunction
changed:
FunctionName: !Ref sendUploadNotificationsLambdaFunction
to:
FunctionName: !Ref SendUploadNotificationsLambdaFunction
It now deploys without an issue.

Add statements to serverless "KeyPolicy" on KMS Resource

I have a serverless application, which creates a KMS Resource:
# serverless.yml 1
resources:
Resources:
SomeLambdaRole:
Type: AWS::IAM::Role
AnotherLambdaRole:
Type: AWS::IAM::Role
TheKey:
Type: AWS::KMS::Key
DeletionPolicy: Retain
Properties:
Description: The key
Enabled: true
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Allow use of the key
Effect: Allow
Principal:
AWS:
- Fn::GetAtt: [SomeLambdaRole, Arn]
- Fn::GetAtt: [AnotherLambdaRole, Arn]
Action:
- 'kms:Encrypt'
- 'kms:Decrypt'
- 'kms:ReEncrypt'
- 'kms:GenerateDataKey*'
Resource: '*'
From another serverless application where some roles are created, I want to give these new roles the same permissions that SomeLambdaRole and AnotherLambdaRole have on the "TheKey" Resource
# serverless.yml 2
resources:
Resources:
YetAnotherLambdaRole:
Type: AWS::IAM::Role
# Do something to let this role have the same permission as "SomeLambdaRole" and "AnotherLambdaRole" for the "TheKey" Resource
Is this possible or should I try another approach?

Lambda cannot access KMS Key

When I run my lambda code, I get the following error:
The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access.
I have mostly followed this to create the stack using aws-sam-cli, and the relevant sections of the template are below the code.
The relevant code is:
const ssm = new AWS.SSM();
const param = {
Name: "param1",
WithDecryption: true
};
const secret = await ssm.getParameter(param).promise();
The relevant part of the template.yaml file is:
KeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: 'param1Key'
TargetKeyId: !Ref Key
Key:
Type: AWS::KMS::Key
Properties:
KeyPolicy:
Id: default
Statement:
- Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
Action:
- 'kms:Create*'
- 'kms:Encrypt'
- 'kms:Describe*'
- 'kms:Enable*'
- 'kms:List*'
- 'kms:Put*'
- 'kms:Update*'
- 'kms:Revoke*'
- 'kms:Disable*'
- 'kms:Get*'
- 'kms:Delete*'
- 'kms:ScheduleKeyDeletion'
- 'kms:CancelKeyDeletion'
Resource: '*'
Sid: Allow root account all permissions except to decrypt the key
Version: 2012-10-17
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../
Handler: app.lambda
Runtime: nodejs8.10
Policies:
- DynamoDBReadPolicy:
TableName: !Ref Table
- KMSDecryptPolicy:
KeyId: !Ref Key
- Statement:
- Action:
- "ssm:GetParameter"
Effect: Allow
Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/param1"
Does the KMSDecryptPolicy not allow the use of the key? What am I missing? Thanks!
EDIT: Changing the template to below works, but I'd really like to use the KMSDecryptPolicy in the lambda definition if possible.
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../
Handler: app.lambda
Runtime: nodejs8.10
Policies:
- DynamoDBReadPolicy:
TableName: !Ref Table
- KMSDecryptPolicy:
KeyId: !Ref Key
- Statement:
- Action:
- "ssm:GetParameter"
Effect: Allow
Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/param1"
Key:
Type: AWS::KMS::Key
Properties:
KeyPolicy:
Id: default
Statement:
- Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
Action:
- 'kms:Create*'
- 'kms:Encrypt'
- 'kms:Describe*'
- 'kms:Enable*'
- 'kms:List*'
- 'kms:Put*'
- 'kms:Update*'
- 'kms:Revoke*'
- 'kms:Disable*'
- 'kms:Get*'
- 'kms:Delete*'
- 'kms:ScheduleKeyDeletion'
- 'kms:CancelKeyDeletion'
Resource: '*'
Sid: Allow root account all permissions except to decrypt the key
- Sid: 'Allow use of the key for decryption by the LambdaFunction'
Effect: Allow
Principal:
AWS: !GetAtt LambdaFunctionRole.Arn
Action:
- 'kms:Decrypt'
Resource: '*'
Version: 2012-10-17
The question itself contains the answer. The change is that instead of giving KMS permissions in the lambda role only (identity based way), it has also given permissions to the lambda role in the key policy (resource based way).
Here is the AWS official resource on why this is happening - https://docs.aws.amazon.com/kms/latest/developerguide/iam-policies.html
According to this
All KMS CMKs have a key policy, and you must use it to control access to a CMK. IAM policies by themselves are not sufficient to allow access to a CMK, though you can use them in combination with a CMK's key policy.

Circular dependency in AWS lambda function

The following is the template.yaml for the lambda function. I'm trying to add permissions to access the status database. However, it needs the database to exist and vice versa, and so I get a circular dependency error with DynamoDBIamPolicy. How can I resolve this?
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: An AWS Serverless Specification template describing your function.
Resources:
friendTeachers:
Type: 'AWS::Serverless::Function'
Properties:
Handler: friendTeachers/index.handler
Runtime: nodejs6.10
Description: ''
MemorySize: 128
Timeout: 15
status:
Type: 'AWS::DynamoDB::Table'
Properties:
TableName: status
AttributeDefinitions:
- AttributeName: screenName
AttributeType: S
KeySchema:
- AttributeName: screenName
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
# A policy is a resource that states one or more permssions. It lists actions, resources and effects.
DynamoDBIamPolicy:
Type: 'AWS::IAM::Policy'
DependsOn: status
Properties:
PolicyName: lambda-dynamodb
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:batchWriteItem
Resource: arn:aws:dynamodb:*:*:table/status
Roles:
- Ref: IamRoleLambdaExecution
You are missing a role where you specify that the lambda service can AssumeRole. The role needs to have a policy associated that specifies the operations that can be done in the DynamoDb table. Find below an example that shows what you are trying to accomplish:
---
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: An AWS Serverless Specification template describing your function.
Resources:
friendTeachersFunction:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket:
Ref: LambdaCodeBucket
S3Key:
Ref: LambdaCodePath
Handler: friendTeachers/index.handler
Runtime: "nodejs6.10"
Description: ''
MemorySize: 128
Timeout: 15
Role:
Fn::GetAtt:
- friendTeachersExecutionRole
- Arn
friendTeachersExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: UseDBPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:batchWriteItem
Resource: arn:aws:dynamodb:*:*:table/status
APIDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: status
AttributeDefinitions:
- AttributeName: screenName
AttributeType: S
KeySchema:
- AttributeName: screenName
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
Note that Code.S3Bucket and Code.S3Key are defined as parameters. When you create the stack in AWS Console you can specify the path there.

How to export Cognito User Pool settings to CloudFormation template?

I've created Cognito User Pool through AWS Console, but I want to automate creation of new Cognito User Pools through CloudFormation. Can I export my current User Pool configuration to CloudFormation template?
Its not possible to export. You would need the below 6 resources to automate the process.
Cognito Authenticated role
Cognito unAuthenticated role
User pool
User Pool Client
Identity Pool
Identity Pool Role attachment
You would need 3 outputs which you might need to use in your code. Below is the code for creating these
AWSTemplateFormatVersion: 2010-09-09
Parameters:
envParameter:
Type: String
Default: dev
AllowedValues: [ dev, test, qa, prod ]
Description: Suffix to be added for names.
Resources:
myApiUserPool:
Type: "AWS::Cognito::UserPool"
Properties:
UserPoolName: !Sub myApiUserPool${envParameter}
myApiUserPoolClient:
Type: "AWS::Cognito::UserPoolClient"
Properties:
ClientName: !Sub myApiUserPoolClient${envParameter},
GenerateSecret: False
RefreshTokenValidity: 30
UserPoolId: !Ref myApiUserPool
myApiIdentityPool:
Type: "AWS::Cognito::IdentityPool"
Properties:
IdentityPoolName: !Sub myApiIdentityPool${envParameter}
AllowUnauthenticatedIdentities: False
CognitoIdentityProviders:
- ClientId: !Ref myApiUserPoolClient
ProviderName: !GetAtt myApiUserPool.ProviderName
cognitoUnauthRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Sub Cognito_${myApiIdentityPool.Name}_Unauth_Role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action: [ 'sts:AssumeRole' ]
Policies:
- PolicyName: cognitounauth
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- mobileanalytics:PutEvents
- cognito-sync:*
Resource:
- "*"
cognitoAuthRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Sub Cognito_${myApiIdentityPool.Name}_Auth_Role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action: [ 'sts:AssumeRole' ]
Policies:
- PolicyName: cognitoauth
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- mobileanalytics:PutEvents
- cognito-sync:*
- execute-api:*
Resource:
- "*"
myApiIdentityPoolRoleAttachment:
DependsOn: [ myApiIdentityPool, cognitoUnauthRole, cognitoAuthRole ]
Type: "AWS::Cognito::IdentityPoolRoleAttachment"
Properties:
IdentityPoolId: !Ref myApiIdentityPool
Roles:
authenticated: !GetAtt cognitoAuthRole.Arn
unauthenticated: !GetAtt cognitoUnauthRole.Arn
Outputs:
userPool:
Description: "User pool ID"
Value: !Ref myApiUserPool
identityPool:
Description: "Identity pool ID"
Value: !Ref myApiIdentityPool
ClientId:
Description: "Client id for the user pool appclient"
Value: !Ref myApiUserPoolClient
It's not currently possible to export existing user pools from Cognito. You can, however, create new user pools in AWS CloudFormation and then manage those pools from CloudFormation itself going forward, using AWS::Cognito::UserPool resource type.
I am going to use the describe-user-pool action and then build the cf template based on the output