Im building a serverless application using the following AWS technologies:
AWS Cognito
AWS Lambda
AWS API gateway
AWS IAM
All requests from the client (programmed in Angular2) go through API gateway.
I have created a IAM role for signed in users: Cognito_MyApp_Auth.
I use a Cognito User Pool Authorizer to make sure all calls to my API are from valid signed-in users.
Question: How can I grant the Lambda function the same permission as the signed in user?
Use case: A signed in user may only create, edit or delete in his own S3 bucket, so granting Lambda full permission to S3 is not an option.
Cognito_MyApp_Auth does not set different permissions from user to user. It only defines the permissions for any signed user. So you should allow access to all buckets in this role's policy. (However, I'd limit this access to buckets starting with a specific prefix.)
Fortunately, when Lambda is invoked via Cognito SDK, using Cognito provided credentials, context object passed to handler carries Cognito identity used to invoke the Lambda.
For Node.js you can see how to get this information:
http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html#nodejs-prog-model-context-example
if (typeof context.identity !== 'undefined') {
console.log('Cognito
identity ID =', context.identity.cognitoIdentityId);
}
Once you have the identity id, you can set up your own logic to limit the user's access to her own bucket.
This was the case when you call the Lambda directly from the application using Cognito SDK. If you use API Gateway, there is a good post from an AWS employee: https://forums.aws.amazon.com/thread.jspa?messageID=717379
There is no direct way to grant the Lambda function the same permissions as the signed in user; however, you should be able to implement your use case using Cognito identity pools and policy using the cognito-identity context variable.
For this approach, you won't use Cognito user pools nor the Cognito user pool authorizer. Instead, you use a Cognito identity pool for federated identity. Set the authorization on you API methods as AWS_IAM. Don't use Lambda as the integration, but instead use the AWS proxy to integrate directly to S3. Set the integration request to use the caller's identity.
To cal the API you'll need to use Cognito to get temporary credentials and then sign your API request with them.
With this approach, the S3 call is made with Cognito identity context, so you can attach policy which uses cognito-identity variables to the IAM role associated with authenticated users in your Cognito identity pool.
For example:
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
]
"Resource": [
"arn:aws:s3:::BUCKET_FOR_USER_${cognito-identity.amazonaws.com:sub}/*"
]
}
See this post for a similar use case.
Related
Executing import amplify auth for Cognito User Pool and Identity Pool and passing on the Web Client as well as the Native client fails with the below Error. Is there something that is missing please let me know. TIA
Cannot import Identity Pool without roles.
Error: Cannot import Identity Pool without roles.
at IdentityPoolService.getIdentityPoolRoles (/usr/local/lib/node_modules/#aws-amplify/cli/node_modules/amplify-provider-awscloudformation/src/aws-utils/IdentityPoolService.ts:88:13)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at importServiceWalkthrough (/usr/local/lib/node_modules/#aws-amplify/cli/node_modules/#aws-amplify/amplify-category-auth/src/provider-utils/awscloudformation/import/index.ts:322:74)
at Object.importResource (/usr/local/lib/node_modules/#aws-amplify/cli/node_modules/#aws-amplify/amplify-category-auth/src/provider-utils/awscloudformation/import/index.ts:45:42)
at Object.executeAmplifyCommand (/usr/local/lib/node_modules/#aws-amplify/cli/node_modules/#aws-amplify/amplify-category-auth/src/index.js:421:3)
at executePluginModuleCommand (/usr/local/lib/node_modules/#aws-amplify/cli/src/execution-manager.ts:178:3)
at executeCommand (/usr/local/lib/node_modules/#aws-amplify/cli/src/execution-manager.ts:30:5)
at Object.run (/usr/local/lib/node_modules/#aws-amplify/cli/src/index.ts:205:5)
Ran into this issue as well.
The Amplify Auth import docs mention the following:
Your Identity Pool needs:
an Authenticated Role with a trust relationship to your Identity Pool
an optional Unauthenticated Role if you want to use any guest user access for your Amplify categories. (Example: Guest access for your S3 buckets or REST API endpoints)
However, I:
Using an identity pool with an "authenticated" role with proper trust relationship intact (role, policy, identity pool role attachment).
Made sure the IAM role I was using to deploy this change had proper permissions to list and read identity pools & roles.
Still no help.
I then enabled "Allow unauthenticated identities" (although I did not want this) and things worked smoothly. This is because this setting will automatically generate two authenticated and unauthenticated roles and attach them for you.
However, as I did not want unauthenticated identity access, I disabled that again. Based off that setting working, I wondered if that Unauthenticated Role was truly optional, well it turns out it's not. At least not in the latest Amplify system. Someone must have changed this behaviour without updating the docs.
Solution:
Your Identity Pool needs:
an Authenticated Role with a trust relationship to your Identity Pool
an Unauthenticated Role with a trust relationship to your Identity Pool
To check if your identity pool is set up properly run this function in the AWS CLI:
aws cognito-identity get-identity-pool-roles --identity-pool-id "your identity pool id here"
You should get something like this:
{
"IdentityPoolId": "your identity pool id here",
"Roles": {
"authenticated": "your authenticated role ARN here"
"unauthenticated": "your authenticated role ARN here"
}
}
Is it possible for Lambda to be executed via API Gateway with an assumed role on behalf of a Cognito authenticated user?
Right now I'm doing the role assumption manually, from within the Lambda code:
const assumedRole = await sts.send(new AssumeRoleWithWebIdentityCommand({
RoleArn: 'some role ARN',
RoleSessionName: event.requestContext.authorizer.jwt.claims['cognito:username'],
WebIdentityToken: event.headers.authorization
}));
But I would like to avoid that, and have the Lambda be executed with this assumed role already.
What I am having difficulty with is figuring out what IAM Role and policy is required to achieve that.
Thanks!
You can pass cognito token from header of your request to the lambda and then make a get get credentials for identity call. That would be the best approach for assuming role of your cognito user dynamically. If you have configured cognito to grant role based on token you don't even need to explicitely provide a role arn in the call.
We are planning to use AWS WebIdentityFederation for a single page application.
I have created OIDC provider, which is an external ADFS and created a WebIdentityFederation assumed role. I am able to generate id_token through ADFS. In this id_token, I have added Role claim, which is AD Groups (I can change to AWS Role ARNs if needed) and then generate temporary STS tokens from AWS STS API.
However, it looks like, AWS WebIdentityFederation role doesn't validate any role claim inside id_token, like AWS does for SAML federation. This creates a authorization question, if an user have valid id_token, they can assume any WebIdentityFederation role.
For example: If I have two WebIdentityFederation roles READONLY and ADMIN, then an user with a valid id_token can assume READONLY and ADMIN both, even if in id_token user has READONLY role.
Is there any way to validate role or any other custom claims present in id_token? Can it be done through Trust Policy conditions?
I have a Cognito User Pool and an API Gateway using Cognito User Pool authorizer. Each user in the pool is assigned to a group with an IAM role. With this setup at hand, the client authenticates with Cognito and then sends ID Token to the API Gateway, which authorizes the request and passes it to a Lambda function. The lambda then executes other services (DynamoDB and S3). My question is which credentials do I need to use in the lambda?
One option is to use ID Token passed to the lambda from API Gateway to get temporary credentials from STS. This basically allows me to use user's group IAM Role to access other AWS resources and would look as follows:
var credentials = new CognitoAWSCredentials(
"us-east-1:...", // Identity pool ID
RegionEndpoint.USEast1 // Region
);
credentials.AddLogin(
"cognito-idp.us-east-1.amazonaws.com/us-east-...", // User pool ID
"eyJraWQiOiJi..." // ID token
);
// use credentials to access AWS services
Another option is to use the execution role assigned to the Lambda function, but it almost feels like this is better suited for other scenarios... perhaps those where lambda is executed as part of an event? I also don't seem to understand how to get ahold of those credentials within lambda itself.
Is there something else I'm missing? What's a good approach to take here?
Update
What I had above was almost right, but not quite. First of all, figure out if your users will need unauthorized access. If so, go to your identity pool and enable unauthorized access.
To get access/secret keys for unauthorized users you'd do something like this (Using C# SDK, but the following concepts apply to other languages):
var client = new AmazonCognitoIdentityClient(
new AnonymousAWSCredentials(), // We're making public API calls
RegionEndpoint.USEast1); // Your Region
var identityId = await client.GetIdAsync(new GetIdRequest
{
AccountId = "****", // AWS account ID
IdentityPoolId = "us-east-1:****" // Identity Pool ID
});
var credentials = await client.GetCredentialsForIdentityAsync(new GetCredentialsForIdentityRequest()
{
IdentityId = identityId.IdentityId
});
// Use credentials.Credentials when calling AWS services
The above will issue credentials with permissions defined by the Unauthenticated IAM Role defined in your Identity Pool.
To get credentials for authenticated users you'll do this:
var client = new AmazonCognitoIdentityClient(
new AnonymousAWSCredentials(), // We're making public API calls
RegionEndpoint.USEast1); // Your region
var getIdentityIdRequest = new GetIdRequest
{
AccountId = "****", // AWS Account ID
IdentityPoolId = "us-east-1:****" // Identity Pool ID
};
getIdentityIdRequest.Logins.Add(
"cognito-idp.us-east-1.amazonaws.com/us-east-1****", // User Pool ID
idToken); // ID Token
var identityId = await client.GetIdAsync(getIdentityIdRequest);
var getCredentialsRequest = new GetCredentialsForIdentityRequest()
{
IdentityId = identityId.IdentityId
};
getCredentialsRequest.Logins.Add(
"cognito-idp.us-east-1.amazonaws.com/us-east-1****",
idToken);
var credentials = await client.GetCredentialsForIdentityAsync(getCredentialsRequest);
// Use credentials.Credentials when calling AWS services
You'll need the ID Token for this, so authenticate first and grab the ID token from the response. If the user holding the ID token belongs to a group in Cognito with an assigned IAM Role, the credentials returned from the above code will be for that role. If the user isn't assigned to a group or the group has no role, you'll get credentials for Authenticated User IAM Role assigned to the Identity Pool.
Also, as was suggested by #thomasmichaelwallace, the above can be done either from Lambda or from outside API Gateway/Lambda but only if you're using AWS_IAM Authorizer because it's the only one that accepts secret and access keys for authorization. If you're just using Cognito Authorizer, like i was, you'll need to pass ID token to your lambda and have your lambda gets credentials.
From your example, you're effectively using the "built-in" method for this (Cognito Federated Identities). Arguably you might take this even further and access your resources directly from your application using those credentials (rather than putting API-Gateway + Lambda in the way).
The execution role in Lambda is the role (credentials) that the lambda is invoked with (they do not need to be set, and no new CognitoAWSCredentials needs to be called by you). As you've suggested, this is to limit what a lambda can do (on a per-lambda basis, rather than the invoker).
If Cognito Federated Identities are working for you, then it makes sense for you to use them as they provide a way to use IAM to enforce your application's authorisation layer; and arguably mean that you're responsible for building less. Not all applications fit into this pattern, which is why AWS give you options.
Documentation for IAM state that the Resource-property for AttachUserPolicy and DetacherUserPolicy should be set to the user principal the managed policy will be attached to. While the documentation for iot:AttachPrincipalPolicy and iot:DetachPrincipalPolicy are not as detailed, the error message
AccessDeniedException: User: arn:aws:sts::ACCOUNT_ID:assumed-role/ROLE/CognitoIdentityCredentials is not authorized to perform: iot:AttachPrincipalPolicy on resource: COGNITO_ID
leads me to believe it expects an ARN for Cognito identity or identity pool. However, specifying ARNs shown in Cognito documentation result in the same error. Cognito identity ARNs also fail validation in the policy generator.
What resource should I specify so that iot:AttachPrincipalPolicy and iot:DetachPrincipalPolicy are permitted to attach/detach IoT policies on a Cognito identity?
Using Cognito identities with AWS IoT has two slightly different paths. Cognito identity pools support identities which are either unauthenticated (any agent can get credentials) or authenticated (users tied to a provider like Cognito User Pools, Facebook, OpenID, etc). AWS IoT supports both cases when using Cognito to get credentials.
When using the unauthenticated case, you must attach a policy to the cognito_unauth_role that gets created with your Cognito identity pool. The Identity Access & Management (IAM) console is where you attach a policy to this role. To get started, try attaching the managed policy "AWSIoTDataAccess". Then, your unauthenticated Cognito IDs can get credentials (access key, secret key, session token) which are passed to AWS IoT to establish a connection.
When using the authenticated case, you must attach a policy to the cognito_auth_role that gets created with your Cognito identity pool AND the Cognito ID which is created for each authenticated entity. The AWS IoT API AttachPrincipalPolicy is what you use to attach an AWS IoT policy to the Cognito ID. The format of that API call in the CLI would be like: aws iot attach-principal-policy --policy-name myPolicy --principal us-east-1:abcd1234-5678-1234-abcd1234efgh. The principal in this case is the Cognito ID of the authenticated entity. Only with both policies in place will the credentials returned by Cognito work to make connections to AWS IoT.