I want to invoke mutations on my AppSync API from Lambda functions in response to external events. Now I have been able to do this if I set AppSync to use IAM authorization and then provide access in the role of my Lambda function. The problem is that I need to use Cognito authorization because I need access to Cognito usernames in many of my resolver templates and I do not know of any way to do this when using IAM authorization.
I would thus like to know if there is any way I can authorise a Lambda function using Cognito User Pools to call my AppSync endpoint. Obviously I can create a dummy user in my pool and simply log in with it in the Lambda but Cognito User Pool logins are a very slow process and this method just sounds wrong anyway. The mutations I want to call from Lambdas don't need any Cognito info anyway.
What would also solve my problem is if I had a way to access the Cognito username in resolver templates when using IAM authorization.
With user pools as your authentication mechanism, there is no way to get tokens to authorize with AWS AppSync without signing in, at some point. With that said, you could mitigate the overhead of that a bit. Ultimately, it boils down to what you suggested in your question, using a 'fake user'. It's not an uncommon solution, and involves having some admin level user, the credentials for whom would only be accessed from this Lambda.
First of all, I would recommend you not use the traditional SRP login, for latency reasons. One alternative would be to use AdminInitiateAuth/ADMIN_NO_SRP_AUTH, to offload the overhead of SRP calculations from your Lambda to the Cognito back end, with ADMIN_NO_SRP_AUTH turned on in your pool. I saw from your other question (Authenticate AppSync request with adminInitiateAuth) that you were thinking of doing this, however it is important to point out this is simply a different API used to sign a user in. You can read more about it here: https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-server-side-authentication-flow
An additional alternative could be a custom authentication flow. The same link above has more details on how to do this, but in short, you could set up a quicker flow that fits whatever specific needs your add has.
To improve overhead a bit more, you could then keep those tokens cached, to some degree. That could just mean keeping it in memory in the Lambda and checking their validity/expiry before using them, or even attaching a remote caching mechanism.
#Gerharddc, what if do you try to add policies that allow unauthenticated access to your Cognito Identity Pool?
I have this piece of code that defines authenticated and unauthenticated access to my Identity Pool (you can check the full code here in my personal project).
IdentityPoolUnauthorizedIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action:
- sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
cognito-identity.amazonaws.com:aud: !Ref CognitoIdentityPool
ForAnyValue:StringLike:
cognito-identity.amazonaws.com:amr: unauthenticated
Policies:
- PolicyName: CognitoUserSignInUnauthorizedPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- mobileanalytics:PutEvents
- mobiletargeting:PutEvents
- cognito-sync:*
- cognito-identity:*
Resource: '*'
IdentityPoolAuthorizedIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action:
- sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
cognito-identity.amazonaws.com:aud: !Ref CognitoIdentityPool
ForAnyValue:StringLike:
cognito-identity.amazonaws.com:amr: authenticated
Policies:
- PolicyName: CognitoUserSignInAuthorizedPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- mobileanalytics:PutEvents
- mobiletargeting:PutEvents
- cognito-sync:*
- cognito-identity:*
Resource: '*'
# Assigns the roles to the Identity Pool
CognitoIdentityPoolRoleAttachment:
Type: AWS::Cognito::IdentityPoolRoleAttachment
Properties:
IdentityPoolId: !Ref CognitoIdentityPool
Roles:
unauthenticated: !GetAtt IdentityPoolUnauthorizedIAMRole.Arn
authenticated: !GetAtt IdentityPoolAuthorizedIAMRole.Arn
When I declare my AWS AppSync template, I add this template that permits users to access my AppSync endpoint when signed in:
AppSyncIAMPolicy:
Type: AWS::IAM::Policy
Description: Allow user consume AppSync when signed in
DependsOn: AppSyncGraphQLApi
Properties:
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- appsync:GraphQL
Resource:
- !Join ['/', [!GetAtt AppSyncGraphQLApi.Arn, '*']]
PolicyName: !Sub ${StackName}-appsync-iam-policy
Roles:
- !Sub ${IdentityPoolAuthorizedIAMRoleRef}
My point is: you can attach a policy that allows users not signed (unauthenticated) access AppSync. Instead - !Sub ${IdentityPoolAuthorizedIAMRoleRef} you can in the template above, you can try - !Sub ${IdentityPoolUnauthorizedIAMRoleRef}.
Of course, you can specify which endpoints you allow unauthenticated access, for example:
AppSyncIAMPolicy:
Type: AWS::IAM::Policy
Description: Allow user consume AppSync when NOT signed in
DependsOn: AppSyncGraphQLApi
Properties:
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- appsync:GraphQL
Resource:
- arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-1>
- arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-2>
PolicyName: !Sub ${StackName}-appsync-iam-policy-unauthenticated
Roles:
- !Sub ${IdentityPoolUnauthorizedIAMRoleRef}
Related
I am trying to make an IAM Role via CloudFormation and am getting this error when trying to attach a QueuePolicy resource to an IAM::Role resource.
ARN stack-personSQSPolicy-3F02ILJ96DB1 is not valid. (Service: AmazonIdentityManagement; Status Code: 400; Error Code: InvalidInput; Request ID: 4410ba76-30ce-4d15-be3c-6d5040f971f0)
Here is my CloudFormation Role and Policy definition:
APIGatewaySQSRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: 'sts:AssumeRole'
Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Version: 2012-10-17
ManagedPolicyArns:
- !Ref personSQSPolicy
- 'arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs'
personSQSPolicy:
Type: 'AWS::SQS::QueuePolicy'
Properties:
PolicyDocument:
Version: 2012-10-17
Statement:
Effect: Allow
Action: 'sqs:SendMessage'
Resource: !GetAtt personSQS.Arn
Queues:
- !Ref personSQS
What's the point of Type: 'AWS::SQS::QueuePolicy' If it doesn't allow the use as an Arn in the Role resource? It seems like I still have to manually create that policy in the IAM Role resource block.
Policies:
- PolicyDocument:
Statement:
- Action: sqs:SendMessage
Effect: Allow
Resource: !GetAtt 'personSQS.Arn'
PolicyName: apig-sqs-send-msg-policy
Is there a way to avoid this?
Since SQS Queues can be publicly accessible, they need a mechanism for security if people are going to access it without a role.
This is why you have a QueuePolicy AWS::SQS::QueuePolicy that you can define for the queue and it can be applied to one or more queues. It will help you define who's allowed to access it, how etc directly from the point of view of the queue.
You then attach your QueuePolicy to your Queue(s) with the Cloudformation attribute Queues (see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-policy.html#cfn-sqs-queuepolicy-queues)
If you want to define a role for accessing your queue, yes you'll have to describe kind of the same policy but this time from the point of view of the resource accessing it but I still recommend that you secure the access to your queue with a Queue Policy.
As for your last question, defining the QueuePolicy and attaching it to your queue is the right way to do it.
Watch out, the Queues attribute expect a list of Queue URLs, not ARNs.
Using the serverless framework, I am trying to build a Lambda function that periodically rotates a secret stored in AWS Secrets Manager.
I am having trouble configuring the roles needed for the Secret Manager to execute the Lambda. In my serverless.yml I have defined the following resources:
resources:
Resources:
RotateKeysRole:
Type: AWS::IAM::Role
Properties:
RoleName: rotate-keys-role
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
- secretsmanager.amazonaws.com
Action: sts:AssumeRole
and attach this role to the rotation Lambda like this:
functions:
rotateKeys:
handler: lambdas.rotate_keys.handler
role: RotateKeysRole
Yet, when I try to set up Secrets Manager to use this Lambda for rotating secrets I will get the following error message:
Secrets Manager cannot invoke the specified Lambda function. Ensure
that the function policy grants access to the principal
secretsmanager.amazonaws.com
which puzzles me as this principal is specified. Inspecting the role in the IAM console did not reveal anything that seemed wrong to me.
How do I correctly configure the role setup in this scenario?
The procedure of setting up permissions for a lambda function which rotates AWS Secrets Manager secrets is explained in the docs. [1]
To put it in a nutshell, you need two steps:
Add a trust policy to the lambda function. This can be achieved using the CloudFormation resource AWS::Lambda::Permission in the serverless.yml file. However, it is a little bit tricky to set this up, because you need to depend on the function being created. That is why the DependsOn is necessary and its value must be structured as follows: <function-name-with-first-letter-uppercase>LambdaFunction.
Add statements for the lambda function to call the AWS Secrets Manager API to update the secret. In the following example, I added these statements (for the Single user rotation case - see docs [1]) to the customer managed policy called rotateKeysPolicy.
Note: The function name is referenced in the DependsOn attribute. It is also referenced in the condition StringEquals and the attribute FunctionName as: arn:aws:lambda:${self:custom.region}:${self:custom.accountId}:function:${self:service}-${self:provider.stage}-rotateKeys. Keep in mind to change them if you change your function name.
Here is how the serverless.yml file should look like:
service:
name: <your-service-name>
provider:
name: aws
region: '<your-region>'
custom:
region: ${self:provider.region}
accountId: <your-account-id>
resources:
Resources:
FunctionRole:
Type: AWS::IAM::Role
Properties:
RoleName: basic-function-role
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: rotateKeysPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- secretsmanager:DescribeSecret
- secretsmanager:GetSecretValue
- secretsmanager:PutSecretValue
- secretsmanager:UpdateSecretVersionStage
Resource: '*'
Condition:
StringEquals:
'secretsmanager:resource/AllowRotationLambdaArn': "arn:aws:lambda:${self:custom.region}:${self:custom.accountId}:function:${self:service}-${self:provider.stage}-rotateKeys"
- Effect: Allow
Action:
- secretsmanager:GetRandomPassword
Resource: '*'
- Effect: Allow
Action:
- ec2:CreateNetworkInterface
- ec2:DeleteNetworkInterface
- ec2:DescribeNetworkInterfaces
Resource: '*'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
LambdaInvokePermission:
Type: AWS::Lambda::Permission
DependsOn: RotateKeysLambdaFunction
Properties:
FunctionName: "arn:aws:lambda:${self:custom.region}:${self:custom.accountId}:function:${self:service}-${self:provider.stage}-rotateKeys"
Action: lambda:InvokeFunction
Principal: 'secretsmanager.amazonaws.com'
functions:
rotateKeys:
handler: lambdas.rotate_keys.handler
role: FunctionRole
You have to replace <your-service-name>, <your-region>, <your-account-id> and upload your rotation code using e.g. the package -> include attributes.
Note: There are templates for the lambda function which update the secrets. [2][3]
Please also keep in mind to configure your VPC correctly for the lambda function being able to access the AWS Secrets Manager service over the network. [4]
References
[1] https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-required-permissions.html
[2] https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-create-generic-template.html
[3] https://github.com/aws-samples/aws-secrets-manager-rotation-lambdas
[4] https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotation-network-rqmts.html
I had the same issue today. I ran this and it worked for me:
aws lambda add-permission \
--function-name ARN_of_lambda_function \
--principal secretsmanager.amazonaws.com \
--action lambda:InvokeFunction \
--statement-id SecretsManagerAccess
https://docs.aws.amazon.com/secretsmanager/latest/userguide/troubleshoot_rotation.html
Your policy is incorrect.
The service is secretsmanager but the action you defined is sts:AssumeRole which is from AWS Security Token Service.
A full access policy would be:
Effect: "Allow"
Action: "secretsmanager:*"
Resource: "*"
But you should limit the actions and the Resource the lambda can use.
For this you can use the policy builder which can be found in IAM->Policies.
After creating a policy in the editor you can click on the JSON Tab and see the format. Then you need to adapt it to your serverless yaml format.
I hope I can help you!
Dominik
I'm working on an application and I'm struggling about an issue. My application has Lambdas and DynamoDBs services in which the former needs permissions to call the latter. I solve this creating a role with Principal equals Service: lambda.amazonaws.com.
I'd like to give access to other developers to create roles too in a way which allows developers to create only roles whose principal is a service or federated and deny if it is user or account.
For example, this role would be allowed:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action:
- sts:AssumeRoleWithWebIdentity
Path: ...
Policies: ...
and this would not be allowed:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
AWS: arn:aws:iam::<accountid>:user/<username>
Path: ...
Policies: ...
I'm trying to reach this because a user could create a role with admin access and assume it.
Also, is there another way to achieve to solve this issue?
I didn't find a solution for my issue and a workaround was creating services roles with a specific path (/custom-iam/service-role/ for instance) and allow developers to pass only roles with such path:
Effect: Allow
Action: iam:PassRole
Resource: 'arn:aws:iam::*:role/custom-iam/service-role/*'
I have read the "Specifying Principals in a Policy" doc: https://docs.aws.amazon.com/AmazonS3/latest/dev/s3-bucket-user-policy-specifying-principal-intro.html, and am inferring some behaviors from there and other SO (like aws lambda function getting access denied when getObject from s3) questions that do not specifically deal with Cloudformation.
I am still stumped on this error when I try to create a policy that grants a foreign role access to a local bucket. The error from Cloudformation is: Policy document should not specify a principal.
Situation Breakdown
I have two AWS accounts. Account A creates a bucket, and I want to grant Account B write access to it.
In Account A Cloudformation I have created a Policy that that grants an Account B role access to said bucket. Guide from https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html. That role exists for Account B.
AccountBWriteToS3Policy:
Type: 'AWS::IAM::Policy'
Properties:
PolicyName: AccountBWriteToS3Policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Principal:
AWS: 'arn:aws:iam::123456789876:role/AccountBRole'
Effect: Allow
Action:
- 's3:PutObject'
- 's3:ListBucket'
Resource: !Sub
- '${bucketArn}/*'
- bucketArn: !GetAtt
- AccountABucket
- Arn
Roles:
- AccountARole
However, cloudformation fails to execute, and rolls back with an error
Policy document should not specify a principal.
I'm fairly stumped.
Can anyone explain this error?
Can anyone prescribe a path forward?
This seems like a simple and common need, covered in numerous examples. Maybe I'm supposed to specify the policy within the bucket declaration itself instead of creating an account-wide policy?
you need to create a role with "Trust policy" with the principle and then a "permission policy" to allow read/write access to the S3 Bucket.
Here is a snippet from my Cloudformation.
Role:
Type: "AWS::IAM::Role"
Properties:
RoleName: !Sub '${RuleName}-Role'
Path: "/"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal: !Sub 'arn:aws:iam::${AccountID}:user/*'
Action: sts:AssumeRole
RolePolicies:
Type: "AWS::IAM::ManagedPolicy"
Properties:
ManagedPolicyName: !Sub '${RuleName}-RolePolicies'
Roles:
- Ref: "Role"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:Get*
- s3:Put*
- s3:List*
- s3:AbortMultipartUpload
Resource:
- !Ref Bucket
Ref: Cross account tutorial
I have a managed policy that allows or read access to a kinesis stream (AWSLambdaKinesisExecutionRole), I am trying to add additional permissions to also allow write access to PutRecord and PutRecords on to the kinesis stream.
My serverless.yml currently looks like -
resources:
Resources:
kinesisFullAccessRole:
Type: AWS::IAM::Role
Properties:
RoleName: kinesis-full-access-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole
Policies:
- PolicyName: kinesis-write-access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- kinesis:PutRecord
- kinesis:PutRecords
Resource:
- "arn:<some_arn>:stream/inbound-message-stream-dev"
I am still getting a is not authorized to perform: kinesis:PutRecord on resource error. What am I doing wrong?
If your serverless is creating the IAM role by it self you should had that to the iamRoleStatements like shown here
but it could be easier to create an IAM role in aws console and manage that yourself, and use it like here