Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
527f455
Prototype fix for respecting auth scheme preference.
aemous Mar 27, 2026
5be9c66
Rename bearer auth function.
aemous Mar 27, 2026
e09d885
Remove debug statement
aemous Mar 27, 2026
77e7f7c
Remove unused print statement.
aemous Mar 27, 2026
24c9076
MAke sure operation-level auth is in candidates list.
aemous Mar 27, 2026
b5e9d0b
Remove debug print.
aemous Mar 27, 2026
7a4c270
Extract common code.
aemous Mar 27, 2026
9a8cd56
Remove UnsupportedSignatureVersionError try-except clause; was not th…
aemous Mar 27, 2026
0563fe8
Update code architecture to be cleaner, based on feedback.
aemous Apr 1, 2026
8daf219
Remove dead code.
aemous Apr 1, 2026
5e9e1a2
Migrate import to botocore from awscli.
aemous Apr 1, 2026
9847e4e
Move the function to the right location.
aemous Apr 1, 2026
cc0548f
Simplify reordering code added to EndpointRulesetResolver.
aemous Apr 1, 2026
edc7fd5
Add changelog entry.
aemous Apr 1, 2026
a40c847
Merge branch 'v2' into bugfix-auth-scheme-pref
aemous Apr 1, 2026
5681b21
Add unit test cases for EP2.0 auth_scheme_preference.
aemous Apr 1, 2026
cd01f69
Remove unused import.
aemous Apr 6, 2026
fb7201f
Rename local variables based on PR feedback.
aemous Apr 8, 2026
8f3212e
Refactor endpoint preference resolution to use utility function.
aemous Apr 8, 2026
7f9f88b
Remove redundant code.
aemous Apr 8, 2026
ee60b5b
Fix tests based on latest revisions.
aemous Apr 8, 2026
bb313cd
Merge remote-tracking branch 'origin/v2' into bugfix-auth-scheme-pref
aemous Apr 8, 2026
7697542
Clean regions code to use resolve_auth_scheme_preference
aemous Apr 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-signing-24412.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High level note:

Jonathan has a semi-related bugfix that will likely need to apply here as well - https://github.com/boto/botocore/pull/3663/changes.

When we resolve v4a via auth scheme preference, we need to make sure we are also respecting the signing region set

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout. I approved and merged the v2 port. I'll now merge it into this branch and make sure the signing set is applied when v4a is resolved via preference in all cases (including EP2.0 and operation-level traits).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After merging in Jonathan's code to this branch, and refactoring the code based on your feedback, I have manually tested various resolution cases (EP2.0, operation-level modeled, with auth scheme preference and sigv4a signing region set configured, and everything seems to work correctly).

"type": "bugfix",
"category": "signing",
"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."
}
4 changes: 4 additions & 0 deletions awscli/botocore/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def get_client_args(
s3_disable_express_session_auth = config_kwargs[
's3_disable_express_session_auth'
]
auth_scheme_preference = config_kwargs['auth_scheme_preference']

event_emitter = copy.copy(self._event_emitter)
signer = RequestSigner(
Expand Down Expand Up @@ -169,6 +170,7 @@ def get_client_args(
credentials,
account_id_endpoint_mode,
s3_disable_express_session_auth,
auth_scheme_preference,
)

# Copy the session's user agent factory and adds client configuration.
Expand Down Expand Up @@ -589,6 +591,7 @@ def _build_endpoint_resolver(
credentials,
account_id_endpoint_mode,
s3_disable_express_session_auth,
auth_scheme_preference,
):
if endpoints_ruleset_data is None:
return None
Expand Down Expand Up @@ -645,6 +648,7 @@ def _build_endpoint_resolver(
event_emitter=event_emitter,
use_ssl=is_secure,
requested_auth_scheme=sig_version,
auth_scheme_preference=auth_scheme_preference,
)

def compute_endpoint_resolver_builtin_defaults(
Expand Down
20 changes: 18 additions & 2 deletions awscli/botocore/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
xform_name,
)
from botocore.args import ClientArgsCreator
from botocore.auth import AUTH_TYPE_MAPS, resolve_auth_type
from botocore.auth import (
AUTH_TYPE_MAPS,
resolve_auth_scheme_preference,
resolve_auth_type,
)
from botocore.awsrequest import prepare_request_dict
from botocore.compress import maybe_compress_request

Expand Down Expand Up @@ -838,11 +842,23 @@ def _make_api_call(self, operation_name, api_params):
logger.debug(
'Warning: %s.%s() is deprecated', service_name, operation_name
)
# If the operation has the `auth` property and the client has a
# configured auth scheme preference, use both to compute the
# auth type. Otherwise, fallback to auth/auth_type resolution.
if operation_model.auth and self.meta.config.auth_scheme_preference:
preferred_schemes = (
self.meta.config.auth_scheme_preference.split(',')
)
auth_type = resolve_auth_scheme_preference(
preferred_schemes, operation_model.auth
)
else:
auth_type = operation_model.resolved_auth_type
request_context = {
'client_region': self.meta.region_name,
'client_config': self.meta.config,
'has_streaming_input': operation_model.has_streaming_input,
'auth_type': operation_model.resolved_auth_type,
'auth_type': auth_type,
'unsigned_payload': operation_model.unsigned_payload,
'auth_options': self._service_model.metadata.get('auth'),
}
Expand Down
15 changes: 14 additions & 1 deletion awscli/botocore/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import jmespath
from botocore import UNSIGNED, xform_name
from botocore.auth import AUTH_TYPE_MAPS
from botocore.auth import AUTH_TYPE_MAPS, resolve_auth_scheme_preference
from botocore.endpoint_provider import EndpointProvider
from botocore.exceptions import (
EndpointProviderError,
Expand Down Expand Up @@ -471,6 +471,7 @@ def __init__(
event_emitter,
use_ssl=True,
requested_auth_scheme=None,
auth_scheme_preference=None,
):
self._provider = EndpointProvider(
ruleset_data=endpoint_ruleset_data,
Expand All @@ -483,6 +484,7 @@ def __init__(
self._event_emitter = event_emitter
self._use_ssl = use_ssl
self._requested_auth_scheme = requested_auth_scheme
self._auth_scheme_preference = auth_scheme_preference
self._instance_cache = {}

def construct_endpoint(
Expand Down Expand Up @@ -698,6 +700,9 @@ def auth_schemes_to_signing_ctx(self, auth_schemes):
if self._requested_auth_scheme == UNSIGNED:
return 'none', {}

available_ruleset_names = [
s['name'].split('#')[-1] for s in auth_schemes
]
auth_schemes = [
{**scheme, 'name': self._strip_sig_prefix(scheme['name'])}
for scheme in auth_schemes
Expand All @@ -719,6 +724,14 @@ def auth_schemes_to_signing_ctx(self, auth_schemes):
# exception, instead default to the logic in botocore
# customizations.
return None, {}
elif self._auth_scheme_preference is not None:
prefs = self._auth_scheme_preference.split(',')
auth_schemes_by_auth_type = {
self._strip_sig_prefix(s['name'].split('#')[-1]): s
for s in auth_schemes
}
name = resolve_auth_scheme_preference(prefs, available_ruleset_names)
scheme = auth_schemes_by_auth_type[name]
else:
try:
name, scheme = next(
Expand Down
100 changes: 99 additions & 1 deletion tests/unit/botocore/test_endpoint_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import json
import logging
import os
from unittest import mock

import pytest

Expand Down Expand Up @@ -80,6 +81,28 @@
],
},
}
ENDPOINT_AUTH_SCHEMES_DICT = {
"url": URL_TEMPLATE,
"properties": {
"authSchemes": [
{
"disableDoubleEncoding": True,
"name": "foo",
"signingName": "s3-outposts",
"signingRegionSet": [
"*"
]
},
{
"disableDoubleEncoding": True,
"name": "bar",
"signingName": "s3-outposts",
"signingRegion": REGION_TEMPLATE,
},
],
},
"headers": {},
}


@pytest.fixture(scope="module")
Expand Down Expand Up @@ -562,4 +585,79 @@ def test_construct_endpoint_parametrized(
resolver, '_get_provider_params', return_value=provider_params
):
result = resolver.construct_endpoint(None, None, None)
assert result.url == expected_url
assert result.url == expected_url


@pytest.mark.parametrize(
"auth_scheme_preference,expected_auth_scheme_name",
[
(
'foo,bar',
'foo',
),
(
'bar,foo',
'bar',
),
(
'xyz,foo,bar',
'foo',
),
],
)
def test_auth_scheme_preference(
auth_scheme_preference,
expected_auth_scheme_name,
monkeypatch
):
conditions = [
PARSE_ARN_FUNC,
{
"fn": "not",
"argv": [STRING_EQUALS_FUNC],
},
{
"fn": "aws.partition",
"argv": [REGION_REF],
"assign": "PartitionResults",
},
],
resolver = EndpointRulesetResolver(
endpoint_ruleset_data={
'version': '1.0',
'parameters': {},
'rules': [
{
'conditions': conditions,
'type': 'endpoint',
'endpoint': ENDPOINT_AUTH_SCHEMES_DICT,
}
],
},
partition_data={},
service_model=None,
builtins={},
client_context=None,
event_emitter=None,
use_ssl=True,
requested_auth_scheme=None,
auth_scheme_preference=auth_scheme_preference,
)
auth_schemes = [
{'name': 'foo', 'signingName': 's3', 'signingRegion': 'ap-south-1'},
{'name': 'bar', 'signingName': 's3', 'signingRegion': 'ap-south-2'},
]
with (
mock.patch.dict(
'botocore.auth.AUTH_TYPE_MAPS',
{'bar': None, 'foo': None},
clear=True
),
mock.patch.dict(
'botocore.auth.AUTH_PREF_TO_SIGNATURE_VERSION',
{'bar': 'bar', 'foo': 'foo'},
clear=True
)
):
name, scheme = resolver.auth_schemes_to_signing_ctx(auth_schemes)
assert name == expected_auth_scheme_name
Loading