AWS Lambda cross-account access denied - amazon-web-services

I have 2 AWS accounts. Account 1 has a CloudSearch domain that I need to query from a Lambda function in account 2. I've followed a tutorial here for creating a role in account 1 that allows cross-account access.
So, in account 1 I have a role arn:aws:iam::111111111111:role/my_cloudsearch_query_role that looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "cloudsearch:search",
"Resource": "*"
}
]
}
This role has one trusted entity, account 2, and I can see the correct account ID under the Trusted Entities section for the role in the IAM console.
In account 2, I've created a Lambda function with an execution role that looks like this:
{
"roleName": "my_cloudsearch_query_role",
"policies": [
{
"document": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::111111111111:role/my_cloudsearch_query_role"
}
]
},
"name": "oneClick_lambda_basic_execution_1526469274620",
"type": "inline"
}
]
}
My Lambda function (Python 3) code tries to query CloudSearch like this:
client = boto3.client('cloudsearchdomain', endpoint_url=endpoint)
response = client.search(
query="(and name:'foobar')",
queryParser='structured',
returnFields='curr_addr',
size=1
)
All calls to the Lambda function fail with the following error:
An error occurred (AccessDenied) when calling the Search operation: User: arn:aws:sts::222222222222:assumed-role/my_cloudsearch_query_role/my_lambda_func is not authorized to perform: cloudsearch:search on resource: myCSDomain
I'm positive that I've gotten the account IDs correct so there's no mix-up. Is there something else I need to do to get it working?

You need to assume the role in account 111111111111 and then use the returned credentials to create your client object. Use assume_role boto3 API call to get the credentials. Here's a sample code:
role_arn = "arn:aws:iam::111111111111:role/my_cloudsearch_query_role"
sts = boto3.client('sts', region_name="us-east-1")
token = sts.assume_role(RoleArn=role_arn, RoleSessionName="Session1")
credentials = token['Credentials']
access_key = credentials['AccessKeyId']
secret_key = credentials['SecretAccessKey']
token = credentials['SessionToken']
session = boto3.session.Session(
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
aws_session_token=token
)
client = session.client('cloudsearchdomain', endpoint_url=endpoint)
response = client.search(...)
Please update the role_arn with the correct details. Documentation about Session object can be found here.

Related

Boto3 list all accounts in an organization from member account

I have 2 accounts:
Account A (Master/Management account)
Account B (Member account)
I deployed a Lambda function in Account B(Member account) that lists all the accounts in the organization. Below is the Lambda function code:
import boto3
import json
org_client = boto3.client('organizations')
org_id='o-ib1qmgzn48'
def lambda_handler(event, context):
account_list = []
def list_aws_accounts_for_ou(ou_id):
# add accounts in the current/root OU
try:
lafp_paginator = org_client.get_paginator('list_accounts_for_parent')
lafp_page_iterator = lafp_paginator.paginate(ParentId=org_id)
for page in lafp_page_iterator:
for acct in page['Accounts']:
account_list.append(acct)
except org_client.exceptions.ClientError as error:
print(f'Error: {error}')
# add accounts in child ous
try:
lc_paginator = org_client.get_paginator('list_children')
ou_page_iterator = lc_paginator.paginate(
ParentId=org_id,
ChildType='ORGANIZATIONAL_UNIT'
)
for page in ou_page_iterator:
for child in page['Children']:
print(f"Adding accounts from child OU {child['Id']}")
account_list += list_aws_accounts_for_ou(child['Id'])
except org_client.exceptions.ClientError as error:
print(f'Error: {error}')
print(f"Found {len(account_list)} accounts in Oraganization {org_id}")
return json.loads(json.dumps(account_list, default=str))
This Lambda function has a role(GetAccountsListLambdaRole) with the below permissions:
Basic Lambda execution role
An inline policy that assumes a role in Account A(Management/Master account)
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::Account A:role/OrganizationsReadAssumeRole"
}
}
The 'OrganizationsReadAssumeRole' in Account A has the following permissions and trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"organizations:ListAccounts",
"organizations:ListAccountsForParent",
"organizations:ListChildren"
],
"Resource": "*"
}
]
}
The trust policy is:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::Account B:role/GetAccountsListLambdaRole"
},
"Action": "sts:AssumeRole",
"Condition": {}
}
]
}
When I run the Lambda function, I'm getting the below errors:
Error: An error occurred (AccessDeniedException) when calling the ListAccountsForParent operation: You don't have permissions to access this resource.
Error: An error occurred (AccessDeniedException) when calling the ListChildren operation: You don't have permissions to access this resource.
I have a couple of questions here:
Whatever I'm trying - getting an account list from a member account;
is it possible? If yes, how can I resolve the above errors and
achieve this?
Please help.
You need to actually assume the OrganizationsReadAssumeRole role in the lambda. This blog has an example:
sts_connection = boto3.client('sts')
acct_a = sts_connection.assume_role(
RoleArn="arn:aws:iam::222222222222:role/OrganizationsReadAssumeRole",
RoleSessionName="cross_acct_lambda"
)
ACCESS_KEY = acct_a['Credentials']['AccessKeyId']
SECRET_KEY = acct_a['Credentials']['SecretAccessKey']
SESSION_TOKEN = acct_a['Credentials']['SessionToken']
# create service client using the assumed role credentials, e.g. S3
org_client = boto3.client(
'organizations',
aws_access_key_id=ACCESS_KEY,
aws_secret_access_key=SECRET_KEY,
aws_session_token=SESSION_TOKEN,
)
... # rest of your code
Replace 222222222222 with the AWS account ID of the cross-account role that your function is assuming.

Identity Pool Role Can't Access S3 Bucket Access Point

Summary: I am using AWS Amplify Auth class with a pre-configured Cognito User Pool for authentication. After authentication, I am using the Cognito ID token to fetch identity pool credentials (using AWS CredentialProviders SDK) whose assumed role is given access to an S3 access point. I then attempt to fetch a known object from the bucket's access point using the AWS S3 SDK. The problem is that the request returns a response of 403 Forbidden instead of successfully getting the object, despite my role policy and bucket (access point) policy allowing the s3:GetObject action on the resource.
I am assuming something is wrong with the way my policies are set up. Code snippets below.
I am also concerned I'm not getting the right role back from the credentials provider, but I don't allow unauthenticated roles on the identity pool so I am not sure, and I don't know how to verify the role being sent back in the credentials' session token to check.
I also may not be configuring the sdk client objects properly, but I followed the documentation provided to the best of my understanding from the documentation (I am using AWS SDK v3, not v2, so slightly different syntax and uses modular imports)
Backend Configurations - IAM
Identity Pool: Authenticated Role Trust Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": [
"sts:AssumeRoleWithWebIdentity",
"sts:TagSession"
],
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "<MY_IDENTITY_POOL_ID>"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
Identity Pool: Authenticated Role S3 Access Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::<MY_ACCESS_POINT_NAME>/object/*"
}
]
}
Backend Configurations - S3
S3 Bucket and Access Points: Block All Public Access
S3 Bucket CORS Policy:
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"PUT",
"HEAD"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [],
"MaxAgeSeconds": 300
}
]
S3 Bucket Policy (Delegates Access Control to Access Points):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DelegateAccessControlToAccessPoints",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "*",
"Resource": [
"arn:aws:s3:::<MY_BUCKET_NAME>",
"arn:aws:s3:::<MY_BUCKET_NAME>/*"
],
"Condition": {
"StringEquals": {
"s3:DataAccessPointAccount": "<MY_ACCT_ID>"
}
}
}
]
}
Access Point Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAccessPointToGetObjects",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<ACCT_ID>:role/<MY_IDENTITY_POOL_AUTH_ROLE_NAME>"
},
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:<REGION>:<ACCT_ID>:accesspoint/<MY_ACCESS_POINT_NAME>/object/*"
}
]
}
Front End AuthN & AuthZ
Amplify Configuration of User Pool Auth
Amplify.configure({
Auth: {
region: '<REGION>',
userPoolId: '<MY_USER_POOL_ID>',
userPoolWebClientId: '<MY_USER_POOL_APP_CLIENT_ID>'
}
})
User AuthZ process:
On user login event, call Amplify's Auth.signIn() which returns type CognitoUser:
// Log in user (error checking ommitted here for post)
const CognitoUser = await Auth.signIn(email, secret);
// Get ID Token JWT
const CognitoIdToken = CognitoUser.signInUserSession.getIdToken().getJwtToken();
// Use #aws-sdk/credentials-provider to get Identity Pool Credentials
const credentials = fromCognitoIdentityPool({
clientConfig: { region: '<REGION>' },
identityPoolId: '<MY_IDENTITY_POOL_ID>',
logins: {
'cognito-idp.<REGION>.amazonaws.com/<MY_USER_POOL_ID>': CognitoIdToken
}
})
// Create S3 SDK Client
client = new S3Client({
region: '<REGION>',
credentials
})
// Format S3 GetObjectCommand parameters for object to get from access point
const s3params = {
Bucket: '<MY_ACCESS_POINT_ARN>',
Key: '<MY_OBJECT_KEY>'
}
// Create S3 client command object
const getObjectCommand = new GetObjectCommand(s3params);
// Get object from access point (execute command)
const response = await client.send(getObjectCommand); // -> 403 FORBIDDEN

AWS IAM / QuickSight - user is not authorized to perform: quicksight:GetDashboardEmbedUrl on resource

I am trying to use the embed QuickSight dashboard URL function in my ASP.NET MVC project. For testing, I'm simply trying to output the embed URL to a string. Here is the main part of my code:
var awsCredentials = new BasicAWSCredentials("redacted", "redacted");
AmazonSecurityTokenServiceClient stsClient = new AmazonSecurityTokenServiceClient(awsCredentials);
var tokenServiceRequest = stsClient.GetSessionToken();
var client = new AmazonQuickSightClient(
tokenServiceRequest.Credentials.AccessKeyId,
tokenServiceRequest.Credentials.SecretAccessKey,
tokenServiceRequest.Credentials.SessionToken,
Amazon.RegionEndpoint.APSoutheast2);
try
{
string machineTypeEmbedUrl =
client.GetDashboardEmbedUrlAsync(new GetDashboardEmbedUrlRequest
{
AwsAccountId = "redacted",
DashboardId = "redacted",
IdentityType = IdentityType.IAM,
ResetDisabled = true,
SessionLifetimeInMinutes = 100,
UndoRedoDisabled = false
}).Result.EmbedUrl;
}
catch (Exception ex)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest,ex.Message);
}
In order to support the permissions required, I have set up an IAM user with the STS Assume Role allowed as follows:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1551593192075",
"Action": [
"sts:AssumeRole"
],
"Effect": "Allow",
"Resource": "arn:aws:iam::redacted:role/assume-quicksight-role"
}
]
}
I have set up the role specified above with the following permissions, and set its trust policy so that the IAM user above can assume it.
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "quicksight:RegisterUser",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "quicksight:GetDashboardEmbedUrl",
"Resource": "arn:aws:quicksight:ap-southeast-2:redacted:dashboard/redacted",
"Effect": "Allow"
}
]
}
So as far as I can tell this should work. Debug reveals I do get a session token which is passed to the embedUrl request, however I get the following error:
InnerException = {"User:
arn:aws:iam:::user/api-dev-quicksight-user is not authorized
to perform: quicksight:GetDashboardEmbedUrl on resource:
arn:aws:quicksight:ap-southeast-2::dashboard/"}
I'm not sure why this happens? I have a user that can assume the right role, and the role has the right permissions to the dashboard in question. What am I missing here?
Try to change your role like this (notice the :: double colon before dashboard):
...
"Action": "quicksight:GetDashboardEmbedUrl",
"Resource": "arn:aws:quicksight:ap-southeast-2::dashboard/*",
"Effect": "Allow"
...
This should allow the user to access all the sub-resources under the dashboard.
To follow the Least Privilege principle recommend by AWS, you should list all your resources:
...
"Resource": [
"arn:aws:quicksight:ap-southeast-2::dashboard/",
"arn:aws:quicksight:ap-southeast-2::dashboard/redacted"]
...

Problem with AWS Lambda and cross account roles

I need to assume a cross account role to get access to an ElasticSearch domain for logging on AWS. Here's what I've done:
First, I have created a cross account role in ACCOUNT1. The role name is LoggerAccessToES and the trust relationship is something like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
},
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::ACCOUNT1:root",
"arn:aws:iam::ACCOUNT2:root"
]
},
"Action": "sts:AssumeRole",
"Condition": {}
}
]
}
Then, on ACCOUNT2, I have created a Lambda function to assume the above role with this code:
sts_client = boto3.client('sts', region_name=Config.AWS_ES_REGION)
assumed_role_object=sts_client.assume_role(
RoleArn="arn:aws:iam::ACCOUNT1:role/LoggerAccessToES",
RoleSessionName="AssumeLoggerAccessToESSession1"
)
When I invoke the lambda (basically the lambda is attached to an SNS topic), I get the error:
botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the AssumeRole operation: Access denied
I've already tried everything was suggested by other guys in other questions and I also googled the problem but I couldn't find the resolution. What am I doing wrong here?
From what i understand, you want to assume a role in Account 1 using the lambda in account 2.
This would require two roles to be created -
The first role needs to be created in the Account 2 which is to be attached to the Lambda. This role needs to have the following permission attached -
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::ACCOUNT1:role/LoggerAccessToES"
}
}
The above policy can be added to your existing lambda execution role.
For the second part, only the trust relationship of the Role LoggerAccesstoEs needs to be addedin Account 1 shown below-
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT2:root"
},
"Action": "sts:AssumeRole",
"Condition": {}
}
]
}
The first role policy allows the lambda to use the AssumeRole.
The second policy allows the Account 1 to trust the AssumeRole request from Account 2.

AWS ElasticSearch write to account "A" from lambda in account "B"

I have an AWS ElasticSearch Cluster in account "A".
I'm trying to create a lambda (triggered from a DynamoDB Stream) in account "B" that will write to ES in account "A".
I'm getting the following error:
{
"Message":"User: arn:aws:sts::AccountB:assumed-role/lambdaRole1/sourceTableToES is not authorized to perform: es:ESHttpPost on resource: beta-na-lifeguard"
}
I have tried putting the STS as well as the ROLE into the ES access policy (within account "A") with no luck. Here is my policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::AccountA:user/beta-elasticsearch-admin"
},
"Action": "es:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::AccountA:user/beta-elasticsearch-readwrite",
"arn:aws:iam::AccountA:role/beta-na-DynamoDBStreamLambdaElasticSearch",
"arn:aws:sts::AccountB:assumed-role/lambdaRole1/sourceTableToES",
"arn:aws:iam::AccountB:role/service-role/lambdaRole1"
]
},
"Action": [
"es:ESHttpGet",
"es:ESHttpPost",
"es:ESHttpPut"
],
"Resource": "*"
}
]
}
In my code above I was adding arn:aws:sts::AccountB:assumed-role/lambdaRole1/sourceTableToSNS into the AccountA ES access list, that is wrong. Instead do the following:
I already had arn:aws:iam::AccountA:role/beta-na-DynamoDBStreamLambdaElasticSearch in the ES access list, I needed to add a trust relationship (from the IAM role screen) for that role to be assumable by AccountB. I added this into the trust relationship:
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::AccountB:root"
},
"Action": "sts:AssumeRole"
}
Then, in my accountB lambda code, I needed to assume that role. Here is the relevent code from the lambda.
var AWS = require('aws-sdk');
var sts = new AWS.STS({ region: process.env.REGION });
var params = {
RoleSessionName: "hello-cross-account-session",
RoleArn: "arn:aws:iam::accountA:role/beta-na-DynamoDBStreamLambdaElasticSearch",
DurationSeconds: 900
};
sts.assumeRole(params, function (err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
context.fail('failed to assume role ' + err);
return;
}
log("assumed role successfully! %j", data)
postToES(bulkUpdateCommand, context);
});
When you create a "role" for another account you also need to setup the "Trust relationships". This is done in the AWS IAM console under "Roles". Second tab for your role is "Trust relationships". You will need to specify the account details for the other account as trusted.
The "Trust relationships" is a policy document itself. Here is an example that will allow you to call AssumeRole from another account to my AWS account.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::2812XXXXYYYY:root"
},
"Action": "sts:AssumeRole"
}
]
}
In your role, just specify permissions as normal just like you were granting permissions for another IAM user / service (e.g. remove all those account type entries). The Trust relationships policy document defines who can call AssumeRole to be granted those permissions.
Creating a Role to Delegate Permissions to an IAM User
Modifying a Role