I have a versioned S3 bucket named protected-bucket and I want to programmatically delete objects or versions (sometimes just some versions). Bucket has the following policy attached that enforces the MFA to be present when Delete* actions are about to be executed:
{
"Sid": "RequireMFAForDelete",
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": "s3:Delete*",
"Resource": "arn:aws:s3:::protected-bucket/*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
I also tried to use the "Condition": { "Null": { "aws:MultiFactorAuthAge": true }} in bucket policy, as suggested on the https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html#example-bucket-policies-use-case-7 page. Got the same problem from below...
Here is a minimal Python3 code that is supposed to delete object version in the bucket I mentioned above:
#!/usr/bin/env python3
import boto3
from datetime import datetime
mfa_totp = input("Enter the MFA code: ")
session_name='my-test-session-' + str(int(datetime.utcnow().timestamp()))
client=boto3.client('sts', 'us-east-1')
ar_res = client.assume_role(
RoleArn='arn:aws:iam::123456789102:role/test-role',
RoleSessionName=session_name,
DurationSeconds=900,
SerialNumber='arn:aws:iam::987654321098:mfa/my_user_name',
TokenCode=mfa_totp,
)
print(ar_res)
tmp_creds = ar_res["Credentials"]
s3_client = boto3.client("s3", "us-east-1",
aws_access_key_id=tmp_creds["AccessKeyId"],
aws_secret_access_key=tmp_creds["SecretAccessKey"],
aws_session_token=tmp_creds["SessionToken"])
s3_bucket = "protected-bucket"
s3_key = "test/test4.txt"
s3_version = "XYZXbHbi3lpCNlOM8peIim6gi.IZQJqM"
# If I put code here that lists objects in
if s3_version:
response = s3_client.delete_object(Bucket=s3_bucket,
Key=s3_key,
VersionId=s3_version)
else:
response = s3_client.delete_object(Bucket=s3_bucket,Key=s3_key)
print(response)
The error I am getting follows:
Traceback (most recent call last):
File "./del_test.py", line 37, in <module>
response = s3_client.delete_object(Bucket=s3_bucket,
File "/home/dejan/py/myproj/lib64/python3.8/site-packages/botocore/client.py", line 386, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/home/dejan/py/myproj/lib64/python3.8/site-packages/botocore/client.py", line 705, in _make_api_call
raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the DeleteObject operation: Access Denied
Few things to note:
It turned out I missed the key piece of information given in the https://docs.amazonaws.cn/en_us/IAM/latest/UserGuide/id_credentials_mfa_configure-api-require.html document:
The temporary credentials returned by AssumeRole do not include MFA information in the context, so you cannot check individual API operations for MFA. This is why you must use GetSessionToken to restrict access to resources protected by resource-based policies.
In short, if I just assume_role(), with MFA, like I did in the Python code presented in the question, the MFA data will not be passed down, so get_session_token() is a must... Following refactored code (made with help of my colleague @Chadwick) works as expected:
#!/usr/bin/env python3
import boto3
from datetime import datetime
mfa_serial = "arn:aws:iam::987654321098:mfa/my_user_name"
role_to_assume = "arn:aws:iam::123456789102:role/test-role"
mfa_totp = input("Enter the MFA code: ")
mfa_sts_client = boto3.client("sts", "us-east-1")
mfa_credentials = mfa_sts_client.get_session_token(
SerialNumber=mfa_serial,
TokenCode=mfa_totp,
)["Credentials"]
session_name='my-test-session-' + str(int(datetime.utcnow().timestamp()))
# We now create a client with credentials from the MFA enabled session we created above:
ar_sts_client=boto3.client("sts", "us-east-1",
aws_access_key_id=mfa_credentials["AccessKeyId"],
aws_secret_access_key=mfa_credentials["SecretAccessKey"],
aws_session_token=mfa_credentials["SessionToken"])
ar_res = ar_sts_client.assume_role(
RoleArn=role_to_assume,
RoleSessionName=session_name,
DurationSeconds=900
)
print(ar_res)
tmp_creds = ar_res["Credentials"]
s3_client = boto3.client("s3", "us-east-1",
aws_access_key_id=tmp_creds["AccessKeyId"],
aws_secret_access_key=tmp_creds["SecretAccessKey"],
aws_session_token=tmp_creds["SessionToken"])
s3_bucket = "protected-bucket"
s3_key = "test/test4.txt"
s3_version = "YYFMqnLaVEosoZ1Zk3Xy8dVbNGQVEF35"
# s3_version = None
if s3_version:
response = s3_client.delete_object(Bucket=s3_bucket,
Key=s3_key,
VersionId=s3_version)
else:
response = s3_client.delete_object(Bucket=s3_bucket,Key=s3_key)
print(response)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With