Skip to content

Lambda function to copy in S3 accross-accounts - Python code problem

0

Hello,

I'm new to Lambda and Python coding. I'd like to copy data from one S3 bucket to another one in a different AWS account. I believe that all of my roles and AssumeRole configurations are okay, except that my python code doesn't cover establishing a STSASSUMEROLE connection. I'd appreciate correcting my code.

`import boto3 import botocore import json import os import logging logger = logging.getLogger() logger.setLevel(logging.INFO)

s3 = boto3.resource('s3')

def lambda_handler(event, context): logger.info("New files uploaded to the source bucket.")

STS Code

sts_connection = boto3.client('sts')
acct_b = sts_connection.assume_role(
    RoleArn="arn:aws:iam::<AWSACCOUNTNUMBER>:role/test-lambda-trust",
    RoleSessionName="account2"

STS code

key = event['Records'][0]['s3']['object']['key']
    
source_bucket = event['Records'][0]['s3']['bucket']['name']

destination_bucket = os.environ['destination_bucket']

destination_bucket ='MYSOURCEBUCKET'

destination_bucket ='MYBUCHETNAME'

source = {'Bucket': source_bucket, 'Key': key}
    
try:
    response = s3.meta.client.copy(source, destination_bucket, key)
    logger.info("File copied to the destination bucket successfully!")
    
except botocore.exceptions.ClientError as error:
    logger.error("There was an error copying the file to the destination bucket")
    print('Error Message: {}'.format(error))
    
except botocore.exceptions.ParamValidationError as error:
    logger.error("Missing required parameters while calling the API.")
    print('Error Message: {}'.format(error))`

Thank you in advance, Mo

asked 2 years ago741 views
7 Answers
2

Hello.

To use assume_role in Lambda, I think you need to write code like the following.
https://repost.aws/knowledge-center/lambda-function-assume-iam-role

import boto3 
import botocore 
import json 
import os 
import logging 

logger = logging.getLogger() 
logger.setLevel(logging.INFO)


def lambda_handler(event, context): 
    logger.info("New files uploaded to the source bucket.")

    sts_connection = boto3.client('sts')
    acct_b = sts_connection.assume_role(
        RoleArn="arn:aws:iam::<AWSACCOUNTNUMBER>:role/test-lambda-trust",
        RoleSessionName="account2"
        )

    ACCESS_KEY = acct_b['Credentials']['AccessKeyId']
    SECRET_KEY = acct_b['Credentials']['SecretAccessKey']
    SESSION_TOKEN = acct_b['Credentials']['SessionToken']

    acct_b_s3 = boto3.resource('s3',
        aws_access_key_id=ACCESS_KEY,
        aws_secret_access_key=SECRET_KEY,
        aws_session_token=SESSION_TOKEN,)

    key = event['Records'][0]['s3']['object']['key']
    
    source_bucket = event['Records'][0]['s3']['bucket']['name']
    destination_bucket = os.environ['destination_bucket']
#    destination_bucket ='MYSOURCEBUCKET'
#    destination_bucket ='MYBUCHETNAME'

    source = {'Bucket': source_bucket, 'Key': key}
    
    try:
        response = acct_b_s3.meta.client.copy(source, destination_bucket, key)
        logger.info("File copied to the destination bucket successfully!")
    
    except botocore.exceptions.ClientError as error:
        logger.error("There was an error copying the file to the destination bucket")
        print('Error Message: {}'.format(error))
    
    except botocore.exceptions.ParamValidationError as error:
        logger.error("Missing required parameters while calling the API.")
        print('Error Message: {}'.format(error))
EXPERT
answered 2 years ago
EXPERT
reviewed 2 years ago
EXPERT
reviewed 2 years ago
2

I believe the answer from Riku Kobayashi is quite correct. I'd like to point out additionally that if the destination account is under your control or influence, it would be simpler and more performant to skip the whole AssumeRole step. Your Lambda function could simply run under its original execution role, with temporary credentials automatically provided by the Lambda platform, without you having to call the AssumeRole API.

The Lambda execution role would receive the required permissions to S3 from the IAM policies attached to the execution role, and from the S3 bucket policies of both the source and destination buckets in the remote account.

The only major technical consideration would be that this works when the S3 buckets are using either the default SSE-S3 encryption (which you can see in bucket properties in the console) or SSE-KMS with customer-managed keys. Cross-account access wouldn't work with SSE-KMS encryption when using AWS-managed KMS keys, in which case the S3 operations would have to be executed under a role assumed in the separate account, which is what you started with.

EXPERT
answered 2 years ago
0

The HeadObject API call requires the s3:GetObject permission to the object.

Here are some policy statements that you can add to your bucket policy, replacing the placeholders with your IAM role ARN and bucket ARN. The statements permit listing current object versions in the bucket; reading and deleting (as in downloading) objects; and writing and deleting objects (as in uploading) them.

You can use the same policy statements in the IAM policy you've attached to the role, but in that case, drop the "Principal" element from each statement, as it's not allowed in identity-based policies but implied by the principal (role or user) or group to which the policy is attached.

When the IAM role and the S3 bucket reside in different account, remember that you'll need to permit these permissions both in the IAM policy attached to the role and in the S3 bucket policy. Only allowing it in the S3 policy won't suffice, if the IAM role is in a different account.

{
    "Sid": "AllowBucketReads",
    "Effect": "Allow",
    "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/replace-with-your-role-name"
    },
    "Action": [
        "s3:GetBucketLocation",
        "s3:ListBucket"
    ],
    "Resource": "arn:aws:s3:::replace-with-your-bucket-name"
},
{
    "Sid": "AllowObjectReadAndDelete",
    "Effect": "Allow",
    "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/replace-with-your-role-name"
    },
    "Action": [
        "s3:DeleteObject",
        "s3:GetObject"
    ],
    "Resource": "arn:aws:s3:::replace-with-your-bucket-name/*"
},
{
    "Sid": "AllowObjectWriteAndDelete",
    "Effect": "Allow",
    "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/replace-with-your-role-name"
    },
    "Action": [
        "s3:DeleteObject",
        "s3:PutObject",
        "s3:PutObjectACL"
    ],
    "Resource": "arn:aws:s3:::replace-with-your-bucket-name/*"
}

EXPERT
answered 2 years ago
  • Thanks. I have it already declared as "s3:*" everywhere, but I will review that indeed. Should I explicitly define the access rights? or ALL * should also work?

0

Hello,

Thanks to both of you for your help. I will try the prpopsed code today. Thanks.

Regarding the AssumeRole concept, I'm running the POC accross my AWS accounts; however, in production the destination AWS account is managed/owned by a third-party, thus I must use AssumeRole in that case.

answered 2 years ago
0

Hello,

Thanks once again for your efforts; however, I'm still facing the same issue. By the way, I've had a success Lambda execution when I tried to copy to a bucket within the same account, but the problem remains if it's towards another account. Here's the output from the Lambda test logs, however, I've had the same error with the local copy, I believe because no EVENT from the source bucket is actually sent (correct me if I'm mistaken), in other words, this error below regarding the EVENT could be ignored.

Test Event Name
test7

Response
{
  "errorMessage": "'Records'",
  "errorType": "KeyError",
  "requestId": "003d3e4d-f885-4c81-9748-6359228235ac",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 29, in lambda_handler\n    key = event['Records'][0]['s3']['object']['key']\n"
  ]
}

Function Logs
START RequestId: 003d3e4d-f885-4c81-9748-6359228235ac Version: $LATEST
[INFO]	2024-07-08T13:01:30.597Z	003d3e4d-f885-4c81-9748-6359228235ac	New files uploaded to the source bucket.
LAMBDA_WARNING: Unhandled exception. The most likely cause is an issue in the function code. However, in rare cases, a Lambda runtime update can cause unexpected function behavior. For functions using managed runtimes, runtime updates can be triggered by a function change, or can be applied automatically. To determine if the runtime has been updated, check the runtime version in the INIT_START log entry. If this error correlates with a change in the runtime version, you may be able to mitigate this error by temporarily rolling back to the previous runtime version. For more information, see https://docs.aws.amazon.com/lambda/latest/dg/runtimes-update.html
[ERROR] KeyError: 'Records'
Traceback (most recent call last):
  File "/var/task/lambda_function.py", line 29, in lambda_handler
    key = event['Records'][0]['s3']['object']['key']END RequestId: 003d3e4d-f885-4c81-9748-6359228235ac
REPORT RequestId: 003d3e4d-f885-4c81-9748-6359228235ac	Duration: 512.91 ms	Billed Duration: 513 ms	Memory Size: 128 MB	Max Memory Used: 85 MB

Request ID
003d3e4d-f885-4c81-9748-6359228235ac

Is there also any other useful logs to read for Lambda to better trace the issue? that error I get is meaningless. Thanks,

answered 2 years ago
0

When you are copying data between buckets in different accounts, then regardless of whether you assign the permissions to the execution role of your Lambda function or to a separate role that you assume in your code, the one role or the other will have to have access to the buckets in both AWS accounts.

The identity-based IAM policies attached to the IAM role suffice for granting access to the S3 bucket in the same account with the IAM role, but in order for the copy operation to be able to deliver data to (if it's the destination) or read it from (as the source) the S3 bucket in the other account, the bucket policy of that bucket must permit the s3:PutObject (if it's the destination) or s3:GetObject and probably s3:ListBucket and s3:GetBucketLocation (if it's the source) permissions to that IAM role.

In both cases, in addition to the permissions being permitted by the bucket policy in account B, when the IAM role is in account A, also the identity-based policies attached to the IAM role in account A must permit the same permissions to the bucket in account B. This is sometimes unofficially referred to as a "trust hug", meaning that both the source and destination AWS account must explicitly permit actions performed across accounts.

Since the other account isn't directly under your control, you'll have to ask the party managing the other account B either to 1) grant permissions in their S3 bucket policy to the IAM role in your account A, or 2) grant permissions to your bucket in account A to the IAM role in their account B.

EXPERT
answered 2 years ago
0

Thanks for the comment. Much appreciated. I've added the "assumeRole IAM role" of the destination account to the bucket policy of the source bucket. Actually, I've had no bucket policy for the source just to simplify the POC and to tighten it later in prod. Anyways. I've explicitly named the destination bucket in the code, and I get the following errors in CloudWatch for the invocation, but no idea what to check next. I'd appreciate advising a solution.

cf42ba01-7579-4af8-b30b-199e083676ab	There was an error copying the file to the destination bucket

Error Message: An error occurred (403) when calling the HeadObject operation: Forbidden
answered 2 years ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.