Introduction
When managing an AWS Organization, you often need to execute tasks across multiple AWS accounts. Instead of manually configuring credentials for each account, AWS provides cross-account IAM roles that allow services in one account (such as a Lambda function in the central account) to assume a role in another (the member account). This approach enhances security by avoiding static credentials and provides better control through IAM policies.
In this guide, we will set up a cross-account IAM role in a member account and configure a Lambda function in the central account to assume that role and perform actions, such as listing S3 buckets.
Creating the Lambda Function in the Central Account
To interact with AWS accounts in an AWS Organization, we will deploy a Lambda function in the central account. This function will assume a cross-account IAM role in a member account and execute actions like listing S3 buckets.
1️⃣ Creating the Lambda Function
First, create a new Lambda function using Python in the central account. The default execution role will be automatically generated.

2️⃣ Adjusting the Execution Role
When you create a Lambda function, AWS automatically generates an IAM execution role. By default, this role includes basic Lambda permissions, but it does not have permissions to assume roles in other accounts. To fix this, update the role’s policy to allow sts:AssumeRole
:
- Navigate to IAM > Roles and find the execution role created for your Lambda function.
- Attach the following inline policy to allow assuming roles:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "*"
}
]
}
This policy grants the Lambda function permission to assume any role. For security reasons, it’s best to restrict this to specific roles by replacing "Resource": "*"
with the exact ARN of the role in the member account.

3️⃣ Adding an Environment Variable
Now, let's head back to the Lambda console. To make our Lambda function more flexible and easier to maintain, we'll define the IAM role name of the member account as an environment variable, rather than hardcoding it directly in the function. This approach ensures that if we need to update the role in the future, we can do so without modifying the function code itself. It's a simple yet effective way to streamline updates and improve long-term manageability.

4️⃣ Writing the Lambda Function
Now, let’s write the Python code for our Lambda function. This function will:
✅ Retrieve the role name from environment variables.
✅ Assume the cross-account IAM role in the member account.
✅ Use temporary credentials to list S3 buckets in the member account.
Here’s the full code:
import boto3
import os
def lambda_handler(event, context):
role_name = os.environ['CROSS_ACCOUNT_ROLE_NAME']
account_id = "123456789012"
role_arn = f"arn:aws:iam::{account_id}:role/{role_name}"
session_name = 'LambdaSession'
sts_client = boto3.client('sts')
assumed_role_object = sts_client.assume_role(
RoleArn=role_arn,
ExternalId='0B7C3A30-4713-4825-8A83-64A15B3B3483',
RoleSessionName=session_name
)
credentials = assumed_role_object['Credentials']
# Use the temporary credentials to create a new client for the service you want to access
s3_client = boto3.client(
's3',
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken']
)
# Example: List S3 buckets
response = s3_client.list_buckets()
if 'Buckets' in response:
print('Existing buckets:')
for bucket in response['Buckets']:
print(f' {bucket["Name"]}')
else:
print('No S3 buckets found.')
🔹 Fetching Role Details – The function retrieves the role name from the environment variable and constructs the IAM role ARN dynamically.
🔹 Assuming the Role – Using AWS STS, the function assumes the cross-account role and receives temporary credentials.
🔹 Using Temporary Credentials – These credentials allow the function to interact with AWS services (e.g., S3) in the member account.
🔹 Interacting with AWS Services – It creates an S3 client and lists all available buckets in the member account.
Setting Up the IAM Role in the Member Account
To allow the Lambda function in the central account to access resources in the member account, we need to create an IAM role that grants the necessary permissions.
1️⃣ Generate a Unique External ID
For additional security, AWS recommends using an External ID when allowing cross-account access. This ensures that only your Lambda function can assume the role.
Run the following command to generate a UUID (Universally Unique Identifier):
alexanderhose@Alexanders-Air ~ % uuidgen
0B7C3A30-4713-4825-8A83-64A15B3B3483
We’ll use this as the External ID when creating the IAM role.
2️⃣ Creating the IAM Role
Now, let's create the IAM role in the member account:

For this example, we grant read-only access to S3 in the member account:
- In the Permissions policies step, search for
AmazonS3ReadOnlyAccess
. - Select the policy and click Next.
🚨 Security Tip: In production, create a custom policy with only the necessary permissions instead of using a broad managed policy.

5️⃣ Configuring the Trust Policy
The trust policy is the key component that defines who can assume this role. While it's possible to allow any service or user in the central account to assume the role, a more secure approach is to restrict access to a specific IAM role - such as the service-role/CrossAccountLambda-role-xq53wb4g
used by our Lambda function.
This ensures that only the intended function has permission to assume the role, reducing the risk of unauthorized access.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/service-role/CrossAccountLambda-role-xq53wb4g"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "0B7C3A30-4713-4825-8A83-64A15B3B3483"
}
}
}
]
}

Principal
– This should point to the Lambda execution role in the central account.Action
– Allows the role to be assumed usingsts:AssumeRole
.Condition
– Requires the correct External ID to be provided for security.
🎯 Running the Lambda Function in the Central Account
Navigate to AWS Lambda in the central account, select the Lambda function we created earlier, and click Test. You can use an empty test event since our function doesn't rely on any input.
Once the function runs, check the logs in Amazon CloudWatch or the Lambda console output. If everything is configured correctly, you should see a list of S3 buckets from the member account, similar to this:

This confirms that our cross-account IAM role setup is working as expected! 🎉
Common Issues and Troubleshooting
Invalid principal in policy: "AWS":"arn:aws:iam::123456789012:role/CrossAccountLambda-role-xq53wb4g"
Cause: The IAM role’s trust policy has an incorrect principal.
Solution: Ensure that the role ARN includes /service-role/
, like this:
"AWS": "arn:aws:iam::123456789012:role/service-role/CrossAccountLambda-role-xq53wb4g"
AccessDenied: User is not authorized to perform sts:AssumeRole
Cause: The Lambda function’s IAM policy does not allow sts:AssumeRole
.
Solution: Ensure the Lambda function’s IAM role includes a policy that explicitly grants permission to assume the target role.
{
"Effect":"Allow",
"Action":"sts:AssumeRole",
"Resource":"*"
}
An error occurred (AccessDenied) when calling the ListBuckets operation
Cause: The assumed role does not have permission to list S3 buckets.
Solution: Attach an S3 policy to the assumed role in the member account, such as:
{
"Effect": "Allow",
"Action": "s3:List*",
"Resource": "*"
}
Member discussion