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.
Related
1, How can I see 'which role' is being used to execute a lambda function? I have logged event in Cloudwatch but there is nothing regarding IAM role or AssumeRole.
2, Is AssumeRole is much different from a lambda execution role? Right now in my lambda, I have a function with an authorizer. The authorizer will check the token of the user and in the call back it will pass generated policy.
So what I have understood is,
First, we call the "Function" and Automatically it will trigger authorizer. The authorizer will pass an IAM role to the "Function" then it will execute it. So my question is, is this AssumeRole is a lambda:Execute role?
So what's the difference between the Default lambda execution role and this AssumeRole?
You can use AWS CloudTrail for this. See https://docs.aws.amazon.com/lambda/latest/dg/logging-using-cloudtrail.html
Assumed role is the role the request acts upon. The concept of role is the most core in the AWS, to perform an action (like an API call) you have to have a role.
The authorizer uses AssumeRole to assume a role to the client. AssumeRole generates temporal IAM credential an entity can access AWS resources with. On the other hand, an execution role is the role a resource (like AWS Lambda) acts upon. For example, if your Lambda reads from a DynamoDB table, its execution role must include a permission dynamodb:GetItem - this doesn't have to be included in the assumed role.
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.
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.
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.
Conflicting documentation
The documentation here, pertaining to AssumeRole, seems to contradict itself in one continuous block:
You must call this API using existing IAM user credentials. For more
information, see Creating a Role to Delegate Permissions to an IAM
User and Configuring MFA-Protected API Access.
This is an unsigned call, meaning that the app does not need to have
access to any AWS security credentials in order to make the call.
The contradictions are given bold emphasis.
Code sample
The code sample provided here certainly seems to require credentials:
AmazonSecurityTokenServiceClient securityTokenServiceClient = new AmazonSecurityTokenServiceClient(
Config.AccessKey,
secretKeyAsString,
securityTokenServiceConfig);
…
AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest
{
DurationSeconds = sessionDurationSec,
RoleArn = roleArn,
RoleSessionName = awsUsername,
ExternalId = groupSid
};
…
assumeRoleResponse = securityTokenServiceClient.AssumeRole(assumeRoleRequest);
In conclusion
Which is true? Are the requests in the code sample truly redundant?
Thank you!
The AssumeRole API call does require existing AWS credentials.
In order to assume an IAM role, an existing set of credentials must be used so that AWS knows who is assuming the role. This is so that AWS can verify that the assuming party is allowed to assume the role.
In the documentation:
This is an unsigned call, meaning that the app does not need to have access to any AWS security credentials in order to make the call.
This does appear to be incorrect information.
This is indeed an error in the docs, which is in the process of being corrected. AssumeRole does require existing long-term (IAM User) or temp credentials credentials to call. It is the two federation equivalents, AssumeRoleWithSAML and AssumeRoleWithWebIdentity that can be called without credentials. Sorry for the confusion!