Enforce tagging restrictions for S3 object (#4417)

This commit is contained in:
Shreesha Addala 2021-10-15 15:10:28 -04:00 committed by GitHub
parent d72c6b7baa
commit c62bd5ca41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 78 additions and 27 deletions

View File

@ -73,7 +73,7 @@ class BucketAlreadyExists(BucketError):
"select a different name and try again" "select a different name and try again"
), ),
*args, *args,
**kwargs **kwargs,
) )
@ -139,7 +139,7 @@ class InvalidPartOrder(S3ClientError):
"list must be specified in order by part number." "list must be specified in order by part number."
), ),
*args, *args,
**kwargs **kwargs,
) )
@ -155,7 +155,7 @@ class InvalidPart(S3ClientError):
"entity tag might not have matched the part's entity tag." "entity tag might not have matched the part's entity tag."
), ),
*args, *args,
**kwargs **kwargs,
) )
@ -167,7 +167,7 @@ class EntityTooSmall(S3ClientError):
"EntityTooSmall", "EntityTooSmall",
"Your proposed upload is smaller than the minimum allowed object size.", "Your proposed upload is smaller than the minimum allowed object size.",
*args, *args,
**kwargs **kwargs,
) )
@ -181,7 +181,7 @@ class InvalidRequest(S3ClientError):
method method
), ),
*args, *args,
**kwargs **kwargs,
) )
@ -193,7 +193,7 @@ class IllegalLocationConstraintException(S3ClientError):
"IllegalLocationConstraintException", "IllegalLocationConstraintException",
"The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.", "The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.",
*args, *args,
**kwargs **kwargs,
) )
@ -205,7 +205,7 @@ class MalformedXML(S3ClientError):
"MalformedXML", "MalformedXML",
"The XML you provided was not well-formed or did not validate against our published schema", "The XML you provided was not well-formed or did not validate against our published schema",
*args, *args,
**kwargs **kwargs,
) )
@ -217,7 +217,7 @@ class MalformedACLError(S3ClientError):
"MalformedACLError", "MalformedACLError",
"The XML you provided was not well-formed or did not validate against our published schema", "The XML you provided was not well-formed or did not validate against our published schema",
*args, *args,
**kwargs **kwargs,
) )
@ -266,7 +266,7 @@ class InvalidNotificationDestination(S3ClientError):
"InvalidArgument", "InvalidArgument",
"The notification destination service region is not valid for the bucket location constraint", "The notification destination service region is not valid for the bucket location constraint",
*args, *args,
**kwargs **kwargs,
) )
@ -278,7 +278,7 @@ class InvalidNotificationEvent(S3ClientError):
"InvalidArgument", "InvalidArgument",
"The event is not supported for notifications", "The event is not supported for notifications",
*args, *args,
**kwargs **kwargs,
) )
@ -290,7 +290,7 @@ class InvalidStorageClass(S3ClientError):
"InvalidStorageClass", "InvalidStorageClass",
"The storage class you specified is not valid", "The storage class you specified is not valid",
*args, *args,
**kwargs **kwargs,
) )
@ -311,7 +311,7 @@ class DuplicateTagKeys(S3ClientError):
"InvalidTag", "InvalidTag",
"Cannot provide multiple Tags with the same key", "Cannot provide multiple Tags with the same key",
*args, *args,
**kwargs **kwargs,
) )
@ -341,7 +341,7 @@ class S3InvalidTokenError(S3ClientError):
"InvalidToken", "InvalidToken",
"The provided token is malformed or otherwise invalid.", "The provided token is malformed or otherwise invalid.",
*args, *args,
**kwargs **kwargs,
) )
@ -353,7 +353,7 @@ class BucketInvalidTokenError(BucketError):
"InvalidToken", "InvalidToken",
"The provided token is malformed or otherwise invalid.", "The provided token is malformed or otherwise invalid.",
*args, *args,
**kwargs **kwargs,
) )
@ -365,7 +365,7 @@ class S3InvalidAccessKeyIdError(S3ClientError):
"InvalidAccessKeyId", "InvalidAccessKeyId",
"The AWS Access Key Id you provided does not exist in our records.", "The AWS Access Key Id you provided does not exist in our records.",
*args, *args,
**kwargs **kwargs,
) )
@ -377,7 +377,7 @@ class BucketInvalidAccessKeyIdError(S3ClientError):
"InvalidAccessKeyId", "InvalidAccessKeyId",
"The AWS Access Key Id you provided does not exist in our records.", "The AWS Access Key Id you provided does not exist in our records.",
*args, *args,
**kwargs **kwargs,
) )
@ -389,7 +389,7 @@ class S3SignatureDoesNotMatchError(S3ClientError):
"SignatureDoesNotMatch", "SignatureDoesNotMatch",
"The request signature we calculated does not match the signature you provided. Check your key and signing method.", "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
*args, *args,
**kwargs **kwargs,
) )
@ -401,7 +401,7 @@ class BucketSignatureDoesNotMatchError(S3ClientError):
"SignatureDoesNotMatch", "SignatureDoesNotMatch",
"The request signature we calculated does not match the signature you provided. Check your key and signing method.", "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
*args, *args,
**kwargs **kwargs,
) )
@ -413,7 +413,7 @@ class NoSuchPublicAccessBlockConfiguration(S3ClientError):
"NoSuchPublicAccessBlockConfiguration", "NoSuchPublicAccessBlockConfiguration",
"The public access block configuration was not found", "The public access block configuration was not found",
*args, *args,
**kwargs **kwargs,
) )
@ -425,7 +425,7 @@ class InvalidPublicAccessBlockConfiguration(S3ClientError):
"InvalidRequest", "InvalidRequest",
"Must specify at least one configuration.", "Must specify at least one configuration.",
*args, *args,
**kwargs **kwargs,
) )
@ -458,7 +458,7 @@ class NoSuchUpload(S3ClientError):
"NoSuchUpload", "NoSuchUpload",
"The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.", "The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
*args, *args,
**kwargs **kwargs,
) )
@ -472,7 +472,7 @@ class PreconditionFailed(S3ClientError):
"PreconditionFailed", "PreconditionFailed",
"At least one of the pre-conditions you specified did not hold", "At least one of the pre-conditions you specified did not hold",
condition=failed_condition, condition=failed_condition,
**kwargs **kwargs,
) )
@ -487,7 +487,7 @@ class InvalidRange(S3ClientError):
"The requested range is not satisfiable", "The requested range is not satisfiable",
range_requested=range_requested, range_requested=range_requested,
actual_size=actual_size, actual_size=actual_size,
**kwargs **kwargs,
) )
@ -499,7 +499,7 @@ class InvalidContinuationToken(S3ClientError):
"InvalidArgument", "InvalidArgument",
"The continuation token provided is incorrect", "The continuation token provided is incorrect",
*args, *args,
**kwargs **kwargs,
) )
@ -556,5 +556,14 @@ class InvalidFilterRuleName(InvalidArgumentError):
"FilterRule.Name", "FilterRule.Name",
value, value,
*args, *args,
**kwargs **kwargs,
)
class InvalidTagError(S3ClientError):
code = 400
def __init__(self, value, *args, **kwargs):
super(InvalidTagError, self).__init__(
"InvalidTag", value, *args, **kwargs,
) )

View File

@ -26,7 +26,7 @@ from moto.core.utils import (
) )
from moto.cloudwatch.models import MetricDatum from moto.cloudwatch.models import MetricDatum
from moto.utilities.tagging_service import TaggingService from moto.utilities.tagging_service import TaggingService
from .exceptions import ( from moto.s3.exceptions import (
AccessDeniedByLock, AccessDeniedByLock,
BucketAlreadyExists, BucketAlreadyExists,
BucketNeedsToBeNew, BucketNeedsToBeNew,
@ -45,6 +45,7 @@ from .exceptions import (
InvalidPublicAccessBlockConfiguration, InvalidPublicAccessBlockConfiguration,
WrongPublicAccessBlockAccountIdError, WrongPublicAccessBlockAccountIdError,
NoSuchUpload, NoSuchUpload,
InvalidTagError,
) )
from .cloud_formation import cfn_to_api_encryption, is_replacement_update from .cloud_formation import cfn_to_api_encryption, is_replacement_update
from .utils import clean_key_name, _VersionedKeyStore, undo_clean_key_name from .utils import clean_key_name, _VersionedKeyStore, undo_clean_key_name
@ -1633,9 +1634,13 @@ class S3Backend(BaseBackend):
def set_key_tags(self, key, tags, key_name=None): def set_key_tags(self, key, tags, key_name=None):
if key is None: if key is None:
raise MissingKey(key_name) raise MissingKey(key_name)
boto_tags_dict = self.tagger.convert_dict_to_tags_input(tags)
errmsg = self.tagger.validate_tags(boto_tags_dict)
if errmsg:
raise InvalidTagError(errmsg)
self.tagger.delete_all_tags_for_resource(key.arn) self.tagger.delete_all_tags_for_resource(key.arn)
self.tagger.tag_resource( self.tagger.tag_resource(
key.arn, [{"Key": k, "Value": v} for (k, v) in tags.items()], key.arn, boto_tags_dict,
) )
return key return key

View File

@ -156,3 +156,8 @@ class TaggingService:
if errors if errors
else "" else ""
) )
@staticmethod
def convert_dict_to_tags_input(tags):
""" Given a dictionary, return generic boto params for tags """
return [{"Key": k, "Value": v} for (k, v) in tags.items()]

View File

@ -34,6 +34,7 @@ import sure # noqa
from moto import settings, mock_s3, mock_s3_deprecated, mock_config from moto import settings, mock_s3, mock_s3_deprecated, mock_config
import moto.s3.models as s3model import moto.s3.models as s3model
from moto.s3.exceptions import InvalidTagError
from moto.core.exceptions import InvalidNextTokenException from moto.core.exceptions import InvalidNextTokenException
from moto.settings import get_s3_default_key_buffer_size, S3_UPLOAD_PART_MIN_SIZE from moto.settings import get_s3_default_key_buffer_size, S3_UPLOAD_PART_MIN_SIZE
from uuid import uuid4 from uuid import uuid4
@ -3412,6 +3413,19 @@ def test_boto3_copy_object_with_replacement_tagging():
Bucket="mybucket", Key="original", Body=b"test", Tagging="tag=old" Bucket="mybucket", Key="original", Body=b"test", Tagging="tag=old"
) )
# using system tags will fail
with pytest.raises(ClientError) as err:
client.copy_object(
CopySource={"Bucket": "mybucket", "Key": "original"},
Bucket="mybucket",
Key="copy1",
TaggingDirective="REPLACE",
Tagging="aws:tag=invalid_key",
)
e = err.value
e.response["Error"]["Code"].should.equal("InvalidTag")
client.copy_object( client.copy_object(
CopySource={"Bucket": "mybucket", "Key": "original"}, CopySource={"Bucket": "mybucket", "Key": "original"},
Bucket="mybucket", Bucket="mybucket",
@ -3937,6 +3951,13 @@ def test_boto3_put_object_with_tagging():
key = "key-with-tags" key = "key-with-tags"
s3.create_bucket(Bucket=bucket_name) s3.create_bucket(Bucket=bucket_name)
# using system tags will fail
with pytest.raises(ClientError) as err:
s3.put_object(Bucket=bucket_name, Key=key, Body="test", Tagging="aws:foo=bar")
e = err.value
e.response["Error"]["Code"].should.equal("InvalidTag")
s3.put_object(Bucket=bucket_name, Key=key, Body="test", Tagging="foo=bar") s3.put_object(Bucket=bucket_name, Key=key, Body="test", Tagging="foo=bar")
s3.get_object_tagging(Bucket=bucket_name, Key=key)["TagSet"].should.contain( s3.get_object_tagging(Bucket=bucket_name, Key=key)["TagSet"].should.contain(
@ -4666,6 +4687,17 @@ def test_boto3_put_object_tagging():
s3.put_object(Bucket=bucket_name, Key=key, Body="test") s3.put_object(Bucket=bucket_name, Key=key, Body="test")
# using system tags will fail
with pytest.raises(ClientError) as err:
s3.put_object_tagging(
Bucket=bucket_name,
Key=key,
Tagging={"TagSet": [{"Key": "aws:item1", "Value": "foo"},]},
)
e = err.value
e.response["Error"]["Code"].should.equal("InvalidTag")
resp = s3.put_object_tagging( resp = s3.put_object_tagging(
Bucket=bucket_name, Bucket=bucket_name,
Key=key, Key=key,