I have a lambda that triggers off an S3 bucket upload (it basically converts a PDF to a dataframe and writes it to a different s3 bucket). Both of these belong to AWS account A. I would like to allow cross-account s3 access to trigger this lambda from another IAM user from account B (Administrator
), however I am having issues with the GetObject
operation. Here is how my lambda in account A looks:
LOGGER = logging.getLogger(__name__)
logging.basicConfig(level=logging.ERROR)
logging.getLogger(__name__).setLevel(logging.DEBUG)
session = boto3.Session(
aws_access_key_id="XXXX",
aws_secret_access_key="XXXX",
)
s3 = session.resource('s3')
dest_bucket = 'bucket-output'
csv_buffer = StringIO()
def lambda_handler(event,context):
source_bucket = event['Records'][0]['s3']['bucket']['name']
pdf_name = event['Records'][0]['s3']['object']['key']
LOGGER.info('Reading {} from {}'.format(pdf_name, source_bucket))
pdf_obj = s3.Object(source_bucket,pdf_name)
fs = pdf_obj.get()['Body'].read() #### code is failing here
df = convert_bytes_to_df(BytesIO(fs))
df.to_csv(csv_buffer,index=False)
s3.Object(dest_bucket,str(pdf_name.split('.')[0])+".csv").put(Body=csv_buffer.getvalue())
LOGGER.info('Successfully converted {} from {} to {}'.format(pdf_name,source_bucket,dest_bucket))
The lambda is failing with this error:
ClientError: An error occurred (AccessDenied) when calling the GetObject operation: Access Denied
I’m aware it’s bad practice to have keys in the lambda file but I can’t change things at the moment.
The process works fine if I am uploading to the S3 bucket from within an IAM User in account A itself, but when I expose the S3 buckets to an IAM user from a separate account, the issues above start happening. This is the S3 bucket policy (terraform) allowing cross-account access to an IAM user from account B:
resource "aws_s3_bucket_policy" "cross_account_input_access" {
bucket = aws_s3_bucket.statements_input.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::XXXXXXXXX:user/Administrator"
},
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::capsphere-input"
]
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::XXXXXXXXX:user/Administrator"
},
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::bucket-name",
"arn:aws:s3:::bucket-name/*"
]
}
]
}
And here is the policy attached to an IAM user from another AWS account B which enables Administrator
from account B to write a pdf to account A’s s3 bucket programmatically:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::bucket-name"
]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::bucket-name",
"arn:aws:s3:::bucket-name/*"
]
}
]
}
I write the file to the bucket from Administrator
using aws cli
like this:
aws s3 cp filename.pdf s3://bucket-name
I can’t figure out what else needs to change.
3
Answers
What if you add Region information to the session:
It appears that your situation is:
Account A contains:
You want to allow the
Administrator
IAM User in Account B to upload a file to the source bucket in Account A. This user should be able to retrieve the output from the destination bucket in Account A.The following would achieve these goals:
GetObject
from the source bucket andPutObject
to the destination bucket. There should be no need to reference any credentials within the Lambda function itself, since any necessary permissions will be provided via this IAM Role. The policy would be:Administrator
user in Account B toPutObject
into the bucket:Administrator
user in Account B toGetObject
from the bucket and, I presume, list the bucket and delete objects that have been processed:Administrator
IAM User in Account B to let them access the source and destination buckets. This policy is not required if they already have a lot of S3 permissions, such ass3:*
, since it would work on any buckets including buckets in different AWS Accounts. This policy would go on the IAM User:Ah! I have it!
While your Lambda function has permission to access the object, the fact that the object was uploaded using credentials from another AWS Account means that the object is still ‘owned’ by the other account.
There are two ways to fix this:
Turn off ACLs on the receiving bucket
From Disabling ACLs for all new buckets and enforcing Object Ownership – Amazon Simple Storage Service:
Set ownership when uploading
When uploading the object when using credentials from another, you can specify
ACL=bucket-owner-full-control
(depending on how you perform the upload). This will grant ownership to the AWS Account that owns the receiving bucket.