Separate S3Control methods from S3 (#4745)
This commit is contained in:
		
							parent
							
								
									9c8744ff64
								
							
						
					
					
						commit
						6610862a8f
					
				| @ -4318,6 +4318,69 @@ | |||||||
| - [ ] write_get_object_response | - [ ] write_get_object_response | ||||||
| </details> | </details> | ||||||
| 
 | 
 | ||||||
|  | ## s3control | ||||||
|  | <details> | ||||||
|  | <summary>5% implemented</summary> | ||||||
|  | 
 | ||||||
|  | - [ ] create_access_point | ||||||
|  | - [ ] create_access_point_for_object_lambda | ||||||
|  | - [ ] create_bucket | ||||||
|  | - [ ] create_job | ||||||
|  | - [ ] create_multi_region_access_point | ||||||
|  | - [ ] delete_access_point | ||||||
|  | - [ ] delete_access_point_for_object_lambda | ||||||
|  | - [ ] delete_access_point_policy | ||||||
|  | - [ ] delete_access_point_policy_for_object_lambda | ||||||
|  | - [ ] delete_bucket | ||||||
|  | - [ ] delete_bucket_lifecycle_configuration | ||||||
|  | - [ ] delete_bucket_policy | ||||||
|  | - [ ] delete_bucket_tagging | ||||||
|  | - [ ] delete_job_tagging | ||||||
|  | - [ ] delete_multi_region_access_point | ||||||
|  | - [X] delete_public_access_block | ||||||
|  | - [ ] delete_storage_lens_configuration | ||||||
|  | - [ ] delete_storage_lens_configuration_tagging | ||||||
|  | - [ ] describe_job | ||||||
|  | - [ ] describe_multi_region_access_point_operation | ||||||
|  | - [ ] get_access_point | ||||||
|  | - [ ] get_access_point_configuration_for_object_lambda | ||||||
|  | - [ ] get_access_point_for_object_lambda | ||||||
|  | - [ ] get_access_point_policy | ||||||
|  | - [ ] get_access_point_policy_for_object_lambda | ||||||
|  | - [ ] get_access_point_policy_status | ||||||
|  | - [ ] get_access_point_policy_status_for_object_lambda | ||||||
|  | - [ ] get_bucket | ||||||
|  | - [ ] get_bucket_lifecycle_configuration | ||||||
|  | - [ ] get_bucket_policy | ||||||
|  | - [ ] get_bucket_tagging | ||||||
|  | - [ ] get_job_tagging | ||||||
|  | - [ ] get_multi_region_access_point | ||||||
|  | - [ ] get_multi_region_access_point_policy | ||||||
|  | - [ ] get_multi_region_access_point_policy_status | ||||||
|  | - [X] get_public_access_block | ||||||
|  | - [ ] get_storage_lens_configuration | ||||||
|  | - [ ] get_storage_lens_configuration_tagging | ||||||
|  | - [ ] list_access_points | ||||||
|  | - [ ] list_access_points_for_object_lambda | ||||||
|  | - [ ] list_jobs | ||||||
|  | - [ ] list_multi_region_access_points | ||||||
|  | - [ ] list_regional_buckets | ||||||
|  | - [ ] list_storage_lens_configurations | ||||||
|  | - [ ] put_access_point_configuration_for_object_lambda | ||||||
|  | - [ ] put_access_point_policy | ||||||
|  | - [ ] put_access_point_policy_for_object_lambda | ||||||
|  | - [ ] put_bucket_lifecycle_configuration | ||||||
|  | - [ ] put_bucket_policy | ||||||
|  | - [ ] put_bucket_tagging | ||||||
|  | - [ ] put_job_tagging | ||||||
|  | - [ ] put_multi_region_access_point_policy | ||||||
|  | - [X] put_public_access_block | ||||||
|  | - [ ] put_storage_lens_configuration | ||||||
|  | - [ ] put_storage_lens_configuration_tagging | ||||||
|  | - [ ] update_job_priority | ||||||
|  | - [ ] update_job_status | ||||||
|  | </details> | ||||||
|  | 
 | ||||||
| ## sagemaker | ## sagemaker | ||||||
| <details> | <details> | ||||||
| <summary>15% implemented</summary> | <summary>15% implemented</summary> | ||||||
| @ -5343,7 +5406,6 @@ | |||||||
| - route53-recovery-readiness | - route53-recovery-readiness | ||||||
| - route53domains | - route53domains | ||||||
| - rum | - rum | ||||||
| - s3control |  | ||||||
| - s3outposts | - s3outposts | ||||||
| - sagemaker-a2i-runtime | - sagemaker-a2i-runtime | ||||||
| - sagemaker-edge | - sagemaker-edge | ||||||
|  | |||||||
							
								
								
									
										87
									
								
								docs/docs/services/s3control.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								docs/docs/services/s3control.rst
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | |||||||
|  | .. _implementedservice_s3control: | ||||||
|  | 
 | ||||||
|  | .. |start-h3| raw:: html | ||||||
|  | 
 | ||||||
|  |     <h3> | ||||||
|  | 
 | ||||||
|  | .. |end-h3| raw:: html | ||||||
|  | 
 | ||||||
|  |     </h3> | ||||||
|  | 
 | ||||||
|  | ========= | ||||||
|  | s3control | ||||||
|  | ========= | ||||||
|  | 
 | ||||||
|  | .. autoclass:: moto.s3control.models.S3ControlBackend | ||||||
|  | 
 | ||||||
|  | |start-h3| Example usage |end-h3| | ||||||
|  | 
 | ||||||
|  | .. sourcecode:: python | ||||||
|  | 
 | ||||||
|  |             @mock_s3control | ||||||
|  |             def test_s3control_behaviour: | ||||||
|  |                 boto3.client("s3control") | ||||||
|  |                 ... | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | |start-h3| Implemented features for this service |end-h3| | ||||||
|  | 
 | ||||||
|  | - [ ] create_access_point | ||||||
|  | - [ ] create_access_point_for_object_lambda | ||||||
|  | - [ ] create_bucket | ||||||
|  | - [ ] create_job | ||||||
|  | - [ ] create_multi_region_access_point | ||||||
|  | - [ ] delete_access_point | ||||||
|  | - [ ] delete_access_point_for_object_lambda | ||||||
|  | - [ ] delete_access_point_policy | ||||||
|  | - [ ] delete_access_point_policy_for_object_lambda | ||||||
|  | - [ ] delete_bucket | ||||||
|  | - [ ] delete_bucket_lifecycle_configuration | ||||||
|  | - [ ] delete_bucket_policy | ||||||
|  | - [ ] delete_bucket_tagging | ||||||
|  | - [ ] delete_job_tagging | ||||||
|  | - [ ] delete_multi_region_access_point | ||||||
|  | - [X] delete_public_access_block | ||||||
|  | - [ ] delete_storage_lens_configuration | ||||||
|  | - [ ] delete_storage_lens_configuration_tagging | ||||||
|  | - [ ] describe_job | ||||||
|  | - [ ] describe_multi_region_access_point_operation | ||||||
|  | - [ ] get_access_point | ||||||
|  | - [ ] get_access_point_configuration_for_object_lambda | ||||||
|  | - [ ] get_access_point_for_object_lambda | ||||||
|  | - [ ] get_access_point_policy | ||||||
|  | - [ ] get_access_point_policy_for_object_lambda | ||||||
|  | - [ ] get_access_point_policy_status | ||||||
|  | - [ ] get_access_point_policy_status_for_object_lambda | ||||||
|  | - [ ] get_bucket | ||||||
|  | - [ ] get_bucket_lifecycle_configuration | ||||||
|  | - [ ] get_bucket_policy | ||||||
|  | - [ ] get_bucket_tagging | ||||||
|  | - [ ] get_job_tagging | ||||||
|  | - [ ] get_multi_region_access_point | ||||||
|  | - [ ] get_multi_region_access_point_policy | ||||||
|  | - [ ] get_multi_region_access_point_policy_status | ||||||
|  | - [X] get_public_access_block | ||||||
|  | - [ ] get_storage_lens_configuration | ||||||
|  | - [ ] get_storage_lens_configuration_tagging | ||||||
|  | - [ ] list_access_points | ||||||
|  | - [ ] list_access_points_for_object_lambda | ||||||
|  | - [ ] list_jobs | ||||||
|  | - [ ] list_multi_region_access_points | ||||||
|  | - [ ] list_regional_buckets | ||||||
|  | - [ ] list_storage_lens_configurations | ||||||
|  | - [ ] put_access_point_configuration_for_object_lambda | ||||||
|  | - [ ] put_access_point_policy | ||||||
|  | - [ ] put_access_point_policy_for_object_lambda | ||||||
|  | - [ ] put_bucket_lifecycle_configuration | ||||||
|  | - [ ] put_bucket_policy | ||||||
|  | - [ ] put_bucket_tagging | ||||||
|  | - [ ] put_job_tagging | ||||||
|  | - [ ] put_multi_region_access_point_policy | ||||||
|  | - [X] put_public_access_block | ||||||
|  | - [ ] put_storage_lens_configuration | ||||||
|  | - [ ] put_storage_lens_configuration_tagging | ||||||
|  | - [ ] update_job_priority | ||||||
|  | - [ ] update_job_status | ||||||
|  | 
 | ||||||
| @ -102,6 +102,7 @@ mock_route53resolver = lazy_load( | |||||||
|     ".route53resolver", "mock_route53resolver", boto3_name="route53resolver" |     ".route53resolver", "mock_route53resolver", boto3_name="route53resolver" | ||||||
| ) | ) | ||||||
| mock_s3 = lazy_load(".s3", "mock_s3") | mock_s3 = lazy_load(".s3", "mock_s3") | ||||||
|  | mock_s3control = lazy_load(".s3control", "mock_s3control") | ||||||
| mock_sagemaker = lazy_load(".sagemaker", "mock_sagemaker") | mock_sagemaker = lazy_load(".sagemaker", "mock_sagemaker") | ||||||
| mock_secretsmanager = lazy_load(".secretsmanager", "mock_secretsmanager") | mock_secretsmanager = lazy_load(".secretsmanager", "mock_secretsmanager") | ||||||
| mock_ses = lazy_load(".ses", "mock_ses") | mock_ses = lazy_load(".ses", "mock_ses") | ||||||
|  | |||||||
| @ -108,13 +108,17 @@ backend_url_patterns = [ | |||||||
|         "route53resolver", |         "route53resolver", | ||||||
|         re.compile("https?://route53resolver\\.(.+)\\.amazonaws\\.com"), |         re.compile("https?://route53resolver\\.(.+)\\.amazonaws\\.com"), | ||||||
|     ), |     ), | ||||||
|     ("s3", re.compile("https?://s3(.*)\\.amazonaws.com")), |     ("s3", re.compile("https?://s3(?!-control)(.*)\\.amazonaws.com")), | ||||||
|     ( |     ( | ||||||
|         "s3", |         "s3", | ||||||
|         re.compile( |         re.compile( | ||||||
|             "https?://(?P<bucket_name>[a-zA-Z0-9\\-_.]*)\\.?s3(.*)\\.amazonaws.com" |             "https?://(?P<bucket_name>[a-zA-Z0-9\\-_.]*)\\.?s3(?!-control)(.*)\\.amazonaws.com" | ||||||
|         ), |         ), | ||||||
|     ), |     ), | ||||||
|  |     ( | ||||||
|  |         "s3control", | ||||||
|  |         re.compile("https?://([0-9]+)\\.s3-control\\.(.+)\\.amazonaws\\.com"), | ||||||
|  |     ), | ||||||
|     ("sagemaker", re.compile("https?://api.sagemaker\\.(.+)\\.amazonaws.com")), |     ("sagemaker", re.compile("https?://api.sagemaker\\.(.+)\\.amazonaws.com")), | ||||||
|     ("sdb", re.compile("https?://sdb\\.(.+)\\.amazonaws\\.com")), |     ("sdb", re.compile("https?://sdb\\.(.+)\\.amazonaws\\.com")), | ||||||
|     ("secretsmanager", re.compile("https?://secretsmanager\\.(.+)\\.amazonaws\\.com")), |     ("secretsmanager", re.compile("https?://secretsmanager\\.(.+)\\.amazonaws\\.com")), | ||||||
|  | |||||||
| @ -54,7 +54,8 @@ from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID | |||||||
| from moto.core.responses import AWSServiceSpec | from moto.core.responses import AWSServiceSpec | ||||||
| from moto.core.utils import BackendDict | from moto.core.utils import BackendDict | ||||||
| from moto.iam.config import role_config_query, policy_config_query | from moto.iam.config import role_config_query, policy_config_query | ||||||
| from moto.s3.config import s3_account_public_access_block_query, s3_config_query | from moto.s3.config import s3_config_query | ||||||
|  | from moto.s3control.config import s3_account_public_access_block_query | ||||||
| from moto.utilities.utils import load_resource | from moto.utilities.utils import load_resource | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,13 +1,8 @@ | |||||||
| import datetime |  | ||||||
| import json | import json | ||||||
| import time |  | ||||||
| 
 |  | ||||||
| from boto3 import Session |  | ||||||
| 
 | 
 | ||||||
| from moto.core.exceptions import InvalidNextTokenException | from moto.core.exceptions import InvalidNextTokenException | ||||||
| from moto.core.models import ConfigQueryModel | from moto.core.models import ConfigQueryModel | ||||||
| from moto.s3 import s3_backends | from moto.s3 import s3_backends | ||||||
| from moto.s3.models import get_moto_s3_account_id |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class S3ConfigQuery(ConfigQueryModel): | class S3ConfigQuery(ConfigQueryModel): | ||||||
| @ -124,147 +119,4 @@ class S3ConfigQuery(ConfigQueryModel): | |||||||
|         return config_data |         return config_data | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class S3AccountPublicAccessBlockConfigQuery(ConfigQueryModel): |  | ||||||
|     def list_config_service_resources( |  | ||||||
|         self, |  | ||||||
|         resource_ids, |  | ||||||
|         resource_name, |  | ||||||
|         limit, |  | ||||||
|         next_token, |  | ||||||
|         backend_region=None, |  | ||||||
|         resource_region=None, |  | ||||||
|         aggregator=None, |  | ||||||
|     ): |  | ||||||
|         # For the Account Public Access Block, they are the same for all regions. The resource ID is the AWS account ID |  | ||||||
|         # There is no resource name -- it should be a blank string "" if provided. |  | ||||||
| 
 |  | ||||||
|         # The resource name can only ever be None or an empty string: |  | ||||||
|         if resource_name is not None and resource_name != "": |  | ||||||
|             return [], None |  | ||||||
| 
 |  | ||||||
|         pab = None |  | ||||||
|         account_id = get_moto_s3_account_id() |  | ||||||
|         regions = [region for region in Session().get_available_regions("config")] |  | ||||||
| 
 |  | ||||||
|         # If a resource ID was passed in, then filter accordingly: |  | ||||||
|         if resource_ids: |  | ||||||
|             for resource_id in resource_ids: |  | ||||||
|                 if account_id == resource_id: |  | ||||||
|                     pab = self.backends["global"].account_public_access_block |  | ||||||
|                     break |  | ||||||
| 
 |  | ||||||
|         # Otherwise, just grab the one from the backend: |  | ||||||
|         if not resource_ids: |  | ||||||
|             pab = self.backends["global"].account_public_access_block |  | ||||||
| 
 |  | ||||||
|         # If it's not present, then return nothing |  | ||||||
|         if not pab: |  | ||||||
|             return [], None |  | ||||||
| 
 |  | ||||||
|         # Filter on regions (and paginate on them as well): |  | ||||||
|         if backend_region: |  | ||||||
|             pab_list = [backend_region] |  | ||||||
|         elif resource_region: |  | ||||||
|             # Invalid region? |  | ||||||
|             if resource_region not in regions: |  | ||||||
|                 return [], None |  | ||||||
| 
 |  | ||||||
|             pab_list = [resource_region] |  | ||||||
| 
 |  | ||||||
|         # Aggregated query where no regions were supplied so return them all: |  | ||||||
|         else: |  | ||||||
|             pab_list = regions |  | ||||||
| 
 |  | ||||||
|         # Pagination logic: |  | ||||||
|         sorted_regions = sorted(pab_list) |  | ||||||
|         new_token = None |  | ||||||
| 
 |  | ||||||
|         # Get the start: |  | ||||||
|         if not next_token: |  | ||||||
|             start = 0 |  | ||||||
|         else: |  | ||||||
|             # Tokens for this moto feature is just the region-name: |  | ||||||
|             # For OTHER non-global resource types, it's the region concatenated with the resource ID. |  | ||||||
|             if next_token not in sorted_regions: |  | ||||||
|                 raise InvalidNextTokenException() |  | ||||||
| 
 |  | ||||||
|             start = sorted_regions.index(next_token) |  | ||||||
| 
 |  | ||||||
|         # Get the list of items to collect: |  | ||||||
|         pab_list = sorted_regions[start : (start + limit)] |  | ||||||
| 
 |  | ||||||
|         if len(sorted_regions) > (start + limit): |  | ||||||
|             new_token = sorted_regions[start + limit] |  | ||||||
| 
 |  | ||||||
|         return ( |  | ||||||
|             [ |  | ||||||
|                 { |  | ||||||
|                     "type": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|                     "id": account_id, |  | ||||||
|                     "region": region, |  | ||||||
|                 } |  | ||||||
|                 for region in pab_list |  | ||||||
|             ], |  | ||||||
|             new_token, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     def get_config_resource( |  | ||||||
|         self, resource_id, resource_name=None, backend_region=None, resource_region=None |  | ||||||
|     ): |  | ||||||
|         # Do we even have this defined? |  | ||||||
|         if not self.backends["global"].account_public_access_block: |  | ||||||
|             return None |  | ||||||
| 
 |  | ||||||
|         # Resource name can only ever be "" if it's supplied: |  | ||||||
|         if resource_name is not None and resource_name != "": |  | ||||||
|             return None |  | ||||||
| 
 |  | ||||||
|         # Are we filtering based on region? |  | ||||||
|         account_id = get_moto_s3_account_id() |  | ||||||
|         regions = [region for region in Session().get_available_regions("config")] |  | ||||||
| 
 |  | ||||||
|         # Is the resource ID correct?: |  | ||||||
|         if account_id == resource_id: |  | ||||||
|             if backend_region: |  | ||||||
|                 pab_region = backend_region |  | ||||||
| 
 |  | ||||||
|             # Invalid region? |  | ||||||
|             elif resource_region not in regions: |  | ||||||
|                 return None |  | ||||||
| 
 |  | ||||||
|             else: |  | ||||||
|                 pab_region = resource_region |  | ||||||
| 
 |  | ||||||
|         else: |  | ||||||
|             return None |  | ||||||
| 
 |  | ||||||
|         # Format the PAB to the AWS Config format: |  | ||||||
|         creation_time = datetime.datetime.utcnow() |  | ||||||
|         config_data = { |  | ||||||
|             "version": "1.3", |  | ||||||
|             "accountId": account_id, |  | ||||||
|             "configurationItemCaptureTime": str(creation_time), |  | ||||||
|             "configurationItemStatus": "OK", |  | ||||||
|             "configurationStateId": str( |  | ||||||
|                 int(time.mktime(creation_time.timetuple())) |  | ||||||
|             ),  # PY2 and 3 compatible |  | ||||||
|             "resourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             "resourceId": account_id, |  | ||||||
|             "awsRegion": pab_region, |  | ||||||
|             "availabilityZone": "Not Applicable", |  | ||||||
|             "configuration": self.backends[ |  | ||||||
|                 "global" |  | ||||||
|             ].account_public_access_block.to_config_dict(), |  | ||||||
|             "supplementaryConfiguration": {}, |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         # The 'configuration' field is also a JSON string: |  | ||||||
|         config_data["configuration"] = json.dumps(config_data["configuration"]) |  | ||||||
| 
 |  | ||||||
|         return config_data |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| s3_config_query = S3ConfigQuery(s3_backends) | s3_config_query = S3ConfigQuery(s3_backends) | ||||||
| s3_account_public_access_block_query = S3AccountPublicAccessBlockConfigQuery( |  | ||||||
|     s3_backends |  | ||||||
| ) |  | ||||||
|  | |||||||
| @ -50,7 +50,6 @@ from moto.s3.exceptions import ( | |||||||
|     CrossLocationLoggingProhibitted, |     CrossLocationLoggingProhibitted, | ||||||
|     NoSuchPublicAccessBlockConfiguration, |     NoSuchPublicAccessBlockConfiguration, | ||||||
|     InvalidPublicAccessBlockConfiguration, |     InvalidPublicAccessBlockConfiguration, | ||||||
|     WrongPublicAccessBlockAccountIdError, |  | ||||||
|     NoSuchUpload, |     NoSuchUpload, | ||||||
|     ObjectLockConfigurationNotFoundError, |     ObjectLockConfigurationNotFoundError, | ||||||
|     InvalidTagError, |     InvalidTagError, | ||||||
| @ -1330,7 +1329,6 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider): | |||||||
| 
 | 
 | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.buckets = {} |         self.buckets = {} | ||||||
|         self.account_public_access_block = None |  | ||||||
|         self.tagger = TaggingService() |         self.tagger = TaggingService() | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
| @ -1572,16 +1570,6 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider): | |||||||
| 
 | 
 | ||||||
|         return bucket.public_access_block |         return bucket.public_access_block | ||||||
| 
 | 
 | ||||||
|     def get_account_public_access_block(self, account_id): |  | ||||||
|         # The account ID should equal the account id that is set for Moto: |  | ||||||
|         if account_id != ACCOUNT_ID: |  | ||||||
|             raise WrongPublicAccessBlockAccountIdError() |  | ||||||
| 
 |  | ||||||
|         if not self.account_public_access_block: |  | ||||||
|             raise NoSuchPublicAccessBlockConfiguration() |  | ||||||
| 
 |  | ||||||
|         return self.account_public_access_block |  | ||||||
| 
 |  | ||||||
|     def put_object( |     def put_object( | ||||||
|         self, |         self, | ||||||
|         bucket_name, |         bucket_name, | ||||||
| @ -1768,13 +1756,6 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider): | |||||||
|         bucket = self.get_bucket(bucket_name) |         bucket = self.get_bucket(bucket_name) | ||||||
|         bucket.public_access_block = None |         bucket.public_access_block = None | ||||||
| 
 | 
 | ||||||
|     def delete_account_public_access_block(self, account_id): |  | ||||||
|         # The account ID should equal the account id that is set for Moto: |  | ||||||
|         if account_id != ACCOUNT_ID: |  | ||||||
|             raise WrongPublicAccessBlockAccountIdError() |  | ||||||
| 
 |  | ||||||
|         self.account_public_access_block = None |  | ||||||
| 
 |  | ||||||
|     def put_bucket_notification_configuration(self, bucket_name, notification_config): |     def put_bucket_notification_configuration(self, bucket_name, notification_config): | ||||||
|         bucket = self.get_bucket(bucket_name) |         bucket = self.get_bucket(bucket_name) | ||||||
|         bucket.set_notification_configuration(notification_config) |         bucket.set_notification_configuration(notification_config) | ||||||
| @ -1803,21 +1784,6 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider): | |||||||
|             pub_block_config.get("RestrictPublicBuckets"), |             pub_block_config.get("RestrictPublicBuckets"), | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def put_account_public_access_block(self, account_id, pub_block_config): |  | ||||||
|         # The account ID should equal the account id that is set for Moto: |  | ||||||
|         if account_id != ACCOUNT_ID: |  | ||||||
|             raise WrongPublicAccessBlockAccountIdError() |  | ||||||
| 
 |  | ||||||
|         if not pub_block_config: |  | ||||||
|             raise InvalidPublicAccessBlockConfiguration() |  | ||||||
| 
 |  | ||||||
|         self.account_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): |     def initiate_multipart(self, bucket_name, key_name, metadata): | ||||||
|         bucket = self.get_bucket(bucket_name) |         bucket = self.get_bucket(bucket_name) | ||||||
|         new_multipart = FakeMultipart(key_name, metadata) |         new_multipart = FakeMultipart(key_name, metadata) | ||||||
|  | |||||||
| @ -3,8 +3,6 @@ import os | |||||||
| import re | import re | ||||||
| from typing import List, Union | from typing import List, Union | ||||||
| 
 | 
 | ||||||
| from botocore.awsrequest import AWSPreparedRequest |  | ||||||
| 
 |  | ||||||
| from moto import settings | from moto import settings | ||||||
| from moto.core.utils import amzn_request_id, str_to_rfc_1123_datetime | from moto.core.utils import amzn_request_id, str_to_rfc_1123_datetime | ||||||
| from urllib.parse import ( | from urllib.parse import ( | ||||||
| @ -261,7 +259,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): | |||||||
|         # Depending on which calling format the client is using, we don't know |         # Depending on which calling format the client is using, we don't know | ||||||
|         # if this is a bucket or key request so we have to check |         # if this is a bucket or key request so we have to check | ||||||
|         if self.subdomain_based_buckets(request): |         if self.subdomain_based_buckets(request): | ||||||
|             return self.key_or_control_response(request, full_url, headers) |             return self.key_response(request, full_url, headers) | ||||||
|         else: |         else: | ||||||
|             # Using path-based buckets |             # Using path-based buckets | ||||||
|             return self.bucket_response(request, full_url, headers) |             return self.bucket_response(request, full_url, headers) | ||||||
| @ -1092,7 +1090,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): | |||||||
|         return bytes(new_body) |         return bytes(new_body) | ||||||
| 
 | 
 | ||||||
|     @amzn_request_id |     @amzn_request_id | ||||||
|     def key_or_control_response(self, request, full_url, headers): |     def key_response(self, request, full_url, headers): | ||||||
|         # Key and Control are lumped in because splitting out the regex is too much of a pain :/ |         # Key and Control are lumped in because splitting out the regex is too much of a pain :/ | ||||||
|         self.method = request.method |         self.method = request.method | ||||||
|         self.path = self._get_path(request) |         self.path = self._get_path(request) | ||||||
| @ -1103,11 +1101,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): | |||||||
|         response_headers = {} |         response_headers = {} | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             # Is this an S3 control response? |             response = self._key_response(request, full_url, self.headers) | ||||||
|             if isinstance(request, AWSPreparedRequest) and "s3-control" in request.url: |  | ||||||
|                 response = self._control_response(request, full_url, headers) |  | ||||||
|             else: |  | ||||||
|                 response = self._key_response(request, full_url, self.headers) |  | ||||||
|         except S3ClientError as s3error: |         except S3ClientError as s3error: | ||||||
|             response = s3error.code, {}, s3error.description |             response = s3error.code, {}, s3error.description | ||||||
| 
 | 
 | ||||||
| @ -1130,94 +1124,6 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): | |||||||
|                 return s3error.code, {}, s3error.description |                 return s3error.code, {}, s3error.description | ||||||
|         return status_code, response_headers, response_content |         return status_code, response_headers, response_content | ||||||
| 
 | 
 | ||||||
|     def _control_response(self, request, full_url, headers): |  | ||||||
|         parsed_url = urlparse(full_url) |  | ||||||
|         query = parse_qs(parsed_url.query, keep_blank_values=True) |  | ||||||
|         method = request.method |  | ||||||
| 
 |  | ||||||
|         if hasattr(request, "body"): |  | ||||||
|             # Boto |  | ||||||
|             body = request.body |  | ||||||
|             if hasattr(body, "read"): |  | ||||||
|                 body = body.read() |  | ||||||
|         else: |  | ||||||
|             # Flask server |  | ||||||
|             body = request.data |  | ||||||
|         if body is None: |  | ||||||
|             body = b"" |  | ||||||
| 
 |  | ||||||
|         if method == "GET": |  | ||||||
|             return self._control_response_get(request, query, headers) |  | ||||||
|         elif method == "PUT": |  | ||||||
|             return self._control_response_put(request, body, query, headers) |  | ||||||
|         elif method == "DELETE": |  | ||||||
|             return self._control_response_delete(request, query, headers) |  | ||||||
|         else: |  | ||||||
|             raise NotImplementedError( |  | ||||||
|                 "Method {0} has not been implemented in the S3 backend yet".format( |  | ||||||
|                     method |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|     def _control_response_get(self, request, query, headers): |  | ||||||
|         action = self.path.split("?")[0].split("/")[ |  | ||||||
|             -1 |  | ||||||
|         ]  # Gets the action out of the URL sans query params. |  | ||||||
|         self._set_action("CONTROL", "GET", action) |  | ||||||
|         self._authenticate_and_authorize_s3_action() |  | ||||||
| 
 |  | ||||||
|         response_headers = {} |  | ||||||
|         if "publicAccessBlock" in action: |  | ||||||
|             public_block_config = self.backend.get_account_public_access_block( |  | ||||||
|                 headers["x-amz-account-id"] |  | ||||||
|             ) |  | ||||||
|             template = self.response_template(S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION) |  | ||||||
|             return ( |  | ||||||
|                 200, |  | ||||||
|                 response_headers, |  | ||||||
|                 template.render(public_block_config=public_block_config), |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|         raise NotImplementedError( |  | ||||||
|             "Method {0} has not been implemented in the S3 backend yet".format(action) |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     def _control_response_put(self, request, body, query, headers): |  | ||||||
|         action = self.path.split("?")[0].split("/")[ |  | ||||||
|             -1 |  | ||||||
|         ]  # Gets the action out of the URL sans query params. |  | ||||||
|         self._set_action("CONTROL", "PUT", action) |  | ||||||
|         self._authenticate_and_authorize_s3_action() |  | ||||||
| 
 |  | ||||||
|         response_headers = {} |  | ||||||
|         if "publicAccessBlock" in action: |  | ||||||
|             pab_config = self._parse_pab_config(body) |  | ||||||
|             self.backend.put_account_public_access_block( |  | ||||||
|                 headers["x-amz-account-id"], |  | ||||||
|                 pab_config["PublicAccessBlockConfiguration"], |  | ||||||
|             ) |  | ||||||
|             return 200, response_headers, "" |  | ||||||
| 
 |  | ||||||
|         raise NotImplementedError( |  | ||||||
|             "Method {0} has not been implemented in the S3 backend yet".format(action) |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     def _control_response_delete(self, request, query, headers): |  | ||||||
|         action = self.path.split("?")[0].split("/")[ |  | ||||||
|             -1 |  | ||||||
|         ]  # Gets the action out of the URL sans query params. |  | ||||||
|         self._set_action("CONTROL", "DELETE", action) |  | ||||||
|         self._authenticate_and_authorize_s3_action() |  | ||||||
| 
 |  | ||||||
|         response_headers = {} |  | ||||||
|         if "publicAccessBlock" in action: |  | ||||||
|             self.backend.delete_account_public_access_block(headers["x-amz-account-id"]) |  | ||||||
|             return 200, response_headers, "" |  | ||||||
| 
 |  | ||||||
|         raise NotImplementedError( |  | ||||||
|             "Method {0} has not been implemented in the S3 backend yet".format(action) |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     def _key_response(self, request, full_url, headers): |     def _key_response(self, request, full_url, headers): | ||||||
|         parsed_url = urlparse(full_url) |         parsed_url = urlparse(full_url) | ||||||
|         query = parse_qs(parsed_url.query, keep_blank_values=True) |         query = parse_qs(parsed_url.query, keep_blank_values=True) | ||||||
|  | |||||||
| @ -2,9 +2,10 @@ from moto import settings | |||||||
| 
 | 
 | ||||||
| from .responses import S3ResponseInstance | from .responses import S3ResponseInstance | ||||||
| 
 | 
 | ||||||
|  | # Catch s3.amazonaws.com, but not s3-control.amazonaws.com | ||||||
| url_bases = [ | url_bases = [ | ||||||
|     r"https?://s3(.*)\.amazonaws.com", |     r"https?://s3(?!-control)(.*)\.amazonaws.com", | ||||||
|     r"https?://(?P<bucket_name>[a-zA-Z0-9\-_.]*)\.?s3(.*)\.amazonaws.com", |     r"https?://(?P<bucket_name>[a-zA-Z0-9\-_.]*)\.?s3(?!-control)(.*)\.amazonaws.com", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| url_bases.extend(settings.get_s3_custom_endpoints()) | url_bases.extend(settings.get_s3_custom_endpoints()) | ||||||
| @ -15,7 +16,7 @@ url_paths = { | |||||||
|     # subdomain key of path-based bucket |     # subdomain key of path-based bucket | ||||||
|     "{0}/(?P<key_or_bucket_name>[^/]+)/?$": S3ResponseInstance.ambiguous_response, |     "{0}/(?P<key_or_bucket_name>[^/]+)/?$": S3ResponseInstance.ambiguous_response, | ||||||
|     # path-based bucket + key |     # path-based bucket + key | ||||||
|     "{0}/(?P<bucket_name_path>[^/]+)/(?P<key_name>.+)": S3ResponseInstance.key_or_control_response, |     "{0}/(?P<bucket_name_path>[^/]+)/(?P<key_name>.+)": S3ResponseInstance.key_response, | ||||||
|     # subdomain bucket + key with empty first part of path |     # subdomain bucket + key with empty first part of path | ||||||
|     "{0}/(?P<key_name>/.*)$": S3ResponseInstance.key_or_control_response, |     "{0}/(?P<key_name>/.*)$": S3ResponseInstance.key_response, | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								moto/s3control/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								moto/s3control/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | """s3control module initialization; sets value for base decorator.""" | ||||||
|  | from .models import s3control_backend | ||||||
|  | from ..core.models import base_decorator | ||||||
|  | 
 | ||||||
|  | s3control_backends = {"global": s3control_backend} | ||||||
|  | mock_s3control = base_decorator(s3control_backends) | ||||||
							
								
								
									
										155
									
								
								moto/s3control/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								moto/s3control/config.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,155 @@ | |||||||
|  | import datetime | ||||||
|  | import json | ||||||
|  | import time | ||||||
|  | 
 | ||||||
|  | from boto3 import Session | ||||||
|  | 
 | ||||||
|  | from moto.core.exceptions import InvalidNextTokenException | ||||||
|  | from moto.core.models import ConfigQueryModel | ||||||
|  | from moto.s3control import s3control_backends | ||||||
|  | from moto.s3.models import get_moto_s3_account_id | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class S3AccountPublicAccessBlockConfigQuery(ConfigQueryModel): | ||||||
|  |     def list_config_service_resources( | ||||||
|  |         self, | ||||||
|  |         resource_ids, | ||||||
|  |         resource_name, | ||||||
|  |         limit, | ||||||
|  |         next_token, | ||||||
|  |         backend_region=None, | ||||||
|  |         resource_region=None, | ||||||
|  |         aggregator=None, | ||||||
|  |     ): | ||||||
|  |         # For the Account Public Access Block, they are the same for all regions. The resource ID is the AWS account ID | ||||||
|  |         # There is no resource name -- it should be a blank string "" if provided. | ||||||
|  | 
 | ||||||
|  |         # The resource name can only ever be None or an empty string: | ||||||
|  |         if resource_name is not None and resource_name != "": | ||||||
|  |             return [], None | ||||||
|  | 
 | ||||||
|  |         pab = None | ||||||
|  |         account_id = get_moto_s3_account_id() | ||||||
|  |         regions = [region for region in Session().get_available_regions("config")] | ||||||
|  | 
 | ||||||
|  |         # If a resource ID was passed in, then filter accordingly: | ||||||
|  |         if resource_ids: | ||||||
|  |             for resource_id in resource_ids: | ||||||
|  |                 if account_id == resource_id: | ||||||
|  |                     pab = self.backends["global"].public_access_block | ||||||
|  |                     break | ||||||
|  | 
 | ||||||
|  |         # Otherwise, just grab the one from the backend: | ||||||
|  |         if not resource_ids: | ||||||
|  |             pab = self.backends["global"].public_access_block | ||||||
|  | 
 | ||||||
|  |         # If it's not present, then return nothing | ||||||
|  |         if not pab: | ||||||
|  |             return [], None | ||||||
|  | 
 | ||||||
|  |         # Filter on regions (and paginate on them as well): | ||||||
|  |         if backend_region: | ||||||
|  |             pab_list = [backend_region] | ||||||
|  |         elif resource_region: | ||||||
|  |             # Invalid region? | ||||||
|  |             if resource_region not in regions: | ||||||
|  |                 return [], None | ||||||
|  | 
 | ||||||
|  |             pab_list = [resource_region] | ||||||
|  | 
 | ||||||
|  |         # Aggregated query where no regions were supplied so return them all: | ||||||
|  |         else: | ||||||
|  |             pab_list = regions | ||||||
|  | 
 | ||||||
|  |         # Pagination logic: | ||||||
|  |         sorted_regions = sorted(pab_list) | ||||||
|  |         new_token = None | ||||||
|  | 
 | ||||||
|  |         # Get the start: | ||||||
|  |         if not next_token: | ||||||
|  |             start = 0 | ||||||
|  |         else: | ||||||
|  |             # Tokens for this moto feature is just the region-name: | ||||||
|  |             # For OTHER non-global resource types, it's the region concatenated with the resource ID. | ||||||
|  |             if next_token not in sorted_regions: | ||||||
|  |                 raise InvalidNextTokenException() | ||||||
|  | 
 | ||||||
|  |             start = sorted_regions.index(next_token) | ||||||
|  | 
 | ||||||
|  |         # Get the list of items to collect: | ||||||
|  |         pab_list = sorted_regions[start : (start + limit)] | ||||||
|  | 
 | ||||||
|  |         if len(sorted_regions) > (start + limit): | ||||||
|  |             new_token = sorted_regions[start + limit] | ||||||
|  | 
 | ||||||
|  |         return ( | ||||||
|  |             [ | ||||||
|  |                 { | ||||||
|  |                     "type": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |                     "id": account_id, | ||||||
|  |                     "region": region, | ||||||
|  |                 } | ||||||
|  |                 for region in pab_list | ||||||
|  |             ], | ||||||
|  |             new_token, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def get_config_resource( | ||||||
|  |         self, resource_id, resource_name=None, backend_region=None, resource_region=None | ||||||
|  |     ): | ||||||
|  |         # Do we even have this defined? | ||||||
|  |         if not self.backends["global"].public_access_block: | ||||||
|  |             return None | ||||||
|  | 
 | ||||||
|  |         # Resource name can only ever be "" if it's supplied: | ||||||
|  |         if resource_name is not None and resource_name != "": | ||||||
|  |             return None | ||||||
|  | 
 | ||||||
|  |         # Are we filtering based on region? | ||||||
|  |         account_id = get_moto_s3_account_id() | ||||||
|  |         regions = [region for region in Session().get_available_regions("config")] | ||||||
|  | 
 | ||||||
|  |         # Is the resource ID correct?: | ||||||
|  |         if account_id == resource_id: | ||||||
|  |             if backend_region: | ||||||
|  |                 pab_region = backend_region | ||||||
|  | 
 | ||||||
|  |             # Invalid region? | ||||||
|  |             elif resource_region not in regions: | ||||||
|  |                 return None | ||||||
|  | 
 | ||||||
|  |             else: | ||||||
|  |                 pab_region = resource_region | ||||||
|  | 
 | ||||||
|  |         else: | ||||||
|  |             return None | ||||||
|  | 
 | ||||||
|  |         # Format the PAB to the AWS Config format: | ||||||
|  |         creation_time = datetime.datetime.utcnow() | ||||||
|  |         config_data = { | ||||||
|  |             "version": "1.3", | ||||||
|  |             "accountId": account_id, | ||||||
|  |             "configurationItemCaptureTime": str(creation_time), | ||||||
|  |             "configurationItemStatus": "OK", | ||||||
|  |             "configurationStateId": str( | ||||||
|  |                 int(time.mktime(creation_time.timetuple())) | ||||||
|  |             ),  # PY2 and 3 compatible | ||||||
|  |             "resourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             "resourceId": account_id, | ||||||
|  |             "awsRegion": pab_region, | ||||||
|  |             "availabilityZone": "Not Applicable", | ||||||
|  |             "configuration": self.backends[ | ||||||
|  |                 "global" | ||||||
|  |             ].public_access_block.to_config_dict(), | ||||||
|  |             "supplementaryConfiguration": {}, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         # The 'configuration' field is also a JSON string: | ||||||
|  |         config_data["configuration"] = json.dumps(config_data["configuration"]) | ||||||
|  | 
 | ||||||
|  |         return config_data | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | s3_account_public_access_block_query = S3AccountPublicAccessBlockConfigQuery( | ||||||
|  |     s3control_backends | ||||||
|  | ) | ||||||
							
								
								
									
										61
									
								
								moto/s3control/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								moto/s3control/models.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | |||||||
|  | from moto.core import ACCOUNT_ID, BaseBackend | ||||||
|  | from moto.s3.exceptions import ( | ||||||
|  |     WrongPublicAccessBlockAccountIdError, | ||||||
|  |     NoSuchPublicAccessBlockConfiguration, | ||||||
|  |     InvalidPublicAccessBlockConfiguration, | ||||||
|  | ) | ||||||
|  | from moto.s3.models import PublicAccessBlock | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class S3ControlBackend(BaseBackend): | ||||||
|  |     """ | ||||||
|  |     S3-Control cannot be accessed via the MotoServer without a modification of the hosts file on your system. | ||||||
|  |     This is due to the fact that the URL to the host is in the form of: | ||||||
|  |     ACCOUNT_ID.s3-control.amazonaws.com | ||||||
|  | 
 | ||||||
|  |     That Account ID part is the problem. If you want to make use of the moto server, update your hosts file for `THE_ACCOUNT_ID_FOR_MOTO.localhost` and this will work fine. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def __init__(self, region_name=None): | ||||||
|  |         self.region_name = region_name | ||||||
|  |         self.public_access_block = None | ||||||
|  | 
 | ||||||
|  |     def reset(self): | ||||||
|  |         region_name = self.region_name | ||||||
|  |         self.__dict__ = {} | ||||||
|  |         self.__init__(region_name) | ||||||
|  | 
 | ||||||
|  |     def get_public_access_block(self, account_id): | ||||||
|  |         # The account ID should equal the account id that is set for Moto: | ||||||
|  |         if account_id != ACCOUNT_ID: | ||||||
|  |             raise WrongPublicAccessBlockAccountIdError() | ||||||
|  | 
 | ||||||
|  |         if not self.public_access_block: | ||||||
|  |             raise NoSuchPublicAccessBlockConfiguration() | ||||||
|  | 
 | ||||||
|  |         return self.public_access_block | ||||||
|  | 
 | ||||||
|  |     def delete_public_access_block(self, account_id): | ||||||
|  |         # The account ID should equal the account id that is set for Moto: | ||||||
|  |         if account_id != ACCOUNT_ID: | ||||||
|  |             raise WrongPublicAccessBlockAccountIdError() | ||||||
|  | 
 | ||||||
|  |         self.public_access_block = None | ||||||
|  | 
 | ||||||
|  |     def put_public_access_block(self, account_id, pub_block_config): | ||||||
|  |         # The account ID should equal the account id that is set for Moto: | ||||||
|  |         if account_id != ACCOUNT_ID: | ||||||
|  |             raise WrongPublicAccessBlockAccountIdError() | ||||||
|  | 
 | ||||||
|  |         if not pub_block_config: | ||||||
|  |             raise InvalidPublicAccessBlockConfiguration() | ||||||
|  | 
 | ||||||
|  |         self.public_access_block = PublicAccessBlock( | ||||||
|  |             pub_block_config.get("BlockPublicAcls"), | ||||||
|  |             pub_block_config.get("IgnorePublicAcls"), | ||||||
|  |             pub_block_config.get("BlockPublicPolicy"), | ||||||
|  |             pub_block_config.get("RestrictPublicBuckets"), | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | s3control_backend = S3ControlBackend() | ||||||
							
								
								
									
										54
									
								
								moto/s3control/responses.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								moto/s3control/responses.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | |||||||
|  | import json | ||||||
|  | import xmltodict | ||||||
|  | 
 | ||||||
|  | from moto.core.responses import BaseResponse | ||||||
|  | from moto.core.utils import amzn_request_id | ||||||
|  | from moto.s3.exceptions import S3ClientError | ||||||
|  | from moto.s3.responses import S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION | ||||||
|  | from .models import s3control_backend | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class S3ControlResponse(BaseResponse): | ||||||
|  |     @classmethod | ||||||
|  |     def public_access_block(cls, request, full_url, headers): | ||||||
|  |         response_instance = S3ControlResponse() | ||||||
|  |         try: | ||||||
|  |             return response_instance._public_access_block(request, headers) | ||||||
|  |         except S3ClientError as err: | ||||||
|  |             return err.code, {}, err.description | ||||||
|  | 
 | ||||||
|  |     @amzn_request_id | ||||||
|  |     def _public_access_block(self, request, headers): | ||||||
|  |         if request.method == "GET": | ||||||
|  |             return self.get_public_access_block(headers) | ||||||
|  |         elif request.method == "PUT": | ||||||
|  |             return self.put_public_access_block(request, headers) | ||||||
|  |         elif request.method == "DELETE": | ||||||
|  |             return self.delete_public_access_block(headers) | ||||||
|  | 
 | ||||||
|  |     def get_public_access_block(self, headers): | ||||||
|  |         account_id = headers["x-amz-account-id"] | ||||||
|  |         public_block_config = s3control_backend.get_public_access_block( | ||||||
|  |             account_id=account_id, | ||||||
|  |         ) | ||||||
|  |         template = self.response_template(S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION) | ||||||
|  |         return 200, {}, template.render(public_block_config=public_block_config) | ||||||
|  | 
 | ||||||
|  |     def put_public_access_block(self, request, headers): | ||||||
|  |         account_id = headers["x-amz-account-id"] | ||||||
|  |         pab_config = self._parse_pab_config(request.body) | ||||||
|  |         s3control_backend.put_public_access_block( | ||||||
|  |             account_id, pab_config["PublicAccessBlockConfiguration"] | ||||||
|  |         ) | ||||||
|  |         return 201, {}, json.dumps({}) | ||||||
|  | 
 | ||||||
|  |     def delete_public_access_block(self, headers): | ||||||
|  |         account_id = headers["x-amz-account-id"] | ||||||
|  |         s3control_backend.delete_public_access_block(account_id=account_id,) | ||||||
|  |         return 204, {}, json.dumps({}) | ||||||
|  | 
 | ||||||
|  |     def _parse_pab_config(self, body): | ||||||
|  |         parsed_xml = xmltodict.parse(body) | ||||||
|  |         parsed_xml["PublicAccessBlockConfiguration"].pop("@xmlns", None) | ||||||
|  | 
 | ||||||
|  |         return parsed_xml | ||||||
							
								
								
									
										11
									
								
								moto/s3control/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								moto/s3control/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | """s3control base URL and path.""" | ||||||
|  | from .responses import S3ControlResponse | ||||||
|  | 
 | ||||||
|  | url_bases = [ | ||||||
|  |     r"https?://([0-9]+)\.s3-control\.(.+)\.amazonaws\.com", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | url_paths = { | ||||||
|  |     "{0}/v20180820/configuration/publicAccessBlock$": S3ControlResponse.public_access_block, | ||||||
|  | } | ||||||
| @ -45,4 +45,4 @@ def test_domain_dispatched_with_service(): | |||||||
|     dispatcher = DomainDispatcherApplication(create_backend_app, service="s3") |     dispatcher = DomainDispatcherApplication(create_backend_app, service="s3") | ||||||
|     backend_app = dispatcher.get_application({"HTTP_HOST": "s3.us-east1.amazonaws.com"}) |     backend_app = dispatcher.get_application({"HTTP_HOST": "s3.us-east1.amazonaws.com"}) | ||||||
|     keys = set(backend_app.view_functions.keys()) |     keys = set(backend_app.view_functions.keys()) | ||||||
|     keys.should.contain("ResponseObject.key_or_control_response") |     keys.should.contain("ResponseObject.key_response") | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import datetime | import datetime | ||||||
| import os | import os | ||||||
| from boto3 import Session |  | ||||||
| from urllib.parse import urlparse, parse_qs | from urllib.parse import urlparse, parse_qs | ||||||
| from functools import wraps | from functools import wraps | ||||||
| from gzip import GzipFile | from gzip import GzipFile | ||||||
| @ -1418,376 +1417,6 @@ if not settings.TEST_SERVER_MODE: | |||||||
|             "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." | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     # All tests for s3-control cannot be run under the server without a modification of the |  | ||||||
|     # hosts file on your system. This is due to the fact that the URL to the host is in the form of: |  | ||||||
|     # ACCOUNT_ID.s3-control.amazonaws.com <-- That Account ID part is the problem. If you want to |  | ||||||
|     # make use of the moto server, update your hosts file for `THE_ACCOUNT_ID_FOR_MOTO.localhost` |  | ||||||
|     # and this will work fine. |  | ||||||
| 
 |  | ||||||
|     @mock_s3 |  | ||||||
|     def test_get_public_access_block_for_account(): |  | ||||||
|         from moto.s3.models import ACCOUNT_ID |  | ||||||
| 
 |  | ||||||
|         client = boto3.client("s3control", region_name="us-west-2") |  | ||||||
| 
 |  | ||||||
|         # With an invalid account ID: |  | ||||||
|         with pytest.raises(ClientError) as ce: |  | ||||||
|             client.get_public_access_block(AccountId="111111111111") |  | ||||||
|         assert ce.value.response["Error"]["Code"] == "AccessDenied" |  | ||||||
| 
 |  | ||||||
|         # Without one defined: |  | ||||||
|         with pytest.raises(ClientError) as ce: |  | ||||||
|             client.get_public_access_block(AccountId=ACCOUNT_ID) |  | ||||||
|         assert ( |  | ||||||
|             ce.value.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration" |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         # Put a with an invalid account ID: |  | ||||||
|         with pytest.raises(ClientError) as ce: |  | ||||||
|             client.put_public_access_block( |  | ||||||
|                 AccountId="111111111111", |  | ||||||
|                 PublicAccessBlockConfiguration={"BlockPublicAcls": True}, |  | ||||||
|             ) |  | ||||||
|         assert ce.value.response["Error"]["Code"] == "AccessDenied" |  | ||||||
| 
 |  | ||||||
|         # Put with an invalid PAB: |  | ||||||
|         with pytest.raises(ClientError) as ce: |  | ||||||
|             client.put_public_access_block( |  | ||||||
|                 AccountId=ACCOUNT_ID, PublicAccessBlockConfiguration={} |  | ||||||
|             ) |  | ||||||
|         assert ce.value.response["Error"]["Code"] == "InvalidRequest" |  | ||||||
|         assert ( |  | ||||||
|             "Must specify at least one configuration." |  | ||||||
|             in ce.value.response["Error"]["Message"] |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         # Correct PAB: |  | ||||||
|         client.put_public_access_block( |  | ||||||
|             AccountId=ACCOUNT_ID, |  | ||||||
|             PublicAccessBlockConfiguration={ |  | ||||||
|                 "BlockPublicAcls": True, |  | ||||||
|                 "IgnorePublicAcls": True, |  | ||||||
|                 "BlockPublicPolicy": True, |  | ||||||
|                 "RestrictPublicBuckets": True, |  | ||||||
|             }, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         # Get the correct PAB (for all regions): |  | ||||||
|         for region in Session().get_available_regions("s3control"): |  | ||||||
|             region_client = boto3.client("s3control", region_name=region) |  | ||||||
|             assert region_client.get_public_access_block(AccountId=ACCOUNT_ID)[ |  | ||||||
|                 "PublicAccessBlockConfiguration" |  | ||||||
|             ] == { |  | ||||||
|                 "BlockPublicAcls": True, |  | ||||||
|                 "IgnorePublicAcls": True, |  | ||||||
|                 "BlockPublicPolicy": True, |  | ||||||
|                 "RestrictPublicBuckets": True, |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         # Delete with an invalid account ID: |  | ||||||
|         with pytest.raises(ClientError) as ce: |  | ||||||
|             client.delete_public_access_block(AccountId="111111111111") |  | ||||||
|         assert ce.value.response["Error"]["Code"] == "AccessDenied" |  | ||||||
| 
 |  | ||||||
|         # Delete successfully: |  | ||||||
|         client.delete_public_access_block(AccountId=ACCOUNT_ID) |  | ||||||
| 
 |  | ||||||
|         # Confirm that it's deleted: |  | ||||||
|         with pytest.raises(ClientError) as ce: |  | ||||||
|             client.get_public_access_block(AccountId=ACCOUNT_ID) |  | ||||||
|         assert ( |  | ||||||
|             ce.value.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration" |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     @mock_s3 |  | ||||||
|     @mock_config |  | ||||||
|     def test_config_list_account_pab(): |  | ||||||
|         from moto.s3.models import ACCOUNT_ID |  | ||||||
| 
 |  | ||||||
|         client = boto3.client("s3control", region_name="us-west-2") |  | ||||||
|         config_client = boto3.client("config", region_name="us-west-2") |  | ||||||
| 
 |  | ||||||
|         # Create the aggregator: |  | ||||||
|         account_aggregation_source = { |  | ||||||
|             "AccountIds": [ACCOUNT_ID], |  | ||||||
|             "AllAwsRegions": True, |  | ||||||
|         } |  | ||||||
|         config_client.put_configuration_aggregator( |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             AccountAggregationSources=[account_aggregation_source], |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         # Without a PAB in place: |  | ||||||
|         result = config_client.list_discovered_resources( |  | ||||||
|             resourceType="AWS::S3::AccountPublicAccessBlock" |  | ||||||
|         ) |  | ||||||
|         assert not result["resourceIdentifiers"] |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|         ) |  | ||||||
|         assert not result["ResourceIdentifiers"] |  | ||||||
| 
 |  | ||||||
|         # Create a PAB: |  | ||||||
|         client.put_public_access_block( |  | ||||||
|             AccountId=ACCOUNT_ID, |  | ||||||
|             PublicAccessBlockConfiguration={ |  | ||||||
|                 "BlockPublicAcls": True, |  | ||||||
|                 "IgnorePublicAcls": True, |  | ||||||
|                 "BlockPublicPolicy": True, |  | ||||||
|                 "RestrictPublicBuckets": True, |  | ||||||
|             }, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         # Test that successful queries work (non-aggregated): |  | ||||||
|         result = config_client.list_discovered_resources( |  | ||||||
|             resourceType="AWS::S3::AccountPublicAccessBlock" |  | ||||||
|         ) |  | ||||||
|         assert result["resourceIdentifiers"] == [ |  | ||||||
|             { |  | ||||||
|                 "resourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|                 "resourceId": ACCOUNT_ID, |  | ||||||
|             } |  | ||||||
|         ] |  | ||||||
|         result = config_client.list_discovered_resources( |  | ||||||
|             resourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             resourceIds=[ACCOUNT_ID, "nope"], |  | ||||||
|         ) |  | ||||||
|         assert result["resourceIdentifiers"] == [ |  | ||||||
|             { |  | ||||||
|                 "resourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|                 "resourceId": ACCOUNT_ID, |  | ||||||
|             } |  | ||||||
|         ] |  | ||||||
|         result = config_client.list_discovered_resources( |  | ||||||
|             resourceType="AWS::S3::AccountPublicAccessBlock", resourceName="" |  | ||||||
|         ) |  | ||||||
|         assert result["resourceIdentifiers"] == [ |  | ||||||
|             { |  | ||||||
|                 "resourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|                 "resourceId": ACCOUNT_ID, |  | ||||||
|             } |  | ||||||
|         ] |  | ||||||
| 
 |  | ||||||
|         # Test that successful queries work (aggregated): |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|         ) |  | ||||||
|         regions = {region for region in Session().get_available_regions("config")} |  | ||||||
|         for r in result["ResourceIdentifiers"]: |  | ||||||
|             regions.remove(r.pop("SourceRegion")) |  | ||||||
|             assert r == { |  | ||||||
|                 "ResourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|                 "SourceAccountId": ACCOUNT_ID, |  | ||||||
|                 "ResourceId": ACCOUNT_ID, |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         # Just check that the len is the same -- this should be reasonable |  | ||||||
|         regions = {region for region in Session().get_available_regions("config")} |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             Filters={"ResourceName": ""}, |  | ||||||
|         ) |  | ||||||
|         assert len(regions) == len(result["ResourceIdentifiers"]) |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             Filters={"ResourceName": "", "ResourceId": ACCOUNT_ID}, |  | ||||||
|         ) |  | ||||||
|         assert len(regions) == len(result["ResourceIdentifiers"]) |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             Filters={ |  | ||||||
|                 "ResourceName": "", |  | ||||||
|                 "ResourceId": ACCOUNT_ID, |  | ||||||
|                 "Region": "us-west-2", |  | ||||||
|             }, |  | ||||||
|         ) |  | ||||||
|         assert ( |  | ||||||
|             result["ResourceIdentifiers"][0]["SourceRegion"] == "us-west-2" |  | ||||||
|             and len(result["ResourceIdentifiers"]) == 1 |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         # Test aggregator pagination: |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             Limit=1, |  | ||||||
|         ) |  | ||||||
|         regions = sorted( |  | ||||||
|             [region for region in Session().get_available_regions("config")] |  | ||||||
|         ) |  | ||||||
|         assert result["ResourceIdentifiers"][0] == { |  | ||||||
|             "ResourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             "SourceAccountId": ACCOUNT_ID, |  | ||||||
|             "ResourceId": ACCOUNT_ID, |  | ||||||
|             "SourceRegion": regions[0], |  | ||||||
|         } |  | ||||||
|         assert result["NextToken"] == regions[1] |  | ||||||
| 
 |  | ||||||
|         # Get the next region: |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             Limit=1, |  | ||||||
|             NextToken=regions[1], |  | ||||||
|         ) |  | ||||||
|         assert result["ResourceIdentifiers"][0] == { |  | ||||||
|             "ResourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             "SourceAccountId": ACCOUNT_ID, |  | ||||||
|             "ResourceId": ACCOUNT_ID, |  | ||||||
|             "SourceRegion": regions[1], |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         # Non-aggregated with incorrect info: |  | ||||||
|         result = config_client.list_discovered_resources( |  | ||||||
|             resourceType="AWS::S3::AccountPublicAccessBlock", resourceName="nope" |  | ||||||
|         ) |  | ||||||
|         assert not result["resourceIdentifiers"] |  | ||||||
|         result = config_client.list_discovered_resources( |  | ||||||
|             resourceType="AWS::S3::AccountPublicAccessBlock", resourceIds=["nope"] |  | ||||||
|         ) |  | ||||||
|         assert not result["resourceIdentifiers"] |  | ||||||
| 
 |  | ||||||
|         # Aggregated with incorrect info: |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             Filters={"ResourceName": "nope"}, |  | ||||||
|         ) |  | ||||||
|         assert not result["ResourceIdentifiers"] |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             Filters={"ResourceId": "nope"}, |  | ||||||
|         ) |  | ||||||
|         assert not result["ResourceIdentifiers"] |  | ||||||
|         result = config_client.list_aggregate_discovered_resources( |  | ||||||
|             ResourceType="AWS::S3::AccountPublicAccessBlock", |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             Filters={"Region": "Nope"}, |  | ||||||
|         ) |  | ||||||
|         assert not result["ResourceIdentifiers"] |  | ||||||
| 
 |  | ||||||
|     @mock_s3 |  | ||||||
|     @mock_config |  | ||||||
|     def test_config_get_account_pab(): |  | ||||||
|         from moto.s3.models import ACCOUNT_ID |  | ||||||
| 
 |  | ||||||
|         client = boto3.client("s3control", region_name="us-west-2") |  | ||||||
|         config_client = boto3.client("config", region_name="us-west-2") |  | ||||||
| 
 |  | ||||||
|         # Create the aggregator: |  | ||||||
|         account_aggregation_source = { |  | ||||||
|             "AccountIds": [ACCOUNT_ID], |  | ||||||
|             "AllAwsRegions": True, |  | ||||||
|         } |  | ||||||
|         config_client.put_configuration_aggregator( |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             AccountAggregationSources=[account_aggregation_source], |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         # Without a PAB in place: |  | ||||||
|         with pytest.raises(ClientError) as ce: |  | ||||||
|             config_client.get_resource_config_history( |  | ||||||
|                 resourceType="AWS::S3::AccountPublicAccessBlock", resourceId=ACCOUNT_ID |  | ||||||
|             ) |  | ||||||
|         assert ce.value.response["Error"]["Code"] == "ResourceNotDiscoveredException" |  | ||||||
|         # aggregate |  | ||||||
|         result = config_client.batch_get_resource_config( |  | ||||||
|             resourceKeys=[ |  | ||||||
|                 { |  | ||||||
|                     "resourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|                     "resourceId": "ACCOUNT_ID", |  | ||||||
|                 } |  | ||||||
|             ] |  | ||||||
|         ) |  | ||||||
|         assert not result["baseConfigurationItems"] |  | ||||||
|         result = config_client.batch_get_aggregate_resource_config( |  | ||||||
|             ConfigurationAggregatorName="testing", |  | ||||||
|             ResourceIdentifiers=[ |  | ||||||
|                 { |  | ||||||
|                     "SourceAccountId": ACCOUNT_ID, |  | ||||||
|                     "SourceRegion": "us-west-2", |  | ||||||
|                     "ResourceId": ACCOUNT_ID, |  | ||||||
|                     "ResourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|                     "ResourceName": "", |  | ||||||
|                 } |  | ||||||
|             ], |  | ||||||
|         ) |  | ||||||
|         assert not result["BaseConfigurationItems"] |  | ||||||
| 
 |  | ||||||
|         # Create a PAB: |  | ||||||
|         client.put_public_access_block( |  | ||||||
|             AccountId=ACCOUNT_ID, |  | ||||||
|             PublicAccessBlockConfiguration={ |  | ||||||
|                 "BlockPublicAcls": True, |  | ||||||
|                 "IgnorePublicAcls": True, |  | ||||||
|                 "BlockPublicPolicy": True, |  | ||||||
|                 "RestrictPublicBuckets": True, |  | ||||||
|             }, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         # Get the proper config: |  | ||||||
|         proper_config = { |  | ||||||
|             "blockPublicAcls": True, |  | ||||||
|             "ignorePublicAcls": True, |  | ||||||
|             "blockPublicPolicy": True, |  | ||||||
|             "restrictPublicBuckets": True, |  | ||||||
|         } |  | ||||||
|         result = config_client.get_resource_config_history( |  | ||||||
|             resourceType="AWS::S3::AccountPublicAccessBlock", resourceId=ACCOUNT_ID |  | ||||||
|         ) |  | ||||||
|         assert ( |  | ||||||
|             json.loads(result["configurationItems"][0]["configuration"]) |  | ||||||
|             == proper_config |  | ||||||
|         ) |  | ||||||
|         assert ( |  | ||||||
|             result["configurationItems"][0]["accountId"] |  | ||||||
|             == result["configurationItems"][0]["resourceId"] |  | ||||||
|             == ACCOUNT_ID |  | ||||||
|         ) |  | ||||||
|         result = config_client.batch_get_resource_config( |  | ||||||
|             resourceKeys=[ |  | ||||||
|                 { |  | ||||||
|                     "resourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|                     "resourceId": ACCOUNT_ID, |  | ||||||
|                 } |  | ||||||
|             ] |  | ||||||
|         ) |  | ||||||
|         assert len(result["baseConfigurationItems"]) == 1 |  | ||||||
|         assert ( |  | ||||||
|             json.loads(result["baseConfigurationItems"][0]["configuration"]) |  | ||||||
|             == proper_config |  | ||||||
|         ) |  | ||||||
|         assert ( |  | ||||||
|             result["baseConfigurationItems"][0]["accountId"] |  | ||||||
|             == result["baseConfigurationItems"][0]["resourceId"] |  | ||||||
|             == ACCOUNT_ID |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         for region in Session().get_available_regions("s3control"): |  | ||||||
|             result = config_client.batch_get_aggregate_resource_config( |  | ||||||
|                 ConfigurationAggregatorName="testing", |  | ||||||
|                 ResourceIdentifiers=[ |  | ||||||
|                     { |  | ||||||
|                         "SourceAccountId": ACCOUNT_ID, |  | ||||||
|                         "SourceRegion": region, |  | ||||||
|                         "ResourceId": ACCOUNT_ID, |  | ||||||
|                         "ResourceType": "AWS::S3::AccountPublicAccessBlock", |  | ||||||
|                         "ResourceName": "", |  | ||||||
|                     } |  | ||||||
|                 ], |  | ||||||
|             ) |  | ||||||
|             assert len(result["BaseConfigurationItems"]) == 1 |  | ||||||
|             assert ( |  | ||||||
|                 json.loads(result["BaseConfigurationItems"][0]["configuration"]) |  | ||||||
|                 == proper_config |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| @mock_s3 | @mock_s3 | ||||||
| def test_ranged_get_boto3(): | def test_ranged_get_boto3(): | ||||||
|  | |||||||
							
								
								
									
										0
									
								
								tests/test_s3control/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/test_s3control/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										90
									
								
								tests/test_s3control/test_s3control.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								tests/test_s3control/test_s3control.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | |||||||
|  | import boto3 | ||||||
|  | import pytest | ||||||
|  | import sure  # noqa # pylint: disable=unused-import | ||||||
|  | 
 | ||||||
|  | from boto3 import Session | ||||||
|  | from botocore.client import ClientError | ||||||
|  | from moto import settings, mock_s3control | ||||||
|  | 
 | ||||||
|  | # All tests for s3-control cannot be run under the server without a modification of the | ||||||
|  | # hosts file on your system. This is due to the fact that the URL to the host is in the form of: | ||||||
|  | # ACCOUNT_ID.s3-control.amazonaws.com <-- That Account ID part is the problem. If you want to | ||||||
|  | # make use of the moto server, update your hosts file for `THE_ACCOUNT_ID_FOR_MOTO.localhost` | ||||||
|  | # and this will work fine. | ||||||
|  | 
 | ||||||
|  | if not settings.TEST_SERVER_MODE: | ||||||
|  | 
 | ||||||
|  |     @mock_s3control | ||||||
|  |     def test_get_public_access_block_for_account(): | ||||||
|  |         from moto.s3.models import ACCOUNT_ID | ||||||
|  | 
 | ||||||
|  |         client = boto3.client("s3control", region_name="us-west-2") | ||||||
|  | 
 | ||||||
|  |         # With an invalid account ID: | ||||||
|  |         with pytest.raises(ClientError) as ce: | ||||||
|  |             client.get_public_access_block(AccountId="111111111111") | ||||||
|  |         assert ce.value.response["Error"]["Code"] == "AccessDenied" | ||||||
|  | 
 | ||||||
|  |         # Without one defined: | ||||||
|  |         with pytest.raises(ClientError) as ce: | ||||||
|  |             client.get_public_access_block(AccountId=ACCOUNT_ID) | ||||||
|  |         assert ( | ||||||
|  |             ce.value.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration" | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Put a with an invalid account ID: | ||||||
|  |         with pytest.raises(ClientError) as ce: | ||||||
|  |             client.put_public_access_block( | ||||||
|  |                 AccountId="111111111111", | ||||||
|  |                 PublicAccessBlockConfiguration={"BlockPublicAcls": True}, | ||||||
|  |             ) | ||||||
|  |         assert ce.value.response["Error"]["Code"] == "AccessDenied" | ||||||
|  | 
 | ||||||
|  |         # Put with an invalid PAB: | ||||||
|  |         with pytest.raises(ClientError) as ce: | ||||||
|  |             client.put_public_access_block( | ||||||
|  |                 AccountId=ACCOUNT_ID, PublicAccessBlockConfiguration={} | ||||||
|  |             ) | ||||||
|  |         assert ce.value.response["Error"]["Code"] == "InvalidRequest" | ||||||
|  |         assert ( | ||||||
|  |             "Must specify at least one configuration." | ||||||
|  |             in ce.value.response["Error"]["Message"] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Correct PAB: | ||||||
|  |         client.put_public_access_block( | ||||||
|  |             AccountId=ACCOUNT_ID, | ||||||
|  |             PublicAccessBlockConfiguration={ | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": True, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": True, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Get the correct PAB (for all regions): | ||||||
|  |         for region in Session().get_available_regions("s3control"): | ||||||
|  |             region_client = boto3.client("s3control", region_name=region) | ||||||
|  |             assert region_client.get_public_access_block(AccountId=ACCOUNT_ID)[ | ||||||
|  |                 "PublicAccessBlockConfiguration" | ||||||
|  |             ] == { | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": True, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": True, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         # Delete with an invalid account ID: | ||||||
|  |         with pytest.raises(ClientError) as ce: | ||||||
|  |             client.delete_public_access_block(AccountId="111111111111") | ||||||
|  |         assert ce.value.response["Error"]["Code"] == "AccessDenied" | ||||||
|  | 
 | ||||||
|  |         # Delete successfully: | ||||||
|  |         client.delete_public_access_block(AccountId=ACCOUNT_ID) | ||||||
|  | 
 | ||||||
|  |         # Confirm that it's deleted: | ||||||
|  |         with pytest.raises(ClientError) as ce: | ||||||
|  |             client.get_public_access_block(AccountId=ACCOUNT_ID) | ||||||
|  |         assert ( | ||||||
|  |             ce.value.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration" | ||||||
|  |         ) | ||||||
							
								
								
									
										305
									
								
								tests/test_s3control/test_s3control_config_integration.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								tests/test_s3control/test_s3control_config_integration.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,305 @@ | |||||||
|  | import boto3 | ||||||
|  | import json | ||||||
|  | import pytest | ||||||
|  | import sure  # noqa # pylint: disable=unused-import | ||||||
|  | 
 | ||||||
|  | from boto3 import Session | ||||||
|  | from botocore.client import ClientError | ||||||
|  | from moto import settings, mock_s3control, mock_config | ||||||
|  | 
 | ||||||
|  | # All tests for s3-control cannot be run under the server without a modification of the | ||||||
|  | # hosts file on your system. This is due to the fact that the URL to the host is in the form of: | ||||||
|  | # ACCOUNT_ID.s3-control.amazonaws.com <-- That Account ID part is the problem. If you want to | ||||||
|  | # make use of the moto server, update your hosts file for `THE_ACCOUNT_ID_FOR_MOTO.localhost` | ||||||
|  | # and this will work fine. | ||||||
|  | 
 | ||||||
|  | if not settings.TEST_SERVER_MODE: | ||||||
|  | 
 | ||||||
|  |     @mock_s3control | ||||||
|  |     @mock_config | ||||||
|  |     def test_config_list_account_pab(): | ||||||
|  |         from moto.s3.models import ACCOUNT_ID | ||||||
|  | 
 | ||||||
|  |         client = boto3.client("s3control", region_name="us-west-2") | ||||||
|  |         config_client = boto3.client("config", region_name="us-west-2") | ||||||
|  | 
 | ||||||
|  |         # Create the aggregator: | ||||||
|  |         account_aggregation_source = { | ||||||
|  |             "AccountIds": [ACCOUNT_ID], | ||||||
|  |             "AllAwsRegions": True, | ||||||
|  |         } | ||||||
|  |         config_client.put_configuration_aggregator( | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             AccountAggregationSources=[account_aggregation_source], | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Without a PAB in place: | ||||||
|  |         result = config_client.list_discovered_resources( | ||||||
|  |             resourceType="AWS::S3::AccountPublicAccessBlock" | ||||||
|  |         ) | ||||||
|  |         assert not result["resourceIdentifiers"] | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |         ) | ||||||
|  |         assert not result["ResourceIdentifiers"] | ||||||
|  | 
 | ||||||
|  |         # Create a PAB: | ||||||
|  |         client.put_public_access_block( | ||||||
|  |             AccountId=ACCOUNT_ID, | ||||||
|  |             PublicAccessBlockConfiguration={ | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": True, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": True, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Test that successful queries work (non-aggregated): | ||||||
|  |         result = config_client.list_discovered_resources( | ||||||
|  |             resourceType="AWS::S3::AccountPublicAccessBlock" | ||||||
|  |         ) | ||||||
|  |         assert result["resourceIdentifiers"] == [ | ||||||
|  |             { | ||||||
|  |                 "resourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |                 "resourceId": ACCOUNT_ID, | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |         result = config_client.list_discovered_resources( | ||||||
|  |             resourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             resourceIds=[ACCOUNT_ID, "nope"], | ||||||
|  |         ) | ||||||
|  |         assert result["resourceIdentifiers"] == [ | ||||||
|  |             { | ||||||
|  |                 "resourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |                 "resourceId": ACCOUNT_ID, | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |         result = config_client.list_discovered_resources( | ||||||
|  |             resourceType="AWS::S3::AccountPublicAccessBlock", resourceName="" | ||||||
|  |         ) | ||||||
|  |         assert result["resourceIdentifiers"] == [ | ||||||
|  |             { | ||||||
|  |                 "resourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |                 "resourceId": ACCOUNT_ID, | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |         # Test that successful queries work (aggregated): | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |         ) | ||||||
|  |         regions = {region for region in Session().get_available_regions("config")} | ||||||
|  |         for r in result["ResourceIdentifiers"]: | ||||||
|  |             regions.remove(r.pop("SourceRegion")) | ||||||
|  |             assert r == { | ||||||
|  |                 "ResourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |                 "SourceAccountId": ACCOUNT_ID, | ||||||
|  |                 "ResourceId": ACCOUNT_ID, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         # Just check that the len is the same -- this should be reasonable | ||||||
|  |         regions = {region for region in Session().get_available_regions("config")} | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             Filters={"ResourceName": ""}, | ||||||
|  |         ) | ||||||
|  |         assert len(regions) == len(result["ResourceIdentifiers"]) | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             Filters={"ResourceName": "", "ResourceId": ACCOUNT_ID}, | ||||||
|  |         ) | ||||||
|  |         assert len(regions) == len(result["ResourceIdentifiers"]) | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             Filters={ | ||||||
|  |                 "ResourceName": "", | ||||||
|  |                 "ResourceId": ACCOUNT_ID, | ||||||
|  |                 "Region": "us-west-2", | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         assert ( | ||||||
|  |             result["ResourceIdentifiers"][0]["SourceRegion"] == "us-west-2" | ||||||
|  |             and len(result["ResourceIdentifiers"]) == 1 | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Test aggregator pagination: | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             Limit=1, | ||||||
|  |         ) | ||||||
|  |         regions = sorted( | ||||||
|  |             [region for region in Session().get_available_regions("config")] | ||||||
|  |         ) | ||||||
|  |         assert result["ResourceIdentifiers"][0] == { | ||||||
|  |             "ResourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             "SourceAccountId": ACCOUNT_ID, | ||||||
|  |             "ResourceId": ACCOUNT_ID, | ||||||
|  |             "SourceRegion": regions[0], | ||||||
|  |         } | ||||||
|  |         assert result["NextToken"] == regions[1] | ||||||
|  | 
 | ||||||
|  |         # Get the next region: | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             Limit=1, | ||||||
|  |             NextToken=regions[1], | ||||||
|  |         ) | ||||||
|  |         assert result["ResourceIdentifiers"][0] == { | ||||||
|  |             "ResourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             "SourceAccountId": ACCOUNT_ID, | ||||||
|  |             "ResourceId": ACCOUNT_ID, | ||||||
|  |             "SourceRegion": regions[1], | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         # Non-aggregated with incorrect info: | ||||||
|  |         result = config_client.list_discovered_resources( | ||||||
|  |             resourceType="AWS::S3::AccountPublicAccessBlock", resourceName="nope" | ||||||
|  |         ) | ||||||
|  |         assert not result["resourceIdentifiers"] | ||||||
|  |         result = config_client.list_discovered_resources( | ||||||
|  |             resourceType="AWS::S3::AccountPublicAccessBlock", resourceIds=["nope"] | ||||||
|  |         ) | ||||||
|  |         assert not result["resourceIdentifiers"] | ||||||
|  | 
 | ||||||
|  |         # Aggregated with incorrect info: | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             Filters={"ResourceName": "nope"}, | ||||||
|  |         ) | ||||||
|  |         assert not result["ResourceIdentifiers"] | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             Filters={"ResourceId": "nope"}, | ||||||
|  |         ) | ||||||
|  |         assert not result["ResourceIdentifiers"] | ||||||
|  |         result = config_client.list_aggregate_discovered_resources( | ||||||
|  |             ResourceType="AWS::S3::AccountPublicAccessBlock", | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             Filters={"Region": "Nope"}, | ||||||
|  |         ) | ||||||
|  |         assert not result["ResourceIdentifiers"] | ||||||
|  | 
 | ||||||
|  |     @mock_s3control | ||||||
|  |     @mock_config | ||||||
|  |     def test_config_get_account_pab(): | ||||||
|  |         from moto.s3.models import ACCOUNT_ID | ||||||
|  | 
 | ||||||
|  |         client = boto3.client("s3control", region_name="us-west-2") | ||||||
|  |         config_client = boto3.client("config", region_name="us-west-2") | ||||||
|  | 
 | ||||||
|  |         # Create the aggregator: | ||||||
|  |         account_aggregation_source = { | ||||||
|  |             "AccountIds": [ACCOUNT_ID], | ||||||
|  |             "AllAwsRegions": True, | ||||||
|  |         } | ||||||
|  |         config_client.put_configuration_aggregator( | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             AccountAggregationSources=[account_aggregation_source], | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Without a PAB in place: | ||||||
|  |         with pytest.raises(ClientError) as ce: | ||||||
|  |             config_client.get_resource_config_history( | ||||||
|  |                 resourceType="AWS::S3::AccountPublicAccessBlock", resourceId=ACCOUNT_ID | ||||||
|  |             ) | ||||||
|  |         assert ce.value.response["Error"]["Code"] == "ResourceNotDiscoveredException" | ||||||
|  |         # aggregate | ||||||
|  |         result = config_client.batch_get_resource_config( | ||||||
|  |             resourceKeys=[ | ||||||
|  |                 { | ||||||
|  |                     "resourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |                     "resourceId": "ACCOUNT_ID", | ||||||
|  |                 } | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         assert not result["baseConfigurationItems"] | ||||||
|  |         result = config_client.batch_get_aggregate_resource_config( | ||||||
|  |             ConfigurationAggregatorName="testing", | ||||||
|  |             ResourceIdentifiers=[ | ||||||
|  |                 { | ||||||
|  |                     "SourceAccountId": ACCOUNT_ID, | ||||||
|  |                     "SourceRegion": "us-west-2", | ||||||
|  |                     "ResourceId": ACCOUNT_ID, | ||||||
|  |                     "ResourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |                     "ResourceName": "", | ||||||
|  |                 } | ||||||
|  |             ], | ||||||
|  |         ) | ||||||
|  |         assert not result["BaseConfigurationItems"] | ||||||
|  | 
 | ||||||
|  |         # Create a PAB: | ||||||
|  |         client.put_public_access_block( | ||||||
|  |             AccountId=ACCOUNT_ID, | ||||||
|  |             PublicAccessBlockConfiguration={ | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": True, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": True, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Get the proper config: | ||||||
|  |         proper_config = { | ||||||
|  |             "blockPublicAcls": True, | ||||||
|  |             "ignorePublicAcls": True, | ||||||
|  |             "blockPublicPolicy": True, | ||||||
|  |             "restrictPublicBuckets": True, | ||||||
|  |         } | ||||||
|  |         result = config_client.get_resource_config_history( | ||||||
|  |             resourceType="AWS::S3::AccountPublicAccessBlock", resourceId=ACCOUNT_ID | ||||||
|  |         ) | ||||||
|  |         assert ( | ||||||
|  |             json.loads(result["configurationItems"][0]["configuration"]) | ||||||
|  |             == proper_config | ||||||
|  |         ) | ||||||
|  |         assert ( | ||||||
|  |             result["configurationItems"][0]["accountId"] | ||||||
|  |             == result["configurationItems"][0]["resourceId"] | ||||||
|  |             == ACCOUNT_ID | ||||||
|  |         ) | ||||||
|  |         result = config_client.batch_get_resource_config( | ||||||
|  |             resourceKeys=[ | ||||||
|  |                 { | ||||||
|  |                     "resourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |                     "resourceId": ACCOUNT_ID, | ||||||
|  |                 } | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         assert len(result["baseConfigurationItems"]) == 1 | ||||||
|  |         assert ( | ||||||
|  |             json.loads(result["baseConfigurationItems"][0]["configuration"]) | ||||||
|  |             == proper_config | ||||||
|  |         ) | ||||||
|  |         assert ( | ||||||
|  |             result["baseConfigurationItems"][0]["accountId"] | ||||||
|  |             == result["baseConfigurationItems"][0]["resourceId"] | ||||||
|  |             == ACCOUNT_ID | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         for region in Session().get_available_regions("s3control"): | ||||||
|  |             result = config_client.batch_get_aggregate_resource_config( | ||||||
|  |                 ConfigurationAggregatorName="testing", | ||||||
|  |                 ResourceIdentifiers=[ | ||||||
|  |                     { | ||||||
|  |                         "SourceAccountId": ACCOUNT_ID, | ||||||
|  |                         "SourceRegion": region, | ||||||
|  |                         "ResourceId": ACCOUNT_ID, | ||||||
|  |                         "ResourceType": "AWS::S3::AccountPublicAccessBlock", | ||||||
|  |                         "ResourceName": "", | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |             ) | ||||||
|  |             assert len(result["BaseConfigurationItems"]) == 1 | ||||||
|  |             assert ( | ||||||
|  |                 json.loads(result["BaseConfigurationItems"][0]["configuration"]) | ||||||
|  |                 == proper_config | ||||||
|  |             ) | ||||||
							
								
								
									
										103
									
								
								tests/test_s3control/test_s3control_s3.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								tests/test_s3control/test_s3control_s3.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | |||||||
|  | """Test that using both s3 and s3control do not interfere""" | ||||||
|  | import boto3 | ||||||
|  | import sure  # noqa # pylint: disable=unused-import | ||||||
|  | 
 | ||||||
|  | from moto import mock_s3, mock_s3control, settings | ||||||
|  | from moto.core import ACCOUNT_ID | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if not settings.TEST_SERVER_MODE: | ||||||
|  | 
 | ||||||
|  |     @mock_s3 | ||||||
|  |     @mock_s3control | ||||||
|  |     def test_pab_are_kept_separate(): | ||||||
|  |         client = boto3.client("s3control", region_name="us-east-1") | ||||||
|  |         s3 = boto3.client("s3", region_name="us-east-1") | ||||||
|  |         s3.create_bucket(Bucket="bucket") | ||||||
|  | 
 | ||||||
|  |         client.put_public_access_block( | ||||||
|  |             AccountId=ACCOUNT_ID, | ||||||
|  |             PublicAccessBlockConfiguration={ | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": True, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": True, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         s3.put_public_access_block( | ||||||
|  |             Bucket="bucket", | ||||||
|  |             PublicAccessBlockConfiguration={ | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": False, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": False, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         pab_from_control = client.get_public_access_block(AccountId=ACCOUNT_ID) | ||||||
|  |         pab_from_control.should.have.key("PublicAccessBlockConfiguration").equals( | ||||||
|  |             { | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": True, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": True, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         pab_from_s3 = s3.get_public_access_block(Bucket="bucket") | ||||||
|  |         pab_from_s3.should.have.key("PublicAccessBlockConfiguration").equals( | ||||||
|  |             { | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": False, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": False, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     @mock_s3control | ||||||
|  |     @mock_s3 | ||||||
|  |     def test_pab_are_kept_separate_with_inverse_mocks(): | ||||||
|  |         client = boto3.client("s3control", region_name="us-east-1") | ||||||
|  |         s3 = boto3.client("s3", region_name="us-east-1") | ||||||
|  |         s3.create_bucket(Bucket="bucket") | ||||||
|  | 
 | ||||||
|  |         client.put_public_access_block( | ||||||
|  |             AccountId=ACCOUNT_ID, | ||||||
|  |             PublicAccessBlockConfiguration={ | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": True, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": True, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         s3.put_public_access_block( | ||||||
|  |             Bucket="bucket", | ||||||
|  |             PublicAccessBlockConfiguration={ | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": False, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": False, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         pab_from_control = client.get_public_access_block(AccountId=ACCOUNT_ID) | ||||||
|  |         pab_from_control.should.have.key("PublicAccessBlockConfiguration").equals( | ||||||
|  |             { | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": True, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": True, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         pab_from_s3 = s3.get_public_access_block(Bucket="bucket") | ||||||
|  |         pab_from_s3.should.have.key("PublicAccessBlockConfiguration").equals( | ||||||
|  |             { | ||||||
|  |                 "BlockPublicAcls": True, | ||||||
|  |                 "IgnorePublicAcls": False, | ||||||
|  |                 "BlockPublicPolicy": True, | ||||||
|  |                 "RestrictPublicBuckets": False, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user