Skip to content

Commit

Permalink
Fix FIPS and global endpoint behavior for S3 ARNs
Browse files Browse the repository at this point in the history
  • Loading branch information
Illia Batozskyi committed Apr 30, 2021
1 parent e6b2581 commit 1b6b95e
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 27 deletions.
54 changes: 52 additions & 2 deletions botocore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,8 @@ def update_endpoint_to_s3_object_lambda(self, params, context, **kwargs):
def set_endpoint(self, request, **kwargs):
if self._use_accesspoint_endpoint(request):
self._validate_accesspoint_supported(request)
self._validate_fips_supported(request)
self._validate_global_regions(request)
region_name = self._resolve_region_for_accesspoint_endpoint(
request)
self._resolve_signing_name_for_accesspoint_endpoint(
Expand All @@ -1569,6 +1571,45 @@ def set_endpoint(self, request, **kwargs):
def _use_accesspoint_endpoint(self, request):
return 's3_accesspoint' in request.context

def _validate_fips_supported(self, request):
if 'fips' not in self._region:
return
if 'outpost_name' in request.context['s3_accesspoint']:
raise UnsupportedS3AccesspointConfigurationError(
msg=(
'Invalid configuration Outpost Access Points '
'do not support FIPS- regions'
)
)
client_region = self._region.replace('fips-', '').replace('-fips', '')
accesspoint_region = request.context['s3_accesspoint']['region']
if accesspoint_region != client_region:
if self._s3_config.get('use_arn_region'):
raise UnsupportedS3AccesspointConfigurationError(
msg=(
'Invalid configuration, '
'FIPS region %s does not match ARN region %s' %
(self._region, accesspoint_region)
)
)
else:
raise UnsupportedS3AccesspointConfigurationError(
msg=(
'Invalid configuration, cross region Access Point ARN'
)
)

def _validate_global_regions(self, request):
if self._s3_config.get('use_arn_region'):
return
if self._region in ['aws-global', 's3-external-1']:
raise UnsupportedS3AccesspointConfigurationError(
msg=(
'Invalid configuration, client region '
'is not a regional endpoint'
)
)

def _validate_accesspoint_supported(self, request):
if self._use_accelerate_endpoint:
raise UnsupportedS3AccesspointConfigurationError(
Expand Down Expand Up @@ -1648,9 +1689,13 @@ def _get_accesspoint_netloc(self, request_context, region_name):
outpost_host = [outpost_name, 's3-outposts']
accesspoint_netloc_components.extend(outpost_host)
elif s3_accesspoint['service'] == 's3-object-lambda':
accesspoint_netloc_components.append('s3-object-lambda')
component = self._inject_fips_if_needed(
's3-object-lambda', request_context)
accesspoint_netloc_components.append(component)
else:
accesspoint_netloc_components.append('s3-accesspoint')
component = self._inject_fips_if_needed(
's3-accesspoint', request_context)
accesspoint_netloc_components.append(component)
if self._s3_config.get('use_dualstack_endpoint'):
accesspoint_netloc_components.append('dualstack')
accesspoint_netloc_components.extend(
Expand All @@ -1661,6 +1706,11 @@ def _get_accesspoint_netloc(self, request_context, region_name):
)
return '.'.join(accesspoint_netloc_components)

def _inject_fips_if_needed(self, component, request_context):
if 'fips' in request_context.get('client_region', ''):
return '%s-fips' % component
return component

def _get_accesspoint_path(self, original_path, request_context):
# The Bucket parameter was substituted with the access-point name as
# some value was required in serializing the bucket name. Now that
Expand Down
172 changes: 147 additions & 25 deletions tests/functional/test_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,47 @@ def test_s3_object_lambda_arn_with_s3_dualstack(self):
with self.assertRaises(UnsupportedS3AccesspointConfigurationError):
self.client.list_objects(Bucket=s3_object_lambda_arn)

def test_s3_object_lambda_fips_raise_for_cross_region(self):
s3_object_lambda_arn = (
'arn:aws-us-gov:s3-object-lambda:us-gov-east-1:123456789012:'
'accesspoint/mybanner'
)
self.client, _ = self.create_stubbed_s3_client(
region_name='fips-us-gov-west-1')
expected_exception = UnsupportedS3AccesspointConfigurationError
with self.assertRaisesRegexp(expected_exception,
'cross region Access Point'):
self.client.list_objects(Bucket=s3_object_lambda_arn)

self.client, _ = self.create_stubbed_s3_client(
region_name='fips-us-gov-west-1',
config=Config(s3={'use_arn_region': True})
)
expected_exception = UnsupportedS3AccesspointConfigurationError
with self.assertRaisesRegexp(
expected_exception,
'FIPS region fips-us-gov-west-1 does not match'):
self.client.list_objects(Bucket=s3_object_lambda_arn)

def test_s3_object_lambda_with_global_regions(self):
s3_object_lambda_arn = (
'arn:aws:s3-object-lambda:us-east-1:123456789012:'
'accesspoint/mybanner'
)
self.client, _ = self.create_stubbed_s3_client(
region_name='s3-external-1')
expected_exception = UnsupportedS3AccesspointConfigurationError
with self.assertRaisesRegexp(expected_exception,
'is not a regional endpoint'):
self.client.list_objects(Bucket=s3_object_lambda_arn)
self.client, _ = self.create_stubbed_s3_client(
region_name='aws-global')
expected_exception = UnsupportedS3AccesspointConfigurationError
with self.assertRaisesRegexp(expected_exception,
'is not a regional endpoint'):
self.client.list_objects(Bucket=s3_object_lambda_arn)


def test_s3_object_lambda_arn_with_us_east_1(self):
# test that us-east-1 region is not resolved
# into s3 global endpoint
Expand Down Expand Up @@ -890,6 +931,66 @@ def test_basic_s3_object_lambda_arn(self):
)
self.assert_endpoint(request, expected_endpoint)

def test_outposts_raise_exception_if_fips_region(self):
outpost_arn = (
'arn:aws:s3-outposts:us-gov-wast-1:123456789012:outpost:'
'op-01234567890123456:accesspoint:myaccesspoint'
)
self.client, _ = self.create_stubbed_s3_client(
region_name='us-gov-wast-1-fips')
expected_exception = UnsupportedS3AccesspointConfigurationError
with self.assertRaisesRegexp(expected_exception,
'Outpost Access Points do not support'):
self.client.list_objects(Bucket=outpost_arn)

def test_accesspoint_fips_raise_for_cross_region(self):
s3_accesspoint_arn = (
'arn:aws-us-gov:s3:us-gov-east-1:123456789012:'
'accesspoint:myendpoint'
)
self.client, _ = self.create_stubbed_s3_client(
region_name='fips-us-gov-west-1')
expected_exception = UnsupportedS3AccesspointConfigurationError
with self.assertRaisesRegexp(expected_exception,
'cross region Access Point'):
self.client.list_objects(Bucket=s3_accesspoint_arn)

self.client, _ = self.create_stubbed_s3_client(
region_name='fips-us-gov-west-1',
config=Config(s3={'use_arn_region': True})
)
expected_exception = UnsupportedS3AccesspointConfigurationError
with self.assertRaisesRegexp(
expected_exception,
'FIPS region fips-us-gov-west-1 does not match'):
self.client.list_objects(Bucket=s3_accesspoint_arn)

def test_accesspoint_with_global_regions(self):
s3_accesspoint_arn = (
'arn:aws:s3:us-east-1:123456789012:accesspoint:myendpoint'
)
self.client, _ = self.create_stubbed_s3_client(
region_name='aws-global')
expected_exception = UnsupportedS3AccesspointConfigurationError
with self.assertRaisesRegexp(expected_exception,
'is not a regional endpoint'):
self.client.list_objects(Bucket=s3_accesspoint_arn)

# in shouldn't raise if use_arn_region is True
self.client, self.http_stubber = self.create_stubbed_s3_client(
region_name='s3-external-1',
config=Config(s3={'use_arn_region': True})
)

self.http_stubber.add_response()
self.client.list_objects(Bucket=s3_accesspoint_arn)
request = self.http_stubber.requests[0]
expected_endpoint = (
'myendpoint-123456789012.s3-accesspoint.'
'us-east-1.amazonaws.com'
)
self.assert_endpoint(request, expected_endpoint)


class TestOnlyAsciiCharsAllowed(BaseS3OperationTest):
def test_validates_non_ascii_chars_trigger_validation_error(self):
Expand Down Expand Up @@ -1906,34 +2007,21 @@ def test_correct_url_used_for_s3():
)
yield t.case(
region='s3-external-1', bucket=accesspoint_arn, key='key',
s3_config={'use_arn_region': True},
expected_url=(
'https://myendpoint-123456789012.s3-accesspoint.'
'us-west-2.amazonaws.com/key'
)
)
yield t.case(
region='s3-external-1', bucket=accesspoint_arn, key='key',
s3_config={'use_arn_region': False},
expected_url=(
'https://myendpoint-123456789012.s3-accesspoint.'
's3-external-1.amazonaws.com/key'
)
)

yield t.case(
region='aws-global', bucket=accesspoint_arn, key='key',
s3_config={'use_arn_region': True},
expected_url=(
'https://myendpoint-123456789012.s3-accesspoint.'
'us-west-2.amazonaws.com/key'
)
)
yield t.case(
region='aws-global', bucket=accesspoint_arn, key='key',
s3_config={'use_arn_region': False},
expected_url=(
'https://myendpoint-123456789012.s3-accesspoint.'
'aws-global.amazonaws.com/key'
)
)
yield t.case(
region='unknown', bucket=accesspoint_arn, key='key',
s3_config={'use_arn_region': False},
Expand Down Expand Up @@ -1976,27 +2064,27 @@ def test_correct_url_used_for_s3():
)
)
accesspoint_arn_gov = (
'arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:myendpoint'
'arn:aws-us-gov:s3:us-gov-west-1:123456789012:accesspoint:myendpoint'
)
yield t.case(
region='us-gov-east-1', bucket=accesspoint_arn_gov, key='key',
region='us-gov-west-1', bucket=accesspoint_arn_gov, key='key',
expected_url=(
'https://myendpoint-123456789012.s3-accesspoint.'
'us-gov-east-1.amazonaws.com/key'
'us-gov-west-1.amazonaws.com/key'
)
)
yield t.case(
region='fips-us-gov-west-1', bucket=accesspoint_arn_gov, key='key',
expected_url=(
'https://myendpoint-123456789012.s3-accesspoint.'
'us-gov-east-1.amazonaws.com/key'
'https://myendpoint-123456789012.s3-accesspoint-fips.'
'us-gov-west-1.amazonaws.com/key'
)
)
yield t.case(
region='fips-us-gov-west-1', bucket=accesspoint_arn_gov, key='key',
s3_config={'use_arn_region': False},
expected_url=(
'https://myendpoint-123456789012.s3-accesspoint.'
'https://myendpoint-123456789012.s3-accesspoint-fips.'
'fips-us-gov-west-1.amazonaws.com/key'
)
)
Expand Down Expand Up @@ -2033,16 +2121,26 @@ def test_correct_url_used_for_s3():
)
)
yield t.case(
region='us-gov-east-1', bucket=accesspoint_arn_gov, key='key',
region='us-gov-west-1', bucket=accesspoint_arn_gov, key='key',
s3_config={
'use_dualstack_endpoint': True,
},
expected_url=(
'https://myendpoint-123456789012.s3-accesspoint.dualstack.'
'us-gov-east-1.amazonaws.com/key'
'us-gov-west-1.amazonaws.com/key'
)
)
yield t.case(
region='fips-us-gov-west-1', bucket=accesspoint_arn_gov, key='key',
s3_config={
'use_arn_region': True,
'use_dualstack_endpoint': True,
},
expected_url=(
'https://myendpoint-123456789012.s3-accesspoint-fips.dualstack.'
'us-gov-west-1.amazonaws.com/key'
)
)

# None of the various s3 settings related to paths should affect what
# endpoint to use when an access-point is provided.
yield t.case(
Expand Down Expand Up @@ -2142,6 +2240,30 @@ def test_correct_url_used_for_s3():
expected_url=(
'https://bucket.s3.unknown.amazonaws.com/key'))

s3_object_lambda_arn_gov = (
'arn:aws-us-gov:s3-object-lambda:us-gov-west-1:'
'123456789012:accesspoint:mybanner'
)
yield t.case(
region='fips-us-gov-west-1', bucket=s3_object_lambda_arn_gov, key='key',
expected_url=(
'https://mybanner-123456789012.s3-object-lambda-fips.'
'us-gov-west-1.amazonaws.com/key'
)
)
s3_object_lambda_arn = (
'arn:aws:s3-object-lambda:us-east-1:'
'123456789012:accesspoint:mybanner'
)
yield t.case(
region='aws-global', bucket=s3_object_lambda_arn, key='key',
s3_config={'use_arn_region': True},
expected_url=(
'https://mybanner-123456789012.s3-object-lambda.'
'us-east-1.amazonaws.com/key'
)
)


class BaseTestCase:
def __init__(self, verify_function):
Expand Down

0 comments on commit 1b6b95e

Please sign in to comment.