Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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

"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'
]
preferred_auth_schemes = config_kwargs['auth_scheme_preference']
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.

[Nit, non-blocking]
IMO this variable name should exactly the kwarg name so there's no confusion. There are already a ton of variables around signature version. In this file alone:

signature_version -> sig_version -> requested_auth_scheme, then also you're now adding auth_scheme_preference -> preferred_auth_schemes. It's a lot of complexity to keep in your head, so while it doesn't make a huge difference, I'd prefer to see this renamed to just auth_scheme_preference to make it clear that it's the same thing as the config variable.


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,
preferred_auth_schemes,
)

# 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,
preferred_auth_schemes,
):
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,
preferred_auth_schemes=preferred_auth_schemes,
)

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
13 changes: 13 additions & 0 deletions awscli/botocore/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ def __init__(
event_emitter,
use_ssl=True,
requested_auth_scheme=None,
preferred_auth_schemes=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._preferred_auth_schemes = preferred_auth_schemes
self._instance_cache = {}

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

# if a preferred auth schemes list is provided, reorder the auth schemes
# list based on the preferred ordering.
if self._preferred_auth_schemes is not None:
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.

[Change requested]
We already have a function called resolve_auth_scheme_preference which handles this and should avoid duplicating preference-resolution / ordering logic here.

Right now we manually reorder the endpoint ruleset authSchemes list based on auth_scheme_preference. Instead, we can reuse resolve_auth_scheme_preference() to pick the desired auth type and then map that back to the actual ruleset scheme dict. Here's the proof of concept I wrote:

        if self._requested_auth_scheme is not None:
             ....
        elif self._preferred_auth_schemes is not None:
            prefs = self._preferred_auth_schemes.split(',')
            available_ruleset_names = [s['name'].split('#')[-1] for s in auth_schemes]
            auth_schemes_by_auth_type = {self._strip_sig_prefix(s['name'].split('#')[-1]): s for s in auth_schemes}
            resolved_auth_type = resolve_auth_scheme_preference(prefs, available_ruleset_names)
            name = resolved_auth_type
            scheme = auth_schemes_by_auth_type[resolved_auth_type]

We could definitely refactor this and/or the resolve_auth_scheme_preference to make it much cleaner. What do you think?

prefs = self._preferred_auth_schemes.split(',')
by_name = {s['name']: s for s in auth_schemes}
auth_schemes = [
by_name[p] for p in prefs if p in by_name
] + [
s for s in auth_schemes if s['name'] not in prefs
]

auth_schemes = [
{**scheme, 'name': self._strip_sig_prefix(scheme['name'])}
for scheme in auth_schemes
Expand Down
91 changes: 90 additions & 1 deletion tests/unit/botocore/test_endpoint_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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 +584,71 @@ 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(
"preferred_auth_schemes,expected_auth_scheme_name",
[
(
'foo,bar',
'foo',
),
(
'bar,foo',
'bar',
),
(
'xyz,foo,bar',
'foo',
),
],
)
def test_auth_scheme_preference(
preferred_auth_schemes,
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,
preferred_auth_schemes=preferred_auth_schemes,
)
monkeypatch.setattr(
'botocore.regions.AUTH_TYPE_MAPS',
{'bar': None, 'foo': None}
)
auth_schemes = [
{'name': 'foo', 'signingName': 's3', 'signingRegion': 'ap-south-1'},
{'name': 'bar', 'signingName': 's3', 'signingRegion': 'ap-south-2'},
]
name, scheme = resolver.auth_schemes_to_signing_ctx(auth_schemes)
assert name == expected_auth_scheme_name
Loading