Aws lambda cross account access - amazon-web-services

I have a lambda that consumes data from another AWS account's Athena. So I have a role that my lambda assumes which have cross-account access. I had used STS client in my lambda. sometimes lambda is running perfectly fine and sometimes it breaks and gives me this error.
"errorMessage": "An error occurred (ExpiredTokenException) when calling the StartQueryExecution operation: The security token included in the request is expired",
"errorType": "ClientError",
STS client i used in my code is :
def assume_role_to_session(role_arn, session_name):
client = boto3.client('sts')
response = client.assume_role(RoleArn=role_arn, RoleSessionName=session_name, DurationSeconds=900)
return boto3.Session(
aws_access_key_id=response['Credentials']['AccessKeyId'],
aws_secret_access_key=response['Credentials']['SecretAccessKey'],
aws_session_token=response['Credentials']['SessionToken'],
region_name='us-east-1')
assume_role_athena_session = assume_role_to_session(role_arn='arn:aws:iam::XXXXXXXXXXX:role/role-name',
session_name='AthenaLambdaSession')
How does this work? I want my lambda to run anytime not just sometimes. What could be the problem?

Look at this document
RoleSessionName (string) -- [REQUIRED]
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 using the temporary security
credentials will expose the role session name to the external account
in their CloudTrail logs.
So try to replace AthenaLambdaSession with unique RoleSessionName. Or if you want to reuse existing session, try to check session timeout to make sure that enough time to execute your lambda task (max 5 minutes). If < 5 minutes, re-new it.

Related

Access to the resource https://queue.amazonaws.com/ is denied error in AWS Lambda function

I am trying to set cross account data transfer from AWS Lambda in AWS account A to SQS in AWS account B using boto3. Below are the steps which I have followed.
Created an IAM role in account A which has "SendMessage" access to SQS queue in account B. (Given an ARN of SQS queue of account B)
Added an account ID of AWS account B in the trust relationship of an IAM role in account A.
Attached this IAM role to Lambda function and written a code to send the message to SQS queue using SQS queue URL.
Created an SQS queue in account B.
In the SQS queue access policy I have written a policy which will allow lambda role of account A to send message to its SQS queue.
================================================================================
After that when I am trying to test my lambda function, it is giving me below error.
[ERROR] ClientError: An error occurred (AccessDenied) when calling the SendMessage operation: Access to the resource https://queue.amazonaws.com/ is denied.
=====================================================================================
Can anybody please help to understand what's wrong here?.
This error can occur if you are attempting to access SQS via the boto3 Python library (e.g. OP's lambda) from inside a VPC with private DNS enabled.
Per AWS documentation:
Private DNS doesn't support legacy endpoints such as queue.amazonaws.com or us-east-2.queue.amazonaws.com.
(emphasis mine)
To solve this error:
Create a VPC endpoint for com.amazonaws.<region>.sqs in your VPC
Pass the appropriate service endpoint URL to the boto3.client() constructor:
import boto3
client = boto3.client('sqs', endpoint_url='https://sqs.%s.amazonaws.com' % region)
IAM permissions are left as an exercise to the reader.

How to check if an AWS STS access token is valid

I have a lambda function that uses AWS STS to generate temporary credentials and then sends the access token via HTTP to a Web API in an EC2 instance.
Is there a way to validate the received access token from the API?
Calling STS GetCallerIdentity will tell you if the credentials are usable to make API calls, and it will identify the underlying AWS account and assumed role.
For example:
aws sts get-caller-identity
{
"UserId": "AROAABCDEFGHIJKLMNOPQ:xyz",
"Account": "123456781234",
"Arn": "arn:aws:sts::123456781234:assumed-role/somerole"
}
Notes about the response object:
Account is the AWS account number of the account that owns/contains the calling entity
UserId is the unique identifier of the calling entity. The exact value depends on the type of entity that is making the call.
AWS security architecture assures you that any token generated by IAM represents a valid token, and that the given service that generated the token had permissions to do so. If you are concerned that some entity with elevated privileges generated a token, and that that token is not to be trusted, then you have a security configuration problem. You would need to check CloudWatch to see what entity generated the token, and revoke its permissions.
As #jarmod suggests, if a given token works, then it is valid. That is all you can know about its validity.

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/...

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 STS AssumeRole: Are IAM user credentials needed, or not?

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!