From 5469b53926420bbb32258257aaa5bc35bc519901 Mon Sep 17 00:00:00 2001 From: Antonio Aranda <102337110+arandito@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:08:02 -0400 Subject: [PATCH 1/4] Move PROFILE_ASSUME_ROLE before PROFILE_SESSION_KEYS and PROFILE_STATIC_KEYS in StandardProvider --- ...aking-3ecac569c6ea4f2691d472816fde80ae.json | 4 ++++ .../smithy_aws_core/identity/chain/ordering.py | 2 +- .../tests/unit/identity/chain/test_chain.py | 18 ++++++++++++++++-- .../tests/unit/identity/chain/test_ordering.py | 4 ++-- 4 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 packages/smithy-aws-core/.changes/next-release/smithy-aws-core-breaking-3ecac569c6ea4f2691d472816fde80ae.json diff --git a/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-breaking-3ecac569c6ea4f2691d472816fde80ae.json b/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-breaking-3ecac569c6ea4f2691d472816fde80ae.json new file mode 100644 index 000000000..349381760 --- /dev/null +++ b/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-breaking-3ecac569c6ea4f2691d472816fde80ae.json @@ -0,0 +1,4 @@ +{ + "type": "breaking", + "description": "Updated the credential chain precedence so assume role credentials are resolved before session and static profile keys." +} diff --git a/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/ordering.py b/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/ordering.py index 4fa5cbf2e..800d54a72 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/ordering.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/ordering.py @@ -13,9 +13,9 @@ class StandardProvider(Enum): ENVIRONMENT = "Environment", None WEB_IDENTITY_TOKEN_ENV = "WebIdentityTokenEnv", "aws-credentials-sts" SHARED_CONFIG = "SharedConfig", None + PROFILE_ASSUME_ROLE = "ProfileAssumeRole", "aws-credentials-sts" PROFILE_SESSION_KEYS = "ProfileSessionKeys", None PROFILE_STATIC_KEYS = "ProfileStaticKeys", None - PROFILE_ASSUME_ROLE = "ProfileAssumeRole", "aws-credentials-sts" PROFILE_WEB_IDENTITY = "ProfileWebIdentity", "aws-credentials-sts" PROFILE_SSO_SESSION = "ProfileSsoSession", "aws-credentials-sso" PROFILE_LOGIN = "Login", "aws-credentials-login" diff --git a/packages/smithy-aws-core/tests/unit/identity/chain/test_chain.py b/packages/smithy-aws-core/tests/unit/identity/chain/test_chain.py index 3300b7fb0..07a23c110 100644 --- a/packages/smithy-aws-core/tests/unit/identity/chain/test_chain.py +++ b/packages/smithy-aws-core/tests/unit/identity/chain/test_chain.py @@ -131,12 +131,26 @@ def test_sort_orders_standards_by_slot_declaration() -> None: static = _StubProvider( "static", Standard(slot=StandardProvider.PROFILE_STATIC_KEYS) ) + assume_role = _StubProvider( + "assume-role", Standard(slot=StandardProvider.PROFILE_ASSUME_ROLE) + ) + session = _StubProvider( + "session", Standard(slot=StandardProvider.PROFILE_SESSION_KEYS) + ) env = _StubProvider("env", Standard(slot=StandardProvider.ENVIRONMENT)) shared = _StubProvider("shared", Standard(slot=StandardProvider.SHARED_CONFIG)) - ordered = chain_module._sort_by_ordering((static, env, shared)) + ordered = chain_module._sort_by_ordering( + (static, session, env, assume_role, shared) + ) - assert [p.name for p in ordered] == ["env", "shared", "static"] + assert [p.name for p in ordered] == [ + "env", + "shared", + "assume-role", + "session", + "static", + ] def test_sort_places_before_and_after_around_slot() -> None: diff --git a/packages/smithy-aws-core/tests/unit/identity/chain/test_ordering.py b/packages/smithy-aws-core/tests/unit/identity/chain/test_ordering.py index ffbd3a93e..986162ba3 100644 --- a/packages/smithy-aws-core/tests/unit/identity/chain/test_ordering.py +++ b/packages/smithy-aws-core/tests/unit/identity/chain/test_ordering.py @@ -98,13 +98,13 @@ def test_shared_config_detection( "aws-credentials-sts", ), (StandardProvider.SHARED_CONFIG, "SharedConfig", None), - (StandardProvider.PROFILE_SESSION_KEYS, "ProfileSessionKeys", None), - (StandardProvider.PROFILE_STATIC_KEYS, "ProfileStaticKeys", None), ( StandardProvider.PROFILE_ASSUME_ROLE, "ProfileAssumeRole", "aws-credentials-sts", ), + (StandardProvider.PROFILE_SESSION_KEYS, "ProfileSessionKeys", None), + (StandardProvider.PROFILE_STATIC_KEYS, "ProfileStaticKeys", None), ( StandardProvider.PROFILE_WEB_IDENTITY, "ProfileWebIdentity", From 36edf46ad6f3e899c27c02a3e4145d11425e61b3 Mon Sep 17 00:00:00 2001 From: Antonio Aranda <102337110+arandito@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:12:29 -0400 Subject: [PATCH 2/4] Update profile session and static key providers to defer when assume role configuration exists in profile --- ...ment-168e61426c534a2f92107d39f9241a9e.json | 4 +++ .../identity/chain/providers/profile.py | 7 +++++ .../identity/chain/providers/test_profile.py | 29 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-168e61426c534a2f92107d39f9241a9e.json diff --git a/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-168e61426c534a2f92107d39f9241a9e.json b/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-168e61426c534a2f92107d39f9241a9e.json new file mode 100644 index 000000000..0965c6b9a --- /dev/null +++ b/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-168e61426c534a2f92107d39f9241a9e.json @@ -0,0 +1,4 @@ +{ + "type": "enhancement", + "description": "Updated profile session and static key providers to defer when the selected profile declares an assume-role configuration." +} diff --git a/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/providers/profile.py b/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/providers/profile.py index fca712ec1..5b61b91a9 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/providers/profile.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/providers/profile.py @@ -11,6 +11,7 @@ _SECRET_ACCESS_KEY = "aws_secret_access_key" # noqa: S105 _SESSION_TOKEN = "aws_session_token" # noqa: S105 _ACCOUNT_ID = "aws_account_id" +_ROLE_ARN = "role_arn" class ProfileSessionCredentialsProvider: @@ -36,6 +37,9 @@ async def setup(self, identity_type: type[Identity], setup: ChainSetup) -> None: if config_file is None or profile_name is None: return + if config_file.get(profile_name, _ROLE_ARN) is not None: + return + access_key_id = config_file.get(profile_name, _ACCESS_KEY_ID) secret_access_key = config_file.get(profile_name, _SECRET_ACCESS_KEY) session_token = config_file.get(profile_name, _SESSION_TOKEN) @@ -74,6 +78,9 @@ async def setup(self, identity_type: type[Identity], setup: ChainSetup) -> None: if config_file is None or profile_name is None: return + if config_file.get(profile_name, _ROLE_ARN) is not None: + return + access_key_id = config_file.get(profile_name, _ACCESS_KEY_ID) secret_access_key = config_file.get(profile_name, _SECRET_ACCESS_KEY) if access_key_id is None or secret_access_key is None: diff --git a/packages/smithy-aws-core/tests/unit/identity/chain/providers/test_profile.py b/packages/smithy-aws-core/tests/unit/identity/chain/providers/test_profile.py index 2e37c4749..58633fcf4 100644 --- a/packages/smithy-aws-core/tests/unit/identity/chain/providers/test_profile.py +++ b/packages/smithy-aws-core/tests/unit/identity/chain/providers/test_profile.py @@ -114,6 +114,35 @@ async def test_registers_terminal_resolver_for_complete_profile( assert identity == expected +@pytest.mark.parametrize( + "provider", + [ProfileSessionCredentialsProvider(), ProfileStaticCredentialsProvider()], +) +async def test_defers_when_selected_profile_declares_assume_role( + provider: Any, + setup_provider: Callable[..., Awaitable[ChainSetup]], + merged_config: Callable[..., MergedConfig], +) -> None: + setup = await setup_provider( + provider, + config_file=merged_config( + { + "default": { + "aws_access_key_id": "akid", + "aws_secret_access_key": "secret", + "aws_session_token": "token", + "role_arn": "arn:aws:iam::123456789012:role/test", + "source_profile": "default", + } + } + ), + profile_name="default", + ) + + assert setup.resolvers == () + assert not setup.terminal + + @pytest.mark.parametrize( "provider, properties", [ From a1795597ff645bd7a021e775e757ada8dd6e663c Mon Sep 17 00:00:00 2001 From: Antonio Aranda <102337110+arandito@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:24:39 -0400 Subject: [PATCH 3/4] Update env credentials provider to defer when profile_name is configured in IdentityChain.create() --- ...ment-e32f94fda81c4875a1e24a804eef9788.json | 4 +++ .../identity/chain/__init__.py | 5 +-- .../identity/chain/providers/environment.py | 5 ++- .../chain/providers/test_environment.py | 31 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-e32f94fda81c4875a1e24a804eef9788.json diff --git a/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-e32f94fda81c4875a1e24a804eef9788.json b/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-e32f94fda81c4875a1e24a804eef9788.json new file mode 100644 index 000000000..9b253dbf9 --- /dev/null +++ b/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-e32f94fda81c4875a1e24a804eef9788.json @@ -0,0 +1,4 @@ +{ + "type": "enhancement", + "description": "Updated environment credentials provider to defer when `profile_name` is passed to `IdentityChain.create()`." +} diff --git a/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/__init__.py b/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/__init__.py index 4ee48e70d..f644c1d09 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/__init__.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/__init__.py @@ -185,8 +185,9 @@ async def create[ChainIdentity: Identity]( :param identity_type: The identity type to resolve. :param config_file: Parsed config/credentials file. Loaded from disk when not set. - :param profile_name: Profile name to use. If omitted, the shared config - provider uses ``AWS_PROFILE`` when set, otherwise ``default``. + :param profile_name: Explicit profile name to use. When set, top-level + environment credential resolution is suppressed. If omitted, the shared + config provider uses ``AWS_PROFILE`` when set, otherwise ``default``. :param region_override: Region to use for providers whose resolvers fetch credentials through a service call. :param http_client: HTTP client to use for providers whose resolvers make diff --git a/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/providers/environment.py b/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/providers/environment.py index 058e8c265..269ae2840 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/providers/environment.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/identity/chain/providers/environment.py @@ -14,7 +14,7 @@ class EnvironmentCredentialsProvider: - """Adds an environment resolver when credentials are configured in the environment.""" + """Adds an environment resolver unless an explicit profile is selected.""" @property def name(self) -> str: @@ -35,6 +35,9 @@ async def setup( if identity_type is not AWSCredentialsIdentity: return + if setup.profile_name is not None: + return + if not os.getenv(_ACCESS_KEY_ID) or not os.getenv(_SECRET_ACCESS_KEY): return diff --git a/packages/smithy-aws-core/tests/unit/identity/chain/providers/test_environment.py b/packages/smithy-aws-core/tests/unit/identity/chain/providers/test_environment.py index c8c412bab..766e96153 100644 --- a/packages/smithy-aws-core/tests/unit/identity/chain/providers/test_environment.py +++ b/packages/smithy-aws-core/tests/unit/identity/chain/providers/test_environment.py @@ -64,3 +64,34 @@ async def test_registers_terminal_resolver( assert len(setup.resolvers) == 1 assert setup.resolvers[0].provider_name == "Environment" assert isinstance(setup.resolvers[0].resolver, EnvironmentCredentialsResolver) + + +async def test_explicit_profile_suppresses_environment_credentials( + setup_provider: Callable[..., Awaitable[ChainSetup]], + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("AWS_ACCESS_KEY_ID", "akid") + monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "secret") + + setup = await setup_provider( + EnvironmentCredentialsProvider(), + profile_name="work", + ) + + assert setup.resolvers == () + assert not setup.terminal + + +async def test_aws_profile_env_var_does_not_suppress_environment_credentials( + setup_provider: Callable[..., Awaitable[ChainSetup]], + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("AWS_PROFILE", "work") + monkeypatch.setenv("AWS_ACCESS_KEY_ID", "akid") + monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "secret") + + setup = await setup_provider(EnvironmentCredentialsProvider()) + + assert setup.terminal + assert len(setup.resolvers) == 1 + assert isinstance(setup.resolvers[0].resolver, EnvironmentCredentialsResolver) From a112dd6455de3fad726be6136847ae736b37f209 Mon Sep 17 00:00:00 2001 From: Antonio Aranda <102337110+arandito@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:54:13 -0400 Subject: [PATCH 4/4] Add deprecation notices to IMDSCredentialsResolver and ContainerCredentialsResolver --- ...hancement-6fb937c32d5448e88bf1d5f8246389af.json | 4 ++++ .../src/smithy_aws_core/identity/container.py | 14 +++++++++++++- .../src/smithy_aws_core/identity/imds.py | 14 +++++++++++++- .../tests/unit/identity/test_container.py | 4 ++++ .../tests/unit/identity/test_imds.py | 4 ++++ 5 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-6fb937c32d5448e88bf1d5f8246389af.json diff --git a/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-6fb937c32d5448e88bf1d5f8246389af.json b/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-6fb937c32d5448e88bf1d5f8246389af.json new file mode 100644 index 000000000..bd9ec3b2a --- /dev/null +++ b/packages/smithy-aws-core/.changes/next-release/smithy-aws-core-enhancement-6fb937c32d5448e88bf1d5f8246389af.json @@ -0,0 +1,4 @@ +{ + "type": "enhancement", + "description": "Deprecated the built-in IMDS and container credentials resolvers in favor of the resolvers provided by the `aws-credentials-imds` and `aws-credentials-http` packages." +} \ No newline at end of file diff --git a/packages/smithy-aws-core/src/smithy_aws_core/identity/container.py b/packages/smithy-aws-core/src/smithy_aws_core/identity/container.py index ed9fcc032..0082921ef 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/identity/container.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/identity/container.py @@ -4,6 +4,7 @@ import ipaddress import json import os +import warnings from dataclasses import dataclass from datetime import UTC, datetime from urllib.parse import urlparse @@ -106,7 +107,12 @@ def _is_allowed_container_metadata_host(self, hostname: str) -> bool: class ContainerCredentialsResolver( IdentityResolver[AWSCredentialsIdentity, AWSIdentityProperties] ): - """Resolves AWS Credentials from container credential sources.""" + """Resolves AWS Credentials from container credential sources. + + .. warning:: + This resolver is deprecated. Use the resolver provided by the + ``aws-credentials-http`` package instead. + """ ENV_VAR = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" ENV_VAR_FULL = "AWS_CONTAINER_CREDENTIALS_FULL_URI" @@ -118,6 +124,12 @@ def __init__( http_client: HTTPClient, config: ContainerCredentialsConfig | None = None, ): + warnings.warn( + "`ContainerCredentialsResolver` is deprecated; install " + "`aws-credentials-http` and use its resolver instead.", + DeprecationWarning, + stacklevel=2, + ) self._http_client = http_client self._config = config or ContainerCredentialsConfig() self._client = ContainerMetadataClient(http_client, self._config) diff --git a/packages/smithy-aws-core/src/smithy_aws_core/identity/imds.py b/packages/smithy-aws-core/src/smithy_aws_core/identity/imds.py index e71c8905b..f5f96c717 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/identity/imds.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/identity/imds.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import asyncio import json +import warnings from dataclasses import dataclass from datetime import UTC, datetime, timedelta from types import MappingProxyType @@ -182,11 +183,22 @@ async def get(self, *, path: str) -> str: class IMDSCredentialsResolver( IdentityResolver[AWSCredentialsIdentity, AWSIdentityProperties] ): - """Resolves AWS Credentials from an EC2 Instance Metadata Service (IMDS) client.""" + """Resolves AWS Credentials from an EC2 Instance Metadata Service (IMDS) client. + + .. warning:: + This resolver is deprecated. Use the resolver provided by the + ``aws-credentials-imds`` package instead. + """ _METADATA_PATH_BASE = "/latest/meta-data/iam/security-credentials" def __init__(self, http_client: HTTPClient, config: Config | None = None): + warnings.warn( + "`IMDSCredentialsResolver` is deprecated; install " + "`aws-credentials-imds` and use its resolver instead.", + DeprecationWarning, + stacklevel=2, + ) # TODO: Respect IMDS specific config values from aws shared config file and environment. self._http_client = http_client self._ec2_metadata_client = EC2Metadata(http_client=http_client, config=config) diff --git a/packages/smithy-aws-core/tests/unit/identity/test_container.py b/packages/smithy-aws-core/tests/unit/identity/test_container.py index 8488e55fb..7fa49688b 100644 --- a/packages/smithy-aws-core/tests/unit/identity/test_container.py +++ b/packages/smithy-aws-core/tests/unit/identity/test_container.py @@ -30,6 +30,10 @@ ISO8601 = "%Y-%m-%dT%H:%M:%SZ" +pytestmark = pytest.mark.filterwarnings( + "ignore:`ContainerCredentialsResolver` is deprecated:DeprecationWarning" +) + def test_config_custom_values(): config = ContainerCredentialsConfig(timeout=10, retries=5) diff --git a/packages/smithy-aws-core/tests/unit/identity/test_imds.py b/packages/smithy-aws-core/tests/unit/identity/test_imds.py index f5146fbe3..7b20ee315 100644 --- a/packages/smithy-aws-core/tests/unit/identity/test_imds.py +++ b/packages/smithy-aws-core/tests/unit/identity/test_imds.py @@ -19,6 +19,10 @@ from smithy_core.aio.retries import SimpleRetryStrategy from smithy_http.aio import HTTPRequest +pytestmark = pytest.mark.filterwarnings( + "ignore:`IMDSCredentialsResolver` is deprecated:DeprecationWarning" +) + def test_config_defaults(): config = Config()