Implemented S3 Public Access Block
This commit is contained in:
parent
4d5bf1c5c6
commit
84ccdbd1cd
7 changed files with 353 additions and 33 deletions
|
|
@ -304,3 +304,27 @@ def path_url(url):
|
|||
if parsed_url.query:
|
||||
path = path + "?" + parsed_url.query
|
||||
return path
|
||||
|
||||
|
||||
def py2_strip_unicode_keys(blob):
|
||||
"""For Python 2 Only -- this will convert unicode keys in nested Dicts, Lists, and Sets to standard strings."""
|
||||
if type(blob) == unicode: # noqa
|
||||
return str(blob)
|
||||
|
||||
elif type(blob) == dict:
|
||||
for key in list(blob.keys()):
|
||||
value = blob.pop(key)
|
||||
blob[str(key)] = py2_strip_unicode_keys(value)
|
||||
|
||||
elif type(blob) == list:
|
||||
for i in range(0, len(blob)):
|
||||
blob[i] = py2_strip_unicode_keys(blob[i])
|
||||
|
||||
elif type(blob) == set:
|
||||
new_set = set()
|
||||
for value in blob:
|
||||
new_set.add(py2_strip_unicode_keys(value))
|
||||
|
||||
blob = new_set
|
||||
|
||||
return blob
|
||||
|
|
|
|||
|
|
@ -323,3 +323,27 @@ class BucketSignatureDoesNotMatchError(S3ClientError):
|
|||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
class NoSuchPublicAccessBlockConfiguration(S3ClientError):
|
||||
code = 404
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NoSuchPublicAccessBlockConfiguration, self).__init__(
|
||||
"NoSuchPublicAccessBlockConfiguration",
|
||||
"The public access block configuration was not found",
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
class InvalidPublicAccessBlockConfiguration(S3ClientError):
|
||||
code = 400
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(InvalidPublicAccessBlockConfiguration, self).__init__(
|
||||
"InvalidRequest",
|
||||
"Must specify at least one configuration.",
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ from .exceptions import (
|
|||
InvalidTargetBucketForLogging,
|
||||
DuplicateTagKeys,
|
||||
CrossLocationLoggingProhibitted,
|
||||
NoSuchPublicAccessBlockConfiguration,
|
||||
InvalidPublicAccessBlockConfiguration,
|
||||
)
|
||||
from .utils import clean_key_name, _VersionedKeyStore
|
||||
|
||||
|
|
@ -659,11 +661,8 @@ class Notification(BaseModel):
|
|||
else:
|
||||
data["filter"] = None
|
||||
|
||||
data[
|
||||
"objectPrefixes"
|
||||
] = (
|
||||
[]
|
||||
) # Not sure why this is a thing since AWS just seems to return this as filters ¯\_(ツ)_/¯
|
||||
# Not sure why this is a thing since AWS just seems to return this as filters ¯\_(ツ)_/¯
|
||||
data["objectPrefixes"] = []
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -728,6 +727,38 @@ class NotificationConfiguration(BaseModel):
|
|||
return data
|
||||
|
||||
|
||||
def convert_str_to_bool(item):
|
||||
"""Converts a boolean string to a boolean value"""
|
||||
if isinstance(item, str):
|
||||
return item.lower() == "true"
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class PublicAccessBlock(BaseModel):
|
||||
def __init__(
|
||||
self,
|
||||
block_public_acls,
|
||||
ignore_public_acls,
|
||||
block_public_policy,
|
||||
restrict_public_buckets,
|
||||
):
|
||||
# The boto XML appears to expect these values to exist as lowercase strings...
|
||||
self.block_public_acls = block_public_acls or "false"
|
||||
self.ignore_public_acls = ignore_public_acls or "false"
|
||||
self.block_public_policy = block_public_policy or "false"
|
||||
self.restrict_public_buckets = restrict_public_buckets or "false"
|
||||
|
||||
def to_config_dict(self):
|
||||
# Need to make the string values booleans for Config:
|
||||
return {
|
||||
"blockPublicAcls": convert_str_to_bool(self.block_public_acls),
|
||||
"ignorePublicAcls": convert_str_to_bool(self.ignore_public_acls),
|
||||
"blockPublicPolicy": convert_str_to_bool(self.block_public_policy),
|
||||
"restrictPublicBuckets": convert_str_to_bool(self.restrict_public_buckets),
|
||||
}
|
||||
|
||||
|
||||
class FakeBucket(BaseModel):
|
||||
def __init__(self, name, region_name):
|
||||
self.name = name
|
||||
|
|
@ -746,6 +777,7 @@ class FakeBucket(BaseModel):
|
|||
self.accelerate_configuration = None
|
||||
self.payer = "BucketOwner"
|
||||
self.creation_date = datetime.datetime.utcnow()
|
||||
self.public_access_block = None
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
|
|
@ -1079,13 +1111,16 @@ class FakeBucket(BaseModel):
|
|||
}
|
||||
|
||||
# Make the supplementary configuration:
|
||||
# TODO: Implement Public Access Block Support
|
||||
|
||||
# This is a dobule-wrapped JSON for some reason...
|
||||
s_config = {
|
||||
"AccessControlList": json.dumps(json.dumps(self.acl.to_config_dict()))
|
||||
}
|
||||
|
||||
if self.public_access_block:
|
||||
s_config["PublicAccessBlockConfiguration"] = json.dumps(
|
||||
self.public_access_block.to_config_dict()
|
||||
)
|
||||
|
||||
# Tagging is special:
|
||||
if config_dict["tags"]:
|
||||
s_config["BucketTaggingConfiguration"] = json.dumps(
|
||||
|
|
@ -1221,6 +1256,14 @@ class S3Backend(BaseBackend):
|
|||
bucket = self.get_bucket(bucket_name)
|
||||
return bucket.website_configuration
|
||||
|
||||
def get_bucket_public_access_block(self, bucket_name):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
|
||||
if not bucket.public_access_block:
|
||||
raise NoSuchPublicAccessBlockConfiguration()
|
||||
|
||||
return bucket.public_access_block
|
||||
|
||||
def set_key(
|
||||
self, bucket_name, key_name, value, storage=None, etag=None, multipart=None
|
||||
):
|
||||
|
|
@ -1309,6 +1352,10 @@ class S3Backend(BaseBackend):
|
|||
bucket = self.get_bucket(bucket_name)
|
||||
bucket.delete_cors()
|
||||
|
||||
def delete_bucket_public_access_block(self, bucket_name):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
bucket.public_access_block = None
|
||||
|
||||
def put_bucket_notification_configuration(self, bucket_name, notification_config):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
bucket.set_notification_configuration(notification_config)
|
||||
|
|
@ -1324,6 +1371,19 @@ class S3Backend(BaseBackend):
|
|||
raise InvalidRequest("PutBucketAccelerateConfiguration")
|
||||
bucket.set_accelerate_configuration(accelerate_configuration)
|
||||
|
||||
def put_bucket_public_access_block(self, bucket_name, pub_block_config):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
|
||||
if not pub_block_config:
|
||||
raise InvalidPublicAccessBlockConfiguration()
|
||||
|
||||
bucket.public_access_block = PublicAccessBlock(
|
||||
pub_block_config.get("BlockPublicAcls"),
|
||||
pub_block_config.get("IgnorePublicAcls"),
|
||||
pub_block_config.get("BlockPublicPolicy"),
|
||||
pub_block_config.get("RestrictPublicBuckets"),
|
||||
)
|
||||
|
||||
def initiate_multipart(self, bucket_name, key_name, metadata):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
new_multipart = FakeMultipart(key_name, metadata)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from moto.core.utils import str_to_rfc_1123_datetime
|
||||
from moto.core.utils import str_to_rfc_1123_datetime, py2_strip_unicode_keys
|
||||
from six.moves.urllib.parse import parse_qs, urlparse, unquote
|
||||
|
||||
import xmltodict
|
||||
|
|
@ -70,6 +71,7 @@ ACTION_MAP = {
|
|||
"notification": "GetBucketNotification",
|
||||
"accelerate": "GetAccelerateConfiguration",
|
||||
"versions": "ListBucketVersions",
|
||||
"public_access_block": "GetPublicAccessBlock",
|
||||
"DEFAULT": "ListBucket",
|
||||
},
|
||||
"PUT": {
|
||||
|
|
@ -83,6 +85,7 @@ ACTION_MAP = {
|
|||
"cors": "PutBucketCORS",
|
||||
"notification": "PutBucketNotification",
|
||||
"accelerate": "PutAccelerateConfiguration",
|
||||
"public_access_block": "PutPublicAccessBlock",
|
||||
"DEFAULT": "CreateBucket",
|
||||
},
|
||||
"DELETE": {
|
||||
|
|
@ -90,6 +93,7 @@ ACTION_MAP = {
|
|||
"policy": "DeleteBucketPolicy",
|
||||
"tagging": "PutBucketTagging",
|
||||
"cors": "PutBucketCORS",
|
||||
"public_access_block": "DeletePublicAccessBlock",
|
||||
"DEFAULT": "DeleteBucket",
|
||||
},
|
||||
},
|
||||
|
|
@ -399,6 +403,12 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
|
|||
return 200, {}, template.render()
|
||||
template = self.response_template(S3_BUCKET_ACCELERATE)
|
||||
return template.render(bucket=bucket)
|
||||
elif "publicAccessBlock" in querystring:
|
||||
public_block_config = self.backend.get_bucket_public_access_block(
|
||||
bucket_name
|
||||
)
|
||||
template = self.response_template(S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION)
|
||||
return template.render(public_block_config=public_block_config)
|
||||
|
||||
elif "versions" in querystring:
|
||||
delimiter = querystring.get("delimiter", [None])[0]
|
||||
|
|
@ -651,6 +661,23 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
|
|||
except Exception as e:
|
||||
raise e
|
||||
|
||||
elif "publicAccessBlock" in querystring:
|
||||
parsed_xml = xmltodict.parse(body)
|
||||
parsed_xml["PublicAccessBlockConfiguration"].pop("@xmlns", None)
|
||||
|
||||
# If Python 2, fix the unicode strings:
|
||||
if sys.version_info[0] < 3:
|
||||
parsed_xml = {
|
||||
"PublicAccessBlockConfiguration": py2_strip_unicode_keys(
|
||||
dict(parsed_xml["PublicAccessBlockConfiguration"])
|
||||
)
|
||||
}
|
||||
|
||||
self.backend.put_bucket_public_access_block(
|
||||
bucket_name, parsed_xml["PublicAccessBlockConfiguration"]
|
||||
)
|
||||
return ""
|
||||
|
||||
else:
|
||||
if body:
|
||||
# us-east-1, the default AWS region behaves a bit differently
|
||||
|
|
@ -706,6 +733,9 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
|
|||
bucket = self.backend.get_bucket(bucket_name)
|
||||
bucket.delete_lifecycle()
|
||||
return 204, {}, ""
|
||||
elif "publicAccessBlock" in querystring:
|
||||
self.backend.delete_bucket_public_access_block(bucket_name)
|
||||
return 204, {}, ""
|
||||
|
||||
removed_bucket = self.backend.delete_bucket(bucket_name)
|
||||
|
||||
|
|
@ -2053,3 +2083,12 @@ S3_BUCKET_ACCELERATE = """
|
|||
S3_BUCKET_ACCELERATE_NOT_SET = """
|
||||
<AccelerateConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>
|
||||
"""
|
||||
|
||||
S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION = """
|
||||
<PublicAccessBlockConfiguration>
|
||||
<BlockPublicAcls>{{public_block_config.block_public_acls}}</BlockPublicAcls>
|
||||
<IgnorePublicAcls>{{public_block_config.ignore_public_acls}}</IgnorePublicAcls>
|
||||
<BlockPublicPolicy>{{public_block_config.block_public_policy}}</BlockPublicPolicy>
|
||||
<RestrictPublicBuckets>{{public_block_config.restrict_public_buckets}}</RestrictPublicBuckets>
|
||||
</PublicAccessBlockConfiguration>
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue