AWS STS AssumeRole: Are IAM user credentials needed, or not? - amazon-web-services

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!

Related

Executing AWS Lambda with assumed role with web identity

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.

Can't grant cross-account access to a ECS task's role

Background:
I am trying to grant access for an ECS task in Account B to extract data from a DynamoDB table in Account A.
In theory, this works fine by: (1) creating a role in Account A that Account B is allowed assume (with a paired External ID), and then (2) granting that role access to the needed DynamoDB tables.
Problem:
When a process running in ECS assumes the ECS role (Account B), it creates a unique instance of that role, which apparently cannot be the target of a principal statement in the account. If I try granting access to the underlying role, that apparently has no affect.
Can I force ECS to use the original role, which I can grant as principal, rather an a temporary set which apparently can't then assume other roles?
The only workaround I can think of is to create a new user with programmatic API credentials, handoff those creds to the ECS task, and then then have the ECS task overrides it's own role with the one's belonging to the AWS key-pair. That's definitely an antipattern though, as far as I can tell, and it opens up the risk of those credentials being compromised.
Is there any way to do this without resorting to a manually created user and manually passed AWS creds?
Additional info:
I can grant to this principal arn:aws:iam::AcctB****:role/myrole but the ECS task is using this one at runtime: arn:aws:sts::AcctB****:assumed-role/myrole/45716b8c-40c8-4ca7-b346-1ff4ee94eb53.
Error message is: An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::AcctB****:assumed-role/myrole/45716b8c-40c8-4ca7-b346-1ff4ee94eb53 is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::AcctA****:role/ExternalRole
I faced the same situation and found a safe way to programmatically assume the role from an ECS task.
sts_client = boto3.client('sts')
identity = sts_client.get_caller_identity()
print(identity) # identity in account A
response = sts_client.assume_role(RoleArn=assume_role_arn, RoleSessionName=session_name)
session = boto3.Session(aws_access_key_id=response['Credentials']['AccessKeyId'],
aws_secret_access_key=response['Credentials']['SecretAccessKey'],
aws_session_token=response['Credentials']['SessionToken'])
The idea is to connect to account A, assume role from account B, init a session using temporary credentials and then init whatever client you need from that session.
external_client = session.client('sts')
identity = external_client.get_caller_identity()
print(identity) # identity in account B
This is more secure than creating a IAM user and share credentials between accounts because authentication is done internally.
Here you can find more information about how it works
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sts.html
Linking the following topic here: How to assume an AWS role from another AWS role?
TL;DR
Really make sure you don't have a typo, wrong name or external id in one of the policies.
ECS needs to assume your Task Role the same way you are trying to assume the role in Account B. This can't be changed. Even ECS tries to assume Account B role with assumed-role/... it works as expected when granted a principal with role/...

What's the use case for RoleSessionName when assuming a role in AWS and how it affects the performance

I have a scenario in which I want to have access to resources within one account from another one in AWS (cross-account access) in code. And I want to implement this access using NodeJs, implemented as lambda function and also as a long-running code on EC2.
Reading how to do this online, I know I need temporary credentials generated by aws.STS, like this:
const AWS = require('aws-sdk');
const sts = new AWS.STS();
const stsResults = await sts.assumeRole({
RoleArn: 'arn:aws:iam::111111111111:role/role_name',
RoleSessionName: 'STRING_VALUE',
}).promise();
const dynamodb = new AWS.DynamoDB({
region: 'us-east-1',
accessKeyId: stsResults.Credentials.AccessKeyId,
secretAccessKey:stsResults.Credentials.SecretAccessKey,
sessionToken: stsResults.Credentials.SessionToken
});
My question is about the RoleSessionName attribute which is a required one. I'm having a hard time understanding what it does and how I should use it. This is what the AWS documentation has to say about it:
RoleSessionName — (String) An identifier for the assumed role session.
Use the role session name to uniquely identify a session when the same
role is assumed by different principals or for different reasons. In
cross-account scenarios, the role session name is visible to, and can
be logged by the account that owns the role. The role session name is
also used in the ARN of the assumed role principal. This means that
subsequent cross-account API requests that use the temporary security
credentials will expose the role session name to the external account
in their AWS CloudTrail logs.
The regex used to validate this parameter is a string of characters
consisting of upper- and lower-case alphanumeric characters with no
spaces. You can also include underscores or any of the following
characters: =,.#-
Personally, I'm not concerned about security since both accounts are owned by the same company and the only reason to have multiple accounts is to logically separate resources. What I would like to know is the impact of this attribute on the performance of the assumeRole function call. Should I use the same RoleSessionName for all my lambda functions? Should I create a random ID each time I create a new session?
As per the documentation you quoted:
Use the role session name to uniquely identify a session when the same role is assumed by different principals or for different reasons.
Let's say you have an IAM Role and it is assumed by a program. This will return a set of temporary credentials that can be used to access AWS services.
In an audit trail, anything done by the Role will be tracked as having been done by the Role (not by the entity that assumed the Role). This makes it difficult to trace back the source of these API calls, since the role could be assumed by "different principals or for different reasons". For example, multiple programs might use the role.
To assist in tracing the 'origin' of such requests, the RoleSessionName is provided to identify the particular assumption. It's there to help you identify which app is using the credentials.

How to create recordset in Account2 from Account1

I am trying to create a route53 recordset from Account1 in Account2.
From reading other posts and online search I am thinking of doing something like this:
from boto3 import Session
session = Session(aws_access_key_id=*****,aws_secret_access_key=****,region_name='us-east-1')
r53_client = session.client('route53')
r53_resource = session.resource('route53')
Want to know from someone experienced if this is the right way to do this? Or is there a better way to achieve above?
Here is updated code:
def lambda_handler(event, context):
sts = boto3.client('sts')
response = sts.assume_role(
RoleArn='arn:aws:iam::***123:role/lambda',
RoleSessionName='my-random-session-name',
DurationSeconds= 900 # how many seconds these credentials will work
)
tempAccessKeyId = response['Credentials']['AccessKeyId']
tempSecretAccessKey = response['Credentials']['SecretAccessKey']
tempSessionToken = response['Credentials']['SessionToken']
client = boto3.client('route53',
region_name = 'us-west-2',
aws_access_key_id=tempAccessKeyId,
aws_secret_access_key=tempSecretAccessKey,
aws_session_token=tempSessionToken)
response = client.list_resource_record_sets(
HostedZoneId='***',
StartRecordName='test.example.com.',
StartRecordType='A'
)
print(response)
Based on the fact that you are doing this from an AWS Lambda function, the most secure way to do it would be:
In Account 1:
Create an IAM Role (Role 1) that will be used by the Lambda function
Assign permissions to the role that allows it to assume Role-2
Also assign any other permissions the Lambda function requires (you would normally add the AWSLambdaBasicExecutionRole managed policy to allow logging)
Assign Role 1 to the Lambda function
In Account 2:
Create an IAM Role (Role 2) with trust permissions that allows Role 1 in Account 1 to assume it
Grant Role 2 appropriate permissions to use Amazon Route 53
In your Lambda code, you would call AssumeRole() on Role 2. This will provide a set of temporary credentials that can be used to access Account 2 (as per your code, above).
See: Switching to an IAM Role (AWS API) - AWS Identity and Access Management
To make an API call to an AWS account, you either need credentials from that AWS account (eg credentials associated with an IAM User), or you need the ability to assume an IAM Role in that account.
So, in your example, if the credentials being provided belong to Account2, then you will be able to make API calls to Account2 (if that IAM User has been granted the necessary Route 53 permissions).
If you are frequently moving between accounts, you can instead specify a profile, which retrieves a different set of credential from the credentials file.
See: python - How to choose an AWS profile when using boto3 to connect to CloudFront - Stack Overflow

AWS IAM: AssumeRole vs GetSessionToken

I'm trying to generate a temporary credentials access key and secret key. I've used AssumeRole. The description says it generates an access key and secret key. But GetSessionTokenResult can also generate an access key and secret key. Then what's the use of assumeRole?
AWSSecurityTokenService awsSecurityTokenService =
AWSSecurityTokenServiceClientBuilder
.standard().withCredentials(new ProfileCredentialsProvider())
.withRegion(region).build();
AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest()
.withRoleArn(
"arn:aws:iam::account-id:role/p-27c229ade194_ec2")
.withRoleSessionName("RedshiftSession");
AssumeRoleResult assumeRoleResult = awsSecurityTokenService
.assumeRole(assumeRoleRequest);
GetSessionTokenRequest getSessionTokenRequest = new GetSessionTokenRequest();
getSessionTokenRequest.setDurationSeconds(1200);
GetSessionTokenResult getSessionTokenResult = awsSecurityTokenService
.getSessionToken(getSessionTokenRequest);
Credentials sessionCredentials = getSessionTokenResult.getCredentials();
final String adminAccessKeyId = sessionCredentials.getAccessKeyId();
final String adminAccessSecretKey = sessionCredentials
.getSecretAccessKey();
Earlier using assumeRole it showed error => aws:iam::user/admin is not sts:assumeRole on resource role aws:iam::role/role_id.
By adding aws:iam::user/admin in trusted relationship of role_id it worked.
If I will comment out AccessRole and its other called class. I can generate an access key and secret key. What's the purpose of using AssumeRole?
There are several methods to obtain temporary credentials, depending upon your requirements:
GetSessionToken gives you a set of temporary credentials based on your own IAM User. This is commonly used to activate Multi-Factor Authentication (MFA), or to create some scoped-down credentials for a situation where you want to limit access (eg giving access to an application that uploads/downloads data to S3, without giving any non-S3 access). The new credentials have, at most, the same permissions as you have (and never any more).
AssumeRole is used to obtain credentials that have a totally different set of permissions. For example, you might not have permission to access S3, but you might have permission to assume an "S3 access" role. The credentials returned then allow you to temporarily access S3. Another example is providing access to administrator permissions — rather than always using an account with Admin permissions, it is safer to temporarily assume an Admin role, do the admin activity, then return to using normal credentials. Less is likely to go wrong than always using Admin-level access.
Also, AssumeRole can be used to gain cross-account access. For example, a user in Account A could assume a role in Account B, which grants access to resources in Account B. This is not possible via GetSessionToken.
I always find this article useful to explain the differences: Understanding the API Options for Securely Delegating Access to Your AWS Account | AWS Security Blog