Skip to content

Commit 0db98ca

Browse files
authored
Fix bug with auth_scheme_preference when endpoint resolves from EP2.0 or operation-level trait (boto#3670)
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 a38f490 commit 0db98ca

5 files changed

Lines changed: 142 additions & 3 deletions

File tree

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`` client config option, the ``auth_scheme_preference`` shared config setting, or the existing ``AWS_AUTH_SCHEME_PREFERENCE`` environment variable."
5+
}

botocore/args.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def get_client_args(
143143
s3_disable_express_session_auth = config_kwargs[
144144
's3_disable_express_session_auth'
145145
]
146+
auth_scheme_preference = config_kwargs['auth_scheme_preference']
146147

147148
event_emitter = copy.copy(self._event_emitter)
148149
signer = RequestSigner(
@@ -207,6 +208,7 @@ def get_client_args(
207208
credentials,
208209
account_id_endpoint_mode,
209210
s3_disable_express_session_auth,
211+
auth_scheme_preference,
210212
)
211213

212214
# Copy the session's user agent factory and adds client configuration.
@@ -736,6 +738,7 @@ def _build_endpoint_resolver(
736738
credentials,
737739
account_id_endpoint_mode,
738740
s3_disable_express_session_auth,
741+
auth_scheme_preference,
739742
):
740743
if endpoints_ruleset_data is None:
741744
return None
@@ -792,6 +795,7 @@ def _build_endpoint_resolver(
792795
event_emitter=event_emitter,
793796
use_ssl=is_secure,
794797
requested_auth_scheme=sig_version,
798+
auth_scheme_preference=auth_scheme_preference,
795799
)
796800

797801
def compute_endpoint_resolver_builtin_defaults(

botocore/client.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
xform_name,
1919
)
2020
from botocore.args import ClientArgsCreator
21-
from botocore.auth import AUTH_TYPE_MAPS, resolve_auth_type
21+
from botocore.auth import (
22+
AUTH_TYPE_MAPS,
23+
resolve_auth_scheme_preference,
24+
resolve_auth_type,
25+
)
2226
from botocore.awsrequest import prepare_request_dict
2327
from botocore.compress import maybe_compress_request
2428
from botocore.config import Config
@@ -1007,11 +1011,23 @@ def _make_api_call(self, operation_name, api_params):
10071011
logger.debug(
10081012
'Warning: %s.%s() is deprecated', service_name, operation_name
10091013
)
1014+
# If the operation has the `auth` property and the client has a
1015+
# configured auth scheme preference, use both to compute the
1016+
# auth type. Otherwise, fallback to auth/auth_type resolution.
1017+
if operation_model.auth and self.meta.config.auth_scheme_preference:
1018+
preferred_schemes = self.meta.config.auth_scheme_preference.split(
1019+
','
1020+
)
1021+
auth_type = resolve_auth_scheme_preference(
1022+
preferred_schemes, operation_model.auth
1023+
)
1024+
else:
1025+
auth_type = operation_model.resolved_auth_type
10101026
request_context = {
10111027
'client_region': self.meta.region_name,
10121028
'client_config': self.meta.config,
10131029
'has_streaming_input': operation_model.has_streaming_input,
1014-
'auth_type': operation_model.resolved_auth_type,
1030+
'auth_type': auth_type,
10151031
'unsigned_payload': operation_model.unsigned_payload,
10161032
'auth_options': self._service_model.metadata.get('auth'),
10171033
}

botocore/regions.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
import jmespath
2626

2727
from botocore import UNSIGNED, xform_name
28-
from botocore.auth import AUTH_TYPE_MAPS, HAS_CRT
28+
from botocore.auth import (
29+
AUTH_TYPE_MAPS,
30+
HAS_CRT,
31+
resolve_auth_scheme_preference,
32+
)
2933
from botocore.crt import CRT_SUPPORTED_AUTH_TYPES
3034
from botocore.endpoint_provider import EndpointProvider
3135
from botocore.exceptions import (
@@ -478,6 +482,7 @@ def __init__(
478482
event_emitter,
479483
use_ssl=True,
480484
requested_auth_scheme=None,
485+
auth_scheme_preference=None,
481486
):
482487
self._provider = EndpointProvider(
483488
ruleset_data=endpoint_ruleset_data,
@@ -490,6 +495,7 @@ def __init__(
490495
self._event_emitter = event_emitter
491496
self._use_ssl = use_ssl
492497
self._requested_auth_scheme = requested_auth_scheme
498+
self._auth_scheme_preference = auth_scheme_preference
493499
self._instance_cache = {}
494500

495501
def construct_endpoint(
@@ -703,6 +709,9 @@ def auth_schemes_to_signing_ctx(self, auth_schemes):
703709
if self._requested_auth_scheme == UNSIGNED:
704710
return 'none', {}
705711

712+
available_ruleset_names = [
713+
s['name'].split('#')[-1] for s in auth_schemes
714+
]
706715
auth_schemes = [
707716
{**scheme, 'name': self._strip_sig_prefix(scheme['name'])}
708717
for scheme in auth_schemes
@@ -724,6 +733,16 @@ def auth_schemes_to_signing_ctx(self, auth_schemes):
724733
# exception, instead default to the logic in botocore
725734
# customizations.
726735
return None, {}
736+
elif self._auth_scheme_preference is not None:
737+
prefs = self._auth_scheme_preference.split(',')
738+
auth_schemes_by_auth_type = {
739+
self._strip_sig_prefix(s['name'].split('#')[-1]): s
740+
for s in auth_schemes
741+
}
742+
name = resolve_auth_scheme_preference(
743+
prefs, available_ruleset_names
744+
)
745+
scheme = auth_schemes_by_auth_type[name]
727746
else:
728747
try:
729748
name, scheme = next(

tests/unit/test_endpoint_provider.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,26 @@
8282
],
8383
},
8484
}
85+
ENDPOINT_AUTH_SCHEMES_DICT = {
86+
"url": URL_TEMPLATE,
87+
"properties": {
88+
"authSchemes": [
89+
{
90+
"disableDoubleEncoding": True,
91+
"name": "foo",
92+
"signingName": "s3-outposts",
93+
"signingRegionSet": ["*"],
94+
},
95+
{
96+
"disableDoubleEncoding": True,
97+
"name": "bar",
98+
"signingName": "s3-outposts",
99+
"signingRegion": REGION_TEMPLATE,
100+
},
101+
],
102+
},
103+
"headers": {},
104+
}
85105

86106

87107
@pytest.fixture(scope="module")
@@ -619,3 +639,78 @@ def test_construct_endpoint_parametrized(
619639
):
620640
result = resolver.construct_endpoint(None, None, None)
621641
assert result.url == expected_url
642+
643+
644+
@pytest.mark.parametrize(
645+
"auth_scheme_preference,expected_auth_scheme_name",
646+
[
647+
(
648+
'foo,bar',
649+
'foo',
650+
),
651+
(
652+
'bar,foo',
653+
'bar',
654+
),
655+
(
656+
'xyz,foo,bar',
657+
'foo',
658+
),
659+
],
660+
)
661+
def test_auth_scheme_preference(
662+
auth_scheme_preference, expected_auth_scheme_name, monkeypatch
663+
):
664+
conditions = (
665+
[
666+
PARSE_ARN_FUNC,
667+
{
668+
"fn": "not",
669+
"argv": [STRING_EQUALS_FUNC],
670+
},
671+
{
672+
"fn": "aws.partition",
673+
"argv": [REGION_REF],
674+
"assign": "PartitionResults",
675+
},
676+
],
677+
)
678+
resolver = EndpointRulesetResolver(
679+
endpoint_ruleset_data={
680+
'version': '1.0',
681+
'parameters': {},
682+
'rules': [
683+
{
684+
'conditions': conditions,
685+
'type': 'endpoint',
686+
'endpoint': ENDPOINT_AUTH_SCHEMES_DICT,
687+
}
688+
],
689+
},
690+
partition_data={},
691+
service_model=None,
692+
builtins={},
693+
client_context=None,
694+
event_emitter=None,
695+
use_ssl=True,
696+
requested_auth_scheme=None,
697+
auth_scheme_preference=auth_scheme_preference,
698+
)
699+
auth_schemes = [
700+
{'name': 'foo', 'signingName': 's3', 'signingRegion': 'ap-south-1'},
701+
{'name': 'bar', 'signingName': 's3', 'signingRegion': 'ap-south-2'},
702+
]
703+
with (
704+
patch.dict(
705+
'botocore.auth.AUTH_TYPE_MAPS',
706+
{'bar': None, 'foo': None},
707+
clear=True,
708+
),
709+
patch.dict(
710+
'botocore.auth.AUTH_PREF_TO_SIGNATURE_VERSION',
711+
{'bar': 'bar', 'foo': 'foo'},
712+
clear=True,
713+
),
714+
):
715+
name, scheme = resolver.auth_schemes_to_signing_ctx(auth_schemes)
716+
assert name == expected_auth_scheme_name

0 commit comments

Comments
 (0)