Skip to content

Commit 4f879de

Browse files
authored
[v2] Fix bug with auth_scheme_preference when endpoint resolves from EP2.0 or operation-level trait (#10169)
The auth_scheme_preference config setting is now used to reprioritize auth schemes, if configured, if the auth scheme is resolved from Endpoints model or at the operation-level of service-2 models.
1 parent 7f985d7 commit 4f879de

File tree

5 files changed

+140
-4
lines changed

5 files changed

+140
-4
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "bugfix",
3+
"category": "signing",
4+
"description": "Fix bug so that configured auth scheme preference is used when auth scheme is resolved from endpoints rulesets, or from operation-level auth trait. Auth scheme preference can be configured using the existing ``auth_scheme_preference`` shared config setting, or the existing ``AWS_AUTH_SCHEME_PREFERENCE`` environment variable."
5+
}

awscli/botocore/args.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ def get_client_args(
119119
s3_disable_express_session_auth = config_kwargs[
120120
's3_disable_express_session_auth'
121121
]
122+
auth_scheme_preference = config_kwargs['auth_scheme_preference']
122123

123124
event_emitter = copy.copy(self._event_emitter)
124125
signer = RequestSigner(
@@ -169,6 +170,7 @@ def get_client_args(
169170
credentials,
170171
account_id_endpoint_mode,
171172
s3_disable_express_session_auth,
173+
auth_scheme_preference,
172174
)
173175

174176
# Copy the session's user agent factory and adds client configuration.
@@ -589,6 +591,7 @@ def _build_endpoint_resolver(
589591
credentials,
590592
account_id_endpoint_mode,
591593
s3_disable_express_session_auth,
594+
auth_scheme_preference,
592595
):
593596
if endpoints_ruleset_data is None:
594597
return None
@@ -645,6 +648,7 @@ def _build_endpoint_resolver(
645648
event_emitter=event_emitter,
646649
use_ssl=is_secure,
647650
requested_auth_scheme=sig_version,
651+
auth_scheme_preference=auth_scheme_preference,
648652
)
649653

650654
def compute_endpoint_resolver_builtin_defaults(

awscli/botocore/client.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
xform_name,
2020
)
2121
from botocore.args import ClientArgsCreator
22-
from botocore.auth import AUTH_TYPE_MAPS, resolve_auth_type
22+
from botocore.auth import (
23+
AUTH_TYPE_MAPS,
24+
resolve_auth_scheme_preference,
25+
resolve_auth_type,
26+
)
2327
from botocore.awsrequest import prepare_request_dict
2428
from botocore.compress import maybe_compress_request
2529

@@ -838,11 +842,23 @@ def _make_api_call(self, operation_name, api_params):
838842
logger.debug(
839843
'Warning: %s.%s() is deprecated', service_name, operation_name
840844
)
845+
# If the operation has the `auth` property and the client has a
846+
# configured auth scheme preference, use both to compute the
847+
# auth type. Otherwise, fallback to auth/auth_type resolution.
848+
if operation_model.auth and self.meta.config.auth_scheme_preference:
849+
preferred_schemes = (
850+
self.meta.config.auth_scheme_preference.split(',')
851+
)
852+
auth_type = resolve_auth_scheme_preference(
853+
preferred_schemes, operation_model.auth
854+
)
855+
else:
856+
auth_type = operation_model.resolved_auth_type
841857
request_context = {
842858
'client_region': self.meta.region_name,
843859
'client_config': self.meta.config,
844860
'has_streaming_input': operation_model.has_streaming_input,
845-
'auth_type': operation_model.resolved_auth_type,
861+
'auth_type': auth_type,
846862
'unsigned_payload': operation_model.unsigned_payload,
847863
'auth_options': self._service_model.metadata.get('auth'),
848864
}

awscli/botocore/regions.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
import jmespath
2626
from botocore import UNSIGNED, xform_name
27-
from botocore.auth import AUTH_TYPE_MAPS
27+
from botocore.auth import AUTH_TYPE_MAPS, resolve_auth_scheme_preference
2828
from botocore.endpoint_provider import EndpointProvider
2929
from botocore.exceptions import (
3030
EndpointProviderError,
@@ -471,6 +471,7 @@ def __init__(
471471
event_emitter,
472472
use_ssl=True,
473473
requested_auth_scheme=None,
474+
auth_scheme_preference=None,
474475
):
475476
self._provider = EndpointProvider(
476477
ruleset_data=endpoint_ruleset_data,
@@ -483,6 +484,7 @@ def __init__(
483484
self._event_emitter = event_emitter
484485
self._use_ssl = use_ssl
485486
self._requested_auth_scheme = requested_auth_scheme
487+
self._auth_scheme_preference = auth_scheme_preference
486488
self._instance_cache = {}
487489

488490
def construct_endpoint(
@@ -698,6 +700,9 @@ def auth_schemes_to_signing_ctx(self, auth_schemes):
698700
if self._requested_auth_scheme == UNSIGNED:
699701
return 'none', {}
700702

703+
available_ruleset_names = [
704+
s['name'].split('#')[-1] for s in auth_schemes
705+
]
701706
auth_schemes = [
702707
{**scheme, 'name': self._strip_sig_prefix(scheme['name'])}
703708
for scheme in auth_schemes
@@ -719,6 +724,14 @@ def auth_schemes_to_signing_ctx(self, auth_schemes):
719724
# exception, instead default to the logic in botocore
720725
# customizations.
721726
return None, {}
727+
elif self._auth_scheme_preference is not None:
728+
prefs = self._auth_scheme_preference.split(',')
729+
auth_schemes_by_auth_type = {
730+
self._strip_sig_prefix(s['name'].split('#')[-1]): s
731+
for s in auth_schemes
732+
}
733+
name = resolve_auth_scheme_preference(prefs, available_ruleset_names)
734+
scheme = auth_schemes_by_auth_type[name]
722735
else:
723736
try:
724737
name, scheme = next(

tests/unit/botocore/test_endpoint_provider.py

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import json
1515
import logging
1616
import os
17+
from unittest import mock
1718

1819
import pytest
1920

@@ -80,6 +81,28 @@
8081
],
8182
},
8283
}
84+
ENDPOINT_AUTH_SCHEMES_DICT = {
85+
"url": URL_TEMPLATE,
86+
"properties": {
87+
"authSchemes": [
88+
{
89+
"disableDoubleEncoding": True,
90+
"name": "foo",
91+
"signingName": "s3-outposts",
92+
"signingRegionSet": [
93+
"*"
94+
]
95+
},
96+
{
97+
"disableDoubleEncoding": True,
98+
"name": "bar",
99+
"signingName": "s3-outposts",
100+
"signingRegion": REGION_TEMPLATE,
101+
},
102+
],
103+
},
104+
"headers": {},
105+
}
83106

84107

85108
@pytest.fixture(scope="module")
@@ -562,4 +585,79 @@ def test_construct_endpoint_parametrized(
562585
resolver, '_get_provider_params', return_value=provider_params
563586
):
564587
result = resolver.construct_endpoint(None, None, None)
565-
assert result.url == expected_url
588+
assert result.url == expected_url
589+
590+
591+
@pytest.mark.parametrize(
592+
"auth_scheme_preference,expected_auth_scheme_name",
593+
[
594+
(
595+
'foo,bar',
596+
'foo',
597+
),
598+
(
599+
'bar,foo',
600+
'bar',
601+
),
602+
(
603+
'xyz,foo,bar',
604+
'foo',
605+
),
606+
],
607+
)
608+
def test_auth_scheme_preference(
609+
auth_scheme_preference,
610+
expected_auth_scheme_name,
611+
monkeypatch
612+
):
613+
conditions = [
614+
PARSE_ARN_FUNC,
615+
{
616+
"fn": "not",
617+
"argv": [STRING_EQUALS_FUNC],
618+
},
619+
{
620+
"fn": "aws.partition",
621+
"argv": [REGION_REF],
622+
"assign": "PartitionResults",
623+
},
624+
],
625+
resolver = EndpointRulesetResolver(
626+
endpoint_ruleset_data={
627+
'version': '1.0',
628+
'parameters': {},
629+
'rules': [
630+
{
631+
'conditions': conditions,
632+
'type': 'endpoint',
633+
'endpoint': ENDPOINT_AUTH_SCHEMES_DICT,
634+
}
635+
],
636+
},
637+
partition_data={},
638+
service_model=None,
639+
builtins={},
640+
client_context=None,
641+
event_emitter=None,
642+
use_ssl=True,
643+
requested_auth_scheme=None,
644+
auth_scheme_preference=auth_scheme_preference,
645+
)
646+
auth_schemes = [
647+
{'name': 'foo', 'signingName': 's3', 'signingRegion': 'ap-south-1'},
648+
{'name': 'bar', 'signingName': 's3', 'signingRegion': 'ap-south-2'},
649+
]
650+
with (
651+
mock.patch.dict(
652+
'botocore.auth.AUTH_TYPE_MAPS',
653+
{'bar': None, 'foo': None},
654+
clear=True
655+
),
656+
mock.patch.dict(
657+
'botocore.auth.AUTH_PREF_TO_SIGNATURE_VERSION',
658+
{'bar': 'bar', 'foo': 'foo'},
659+
clear=True
660+
)
661+
):
662+
name, scheme = resolver.auth_schemes_to_signing_ctx(auth_schemes)
663+
assert name == expected_auth_scheme_name

0 commit comments

Comments
 (0)