Skip to content

Commit 77d07b7

Browse files
authored
Prefer signature version from service models over endpoints.json (aws#9492)
1 parent 5f8f1a3 commit 77d07b7

4 files changed

Lines changed: 72 additions & 34 deletions

File tree

awscli/botocore/client.py

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,6 @@
6262
get_service_module_name,
6363
)
6464

65-
_LEGACY_SIGNATURE_VERSIONS = frozenset(
66-
(
67-
'v2',
68-
'v3',
69-
'v3https',
70-
'v4',
71-
's3',
72-
's3v4',
73-
)
74-
)
75-
76-
7765
logger = logging.getLogger(__name__)
7866
history_recorder = get_global_history_recorder()
7967

@@ -174,6 +162,7 @@ def create_client(
174162
self._register_s3_events(service_client, client_config, scoped_config)
175163
self._register_s3express_events(client=service_client)
176164
self._register_s3_control_events(service_client)
165+
self._register_importexport_events(client=service_client)
177166
self._register_endpoint_discovery(
178167
service_client, endpoint_url, client_config
179168
)
@@ -332,6 +321,42 @@ def _register_s3_control_events(self, client):
332321
return
333322
S3ControlArnParamHandlerv2().register(client.meta.events)
334323

324+
def _register_importexport_events(
325+
self,
326+
client,
327+
endpoint_bridge=None,
328+
endpoint_url=None,
329+
client_config=None,
330+
scoped_config=None,
331+
):
332+
if client.meta.service_model.service_name != 'importexport':
333+
return
334+
self._set_importexport_signature_version(
335+
client.meta, client_config, scoped_config
336+
)
337+
338+
def _set_importexport_signature_version(
339+
self, client_meta, client_config, scoped_config
340+
):
341+
# This will return the manually configured signature version, or None
342+
# if none was manually set. If a customer manually sets the signature
343+
# version, we always want to use what they set.
344+
configured_signature_version = _get_configured_signature_version(
345+
'importexport', client_config, scoped_config
346+
)
347+
if configured_signature_version is not None:
348+
return
349+
350+
# importexport has a modeled signatureVersion of v2, but we
351+
# previously switched to v4 via endpoint.json before endpoint rulesets.
352+
# Override the model's signatureVersion for backwards compatability.
353+
client_meta.events.register(
354+
'choose-signer.importexport', self._default_signer_to_sigv4
355+
)
356+
357+
def _default_signer_to_sigv4(self, signature_version, **kwargs):
358+
return 'v4'
359+
335360
def _get_client_args(
336361
self,
337362
service_model,
@@ -669,27 +694,28 @@ def _resolve_signature_version(self, service_name, resolved):
669694
if configured_version is not None:
670695
return configured_version
671696

672-
potential_versions = resolved.get('signatureVersions', [])
673-
if (
674-
self.service_signature_version is not None
675-
and self.service_signature_version
676-
not in _LEGACY_SIGNATURE_VERSIONS
677-
):
678-
# Prefer the service model as most specific
679-
# source of truth for new signature versions.
680-
potential_versions = [self.service_signature_version]
697+
# These have since added the "auth" key to the service model
698+
# with "aws.auth#sigv4", but preserve existing behavior from
699+
# when we preferred endpoints.json over the service models
700+
if service_name in ('s3', 's3-control'):
701+
return 's3v4'
681702

682-
# Pick a signature version from the endpoint metadata if present.
683-
if 'signatureVersions' in resolved:
684-
if service_name == 's3':
685-
return 's3v4'
703+
if self.service_signature_version is not None:
704+
# Prefer the service model
705+
potential_versions = [self.service_signature_version]
706+
else:
707+
# Fall back to endpoints.json to preserve existing behavior, which
708+
# may be useful for users who have custom service models
709+
potential_versions = resolved.get('signatureVersions', [])
710+
# This was added for the V2 -> V4 transition,
711+
# for services that added V4 after V2 in endpoints.json
686712
if 'v4' in potential_versions:
687713
return 'v4'
688-
# Now just iterate over the signature versions in order until we
689-
# find the first one that is known to Botocore.
690-
for known in potential_versions:
691-
if known in AUTH_TYPE_MAPS:
692-
return known
714+
# Now just iterate over the signature versions in order until we
715+
# find the first one that is known to Botocore.
716+
for known in potential_versions:
717+
if known in AUTH_TYPE_MAPS:
718+
return known
693719
raise UnknownSignatureVersionError(
694720
signature_version=potential_versions
695721
)

tests/functional/botocore/test_endpoint_rulesets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def get_endpoint_tests_for_service(service_name):
5454
if not file_path.is_file():
5555
raise FileNotFoundError(
5656
f'Cannot find endpoint tests file for "{service_name}" at '
57-
'path {file_path}'
57+
f'path {file_path}'
5858
)
5959
with file_path.open('r') as f:
6060
return json.load(f)

tests/functional/botocore/test_importexport.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ def test_importexport_signature_version(self):
3939
b"</CancelJobOutput>"
4040
)
4141

42-
# Confirm we've ignored the model signatureVersion and chosen v4
43-
assert client.meta.config.signature_version == "v4"
42+
# Note that client.meta.config.signature_version still returns "v2"
43+
# since the "choose-signer" hook doesn't run until later
4444
with stubber:
4545
stubber.add_response(body=importexport_response)
4646
client.cancel_job(JobId="12345")
4747

48-
# Validate we actually signed with sigv4
48+
# So validate we actually signed with sigv4
4949
auth_header = stubber.requests[0].headers.get('Authorization', '')
5050
assert auth_header.startswith(b"AWS4-HMAC-SHA256")
5151
assert b"aws4_request" in auth_header

tests/unit/botocore/test_client.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2108,6 +2108,18 @@ def test_raises_when_signature_version_is_not_found(self):
21082108
with self.assertRaises(UnknownSignatureVersionError):
21092109
bridge.resolve('test', 'us-west-2')
21102110

2111+
def test_prefers_signature_version_from_service_model(self):
2112+
resolver = mock.Mock()
2113+
resolver.construct_endpoint.return_value = {
2114+
'partition': 'aws',
2115+
'hostname': 'test',
2116+
'endpointName': 'us-west-2',
2117+
'signatureVersions': ['v2'],
2118+
}
2119+
bridge = ClientEndpointBridge(resolver, service_signature_version='v4')
2120+
resolved = bridge.resolve('test', 'us-west-2')
2121+
self.assertEqual('v4', resolved['signature_version'])
2122+
21112123
def test_uses_service_name_as_signing_name(self):
21122124
resolver = mock.Mock()
21132125
resolver.construct_endpoint.return_value = {

0 commit comments

Comments
 (0)