2023-08-07 16:48:48 +00:00
|
|
|
import datetime
|
2021-08-17 05:16:59 +00:00
|
|
|
import time
|
2023-08-07 16:48:48 +00:00
|
|
|
|
2021-08-17 05:16:59 +00:00
|
|
|
import boto3
|
2023-08-07 16:48:48 +00:00
|
|
|
import pytest
|
2023-11-30 15:55:51 +00:00
|
|
|
from botocore.client import ClientError
|
|
|
|
from botocore.config import Config
|
2023-08-07 16:48:48 +00:00
|
|
|
|
|
|
|
from moto import mock_s3
|
2023-09-11 22:23:44 +00:00
|
|
|
from moto.core.utils import utcnow
|
2021-08-17 05:16:59 +00:00
|
|
|
from moto.s3.responses import DEFAULT_REGION_NAME
|
|
|
|
|
|
|
|
|
|
|
|
@mock_s3
|
|
|
|
def test_locked_object():
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client = boto3.client("s3", config=Config(region_name=DEFAULT_REGION_NAME))
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
bucket_name = "locked-bucket-test"
|
|
|
|
key_name = "file.txt"
|
|
|
|
seconds_lock = 2
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.create_bucket(Bucket=bucket_name, ObjectLockEnabledForBucket=True)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-09-11 22:23:44 +00:00
|
|
|
until = utcnow() + datetime.timedelta(0, seconds_lock)
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object(
|
2021-08-17 05:16:59 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Body=b"test",
|
|
|
|
Key=key_name,
|
|
|
|
ObjectLockMode="COMPLIANCE",
|
|
|
|
ObjectLockRetainUntilDate=until,
|
|
|
|
)
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
versions_response = s3_client.list_object_versions(Bucket=bucket_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
version_id = versions_response["Versions"][0]["VersionId"]
|
|
|
|
|
|
|
|
deleted = False
|
|
|
|
try:
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id)
|
2021-08-17 05:16:59 +00:00
|
|
|
deleted = True
|
2023-08-07 16:48:48 +00:00
|
|
|
except ClientError as exc:
|
|
|
|
assert exc.response["Error"]["Code"] == "AccessDenied"
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
assert deleted is False
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
# cleaning
|
|
|
|
time.sleep(seconds_lock)
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id)
|
|
|
|
s3_client.delete_bucket(Bucket=bucket_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
@mock_s3
|
|
|
|
def test_fail_locked_object():
|
|
|
|
bucket_name = "locked-bucket2"
|
|
|
|
key_name = "file.txt"
|
|
|
|
seconds_lock = 2
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client = boto3.client("s3", config=Config(region_name=DEFAULT_REGION_NAME))
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.create_bucket(Bucket=bucket_name, ObjectLockEnabledForBucket=False)
|
2023-09-11 22:23:44 +00:00
|
|
|
until = utcnow() + datetime.timedelta(0, seconds_lock)
|
2021-08-17 05:16:59 +00:00
|
|
|
failed = False
|
|
|
|
try:
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object(
|
2021-08-17 05:16:59 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Body=b"test",
|
|
|
|
Key=key_name,
|
|
|
|
ObjectLockMode="COMPLIANCE",
|
|
|
|
ObjectLockRetainUntilDate=until,
|
|
|
|
)
|
2023-08-07 16:48:48 +00:00
|
|
|
except ClientError as exc:
|
|
|
|
assert exc.response["Error"]["Code"] == "InvalidRequest"
|
2021-08-17 05:16:59 +00:00
|
|
|
failed = True
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
assert failed is True
|
|
|
|
s3_client.delete_bucket(Bucket=bucket_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
@mock_s3
|
|
|
|
def test_put_object_lock():
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client = boto3.client("s3", config=Config(region_name=DEFAULT_REGION_NAME))
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
bucket_name = "put-lock-bucket-test"
|
|
|
|
key_name = "file.txt"
|
|
|
|
seconds_lock = 2
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.create_bucket(Bucket=bucket_name, ObjectLockEnabledForBucket=True)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object(Bucket=bucket_name, Body=b"test", Key=key_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
versions_response = s3_client.list_object_versions(Bucket=bucket_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
version_id = versions_response["Versions"][0]["VersionId"]
|
2023-09-11 22:23:44 +00:00
|
|
|
until = utcnow() + datetime.timedelta(0, seconds_lock)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object_retention(
|
2021-08-17 05:16:59 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Key=key_name,
|
|
|
|
VersionId=version_id,
|
|
|
|
Retention={"Mode": "COMPLIANCE", "RetainUntilDate": until},
|
|
|
|
)
|
|
|
|
|
|
|
|
deleted = False
|
|
|
|
try:
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id)
|
2021-08-17 05:16:59 +00:00
|
|
|
deleted = True
|
2023-08-07 16:48:48 +00:00
|
|
|
except ClientError as exc:
|
|
|
|
assert exc.response["Error"]["Code"] == "AccessDenied"
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
assert deleted is False
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
# cleaning
|
|
|
|
time.sleep(seconds_lock)
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id)
|
|
|
|
s3_client.delete_bucket(Bucket=bucket_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
@mock_s3
|
|
|
|
def test_put_object_legal_hold():
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client = boto3.client("s3", config=Config(region_name=DEFAULT_REGION_NAME))
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
bucket_name = "put-legal-bucket"
|
|
|
|
key_name = "file.txt"
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.create_bucket(Bucket=bucket_name, ObjectLockEnabledForBucket=True)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object(Bucket=bucket_name, Body=b"test", Key=key_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
versions_response = s3_client.list_object_versions(Bucket=bucket_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
version_id = versions_response["Versions"][0]["VersionId"]
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object_legal_hold(
|
2021-08-17 05:16:59 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Key=key_name,
|
|
|
|
VersionId=version_id,
|
|
|
|
LegalHold={"Status": "ON"},
|
|
|
|
)
|
|
|
|
|
|
|
|
deleted = False
|
|
|
|
try:
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id)
|
2021-08-17 05:16:59 +00:00
|
|
|
deleted = True
|
2023-08-07 16:48:48 +00:00
|
|
|
except ClientError as exc:
|
|
|
|
assert exc.response["Error"]["Code"] == "AccessDenied"
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
assert deleted is False
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
# cleaning
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object_legal_hold(
|
2021-08-17 05:16:59 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Key=key_name,
|
|
|
|
VersionId=version_id,
|
|
|
|
LegalHold={"Status": "OFF"},
|
|
|
|
)
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id)
|
|
|
|
s3_client.delete_bucket(Bucket=bucket_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
@mock_s3
|
|
|
|
def test_put_default_lock():
|
|
|
|
# do not run this test in aws, it will block the deletion for a whole day
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client = boto3.client("s3", config=Config(region_name=DEFAULT_REGION_NAME))
|
2021-08-17 05:16:59 +00:00
|
|
|
bucket_name = "put-default-lock-bucket"
|
|
|
|
key_name = "file.txt"
|
|
|
|
|
|
|
|
days = 1
|
|
|
|
mode = "COMPLIANCE"
|
|
|
|
enabled = "Enabled"
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.create_bucket(Bucket=bucket_name, ObjectLockEnabledForBucket=True)
|
|
|
|
s3_client.put_object_lock_configuration(
|
2021-08-17 05:16:59 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
ObjectLockConfiguration={
|
|
|
|
"ObjectLockEnabled": enabled,
|
|
|
|
"Rule": {"DefaultRetention": {"Mode": mode, "Days": days}},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object(Bucket=bucket_name, Body=b"test", Key=key_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
|
|
|
|
deleted = False
|
2023-08-07 16:48:48 +00:00
|
|
|
versions_response = s3_client.list_object_versions(Bucket=bucket_name)
|
2021-08-17 05:16:59 +00:00
|
|
|
version_id = versions_response["Versions"][0]["VersionId"]
|
|
|
|
|
|
|
|
try:
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id)
|
2021-08-17 05:16:59 +00:00
|
|
|
deleted = True
|
2023-08-07 16:48:48 +00:00
|
|
|
except ClientError as exc:
|
|
|
|
assert exc.response["Error"]["Code"] == "AccessDenied"
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
assert deleted is False
|
2021-08-17 05:16:59 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
response = s3_client.get_object_lock_configuration(Bucket=bucket_name)
|
|
|
|
assert response["ObjectLockConfiguration"]["ObjectLockEnabled"] == enabled
|
|
|
|
assert (
|
|
|
|
response["ObjectLockConfiguration"]["Rule"]["DefaultRetention"]["Mode"] == mode
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
response["ObjectLockConfiguration"]["Rule"]["DefaultRetention"]["Days"] == days
|
|
|
|
)
|
2023-05-08 20:41:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
@mock_s3
|
|
|
|
def test_put_object_legal_hold_with_versions():
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client = boto3.client("s3", config=Config(region_name=DEFAULT_REGION_NAME))
|
2023-05-08 20:41:22 +00:00
|
|
|
|
|
|
|
bucket_name = "put-legal-bucket"
|
|
|
|
key_name = "file.txt"
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.create_bucket(Bucket=bucket_name, ObjectLockEnabledForBucket=True)
|
2023-05-08 20:41:22 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
put_obj_1 = s3_client.put_object(Bucket=bucket_name, Body=b"test", Key=key_name)
|
2023-05-08 20:41:22 +00:00
|
|
|
version_id_1 = put_obj_1["VersionId"]
|
|
|
|
# lock the object with the version, locking the version 1
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object_legal_hold(
|
2023-05-08 20:41:22 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Key=key_name,
|
|
|
|
VersionId=version_id_1,
|
|
|
|
LegalHold={"Status": "ON"},
|
|
|
|
)
|
|
|
|
|
|
|
|
# put an object on the same key, effectively creating a version 2 of the object
|
2023-08-07 16:48:48 +00:00
|
|
|
put_obj_2 = s3_client.put_object(Bucket=bucket_name, Body=b"test", Key=key_name)
|
2023-05-08 20:41:22 +00:00
|
|
|
version_id_2 = put_obj_2["VersionId"]
|
|
|
|
# also lock the version 2 of the object
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object_legal_hold(
|
2023-05-08 20:41:22 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Key=key_name,
|
|
|
|
VersionId=version_id_2,
|
|
|
|
LegalHold={"Status": "ON"},
|
|
|
|
)
|
|
|
|
|
|
|
|
# assert that the version 1 is locked
|
2023-08-07 16:48:48 +00:00
|
|
|
head_obj_1 = s3_client.head_object(
|
2023-05-08 20:41:22 +00:00
|
|
|
Bucket=bucket_name, Key=key_name, VersionId=version_id_1
|
|
|
|
)
|
|
|
|
assert head_obj_1["ObjectLockLegalHoldStatus"] == "ON"
|
|
|
|
|
|
|
|
# remove the lock from the version 1 of the object
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object_legal_hold(
|
2023-05-08 20:41:22 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Key=key_name,
|
|
|
|
VersionId=version_id_1,
|
|
|
|
LegalHold={"Status": "OFF"},
|
|
|
|
)
|
|
|
|
|
|
|
|
# assert that you can now delete the version 1 of the object
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id_1)
|
2023-05-08 20:41:22 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
with pytest.raises(ClientError) as exc:
|
|
|
|
s3_client.head_object(Bucket=bucket_name, Key=key_name, VersionId=version_id_1)
|
|
|
|
assert exc.value.response["Error"]["Code"] == "404"
|
2023-05-08 20:41:22 +00:00
|
|
|
|
|
|
|
# cleaning
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object_legal_hold(
|
2023-05-08 20:41:22 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Key=key_name,
|
|
|
|
VersionId=version_id_2,
|
|
|
|
LegalHold={"Status": "OFF"},
|
|
|
|
)
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id_2)
|
|
|
|
s3_client.delete_bucket(Bucket=bucket_name)
|
2023-05-08 20:41:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
@mock_s3
|
|
|
|
def test_put_object_lock_with_versions():
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client = boto3.client("s3", config=Config(region_name=DEFAULT_REGION_NAME))
|
2023-05-08 20:41:22 +00:00
|
|
|
|
|
|
|
bucket_name = "put-lock-bucket-test"
|
|
|
|
key_name = "file.txt"
|
|
|
|
seconds_lock = 2
|
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.create_bucket(Bucket=bucket_name, ObjectLockEnabledForBucket=True)
|
2023-05-08 20:41:22 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
put_obj_1 = s3_client.put_object(Bucket=bucket_name, Body=b"test", Key=key_name)
|
2023-05-08 20:41:22 +00:00
|
|
|
version_id_1 = put_obj_1["VersionId"]
|
2023-08-07 16:48:48 +00:00
|
|
|
put_obj_2 = s3_client.put_object(Bucket=bucket_name, Body=b"test", Key=key_name)
|
2023-05-08 20:41:22 +00:00
|
|
|
version_id_2 = put_obj_2["VersionId"]
|
|
|
|
|
2023-09-11 22:23:44 +00:00
|
|
|
until = utcnow() + datetime.timedelta(seconds=seconds_lock)
|
2023-05-08 20:41:22 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.put_object_retention(
|
2023-05-08 20:41:22 +00:00
|
|
|
Bucket=bucket_name,
|
|
|
|
Key=key_name,
|
|
|
|
VersionId=version_id_1,
|
|
|
|
Retention={"Mode": "COMPLIANCE", "RetainUntilDate": until},
|
|
|
|
)
|
|
|
|
|
|
|
|
# assert that you can delete the locked version 1 of the object
|
|
|
|
deleted = False
|
|
|
|
try:
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(
|
|
|
|
Bucket=bucket_name, Key=key_name, VersionId=version_id_1
|
|
|
|
)
|
2023-05-08 20:41:22 +00:00
|
|
|
deleted = True
|
2023-08-07 16:48:48 +00:00
|
|
|
except ClientError as exc:
|
|
|
|
assert exc.response["Error"]["Code"] == "AccessDenied"
|
2023-05-08 20:41:22 +00:00
|
|
|
|
2023-08-07 16:48:48 +00:00
|
|
|
assert deleted is False
|
2023-05-08 20:41:22 +00:00
|
|
|
|
|
|
|
# assert that you can delete the version 2 of the object, not concerned by the lock
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id_2)
|
|
|
|
with pytest.raises(ClientError) as exc:
|
|
|
|
s3_client.head_object(Bucket=bucket_name, Key=key_name, VersionId=version_id_2)
|
|
|
|
assert exc.value.response["Error"]["Code"] == "404"
|
2023-05-08 20:41:22 +00:00
|
|
|
|
|
|
|
# cleaning
|
|
|
|
time.sleep(seconds_lock)
|
2023-08-07 16:48:48 +00:00
|
|
|
s3_client.delete_object(Bucket=bucket_name, Key=key_name, VersionId=version_id_1)
|
|
|
|
s3_client.delete_bucket(Bucket=bucket_name)
|