From 9505bbd8c5939b8b8f911da25a98bb153b5b1eae Mon Sep 17 00:00:00 2001 From: Andrew Asseily Date: Tue, 3 Nov 2026 09:57:17 -0500 Subject: [PATCH 1/2] Fix IMDS region resolution in Local Zones InstanceMetadataRegionFetcher derived the region by stripping the last character from placement/availability-zone, which breaks for Local Zones (us-east-1-atl-2a became us-east-1-atl-2). Read placement/region instead, which returns the parent region directly. Also fix a typo in scripts/new-change. --- .changes/next-release/bugfix-IMDS-51328.json | 5 +++++ awscli/utils.py | 6 ++---- scripts/new-change | 2 +- tests/unit/test_utils.py | 22 +++++++++++++++++++- 4 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 .changes/next-release/bugfix-IMDS-51328.json diff --git a/.changes/next-release/bugfix-IMDS-51328.json b/.changes/next-release/bugfix-IMDS-51328.json new file mode 100644 index 000000000000..38b5467c833e --- /dev/null +++ b/.changes/next-release/bugfix-IMDS-51328.json @@ -0,0 +1,5 @@ +{ + "type": "bugfix", + "category": "IMDS", + "description": "Fixes region resolution from IMDS in Local Zones by reading the region directly instead of deriving it from the availability zone." +} diff --git a/awscli/utils.py b/awscli/utils.py index 270d98fdfe1f..543f3a30a0f7 100644 --- a/awscli/utils.py +++ b/awscli/utils.py @@ -163,7 +163,7 @@ def _create_fetcher(self): class InstanceMetadataRegionFetcher(IMDSFetcher): - _URL_PATH = 'latest/meta-data/placement/availability-zone/' + _URL_PATH = 'latest/meta-data/placement/region' def retrieve_region(self): """Get the current region from the instance metadata service. @@ -204,9 +204,7 @@ def _get_region(self): retry_func=self._default_retry, token=token, ) - availability_zone = response.text - region = availability_zone[:-1] - return region + return response.text def split_on_commas(value): diff --git a/scripts/new-change b/scripts/new-change index dc0b69893904..cb644ea4a8ae 100755 --- a/scripts/new-change +++ b/scripts/new-change @@ -29,7 +29,7 @@ You'll see this template:: Fill in the appropriate values, save and exit the editor. Make sure to commit these changes as part of your pull request. -If, when your editor is open, you decide don't don't want to add a changelog +If, when your editor is open, you decide you don't want to add a changelog entry, save an empty file and no entry will be generated. You can then use the ``scripts/render-change`` to generate the diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index a08a085a7bfd..baf095f3ed86 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -399,7 +399,7 @@ def setUp(self): self._send = self._urllib3_patch.start() self._imds_responses = [] self._send.side_effect = self.get_imds_response - self._region = 'us-mars-1a' + self._region = 'us-mars-1' self.environ = {} self.environ_patch = mock.patch('os.environ', self.environ) self.environ_patch.start() @@ -473,6 +473,26 @@ def test_includes_user_agent_header(self): headers = self._send.call_args[0][0].headers self.assertEqual(headers['User-Agent'], user_agent) + def test_requests_region_from_placement_region(self): + self.add_imds_token_response() + self.add_get_region_imds_response() + + InstanceMetadataRegionFetcher().retrieve_region() + + url = self._send.call_args[0][0].url + self.assertTrue(url.endswith('latest/meta-data/placement/region')) + + def test_local_zone_returns_parent_region(self): + # In a Local Zone the availability zone is e.g. us-east-1-atl-2a, + # so the region has to come from placement/region rather than + # being derived from the availability zone. + self.add_imds_token_response() + self.add_get_region_imds_response(region='us-east-1') + + result = InstanceMetadataRegionFetcher().retrieve_region() + + self.assertEqual(result, 'us-east-1') + def test_non_200_response_for_region_is_retried(self): # Response for role name that have a non 200 status code should # be retried. From 0e612d47ec7c0758041e577cadb8645cf8b88528 Mon Sep 17 00:00:00 2001 From: Andrew Asseily Date: Tue, 3 Nov 2026 10:30:04 -0500 Subject: [PATCH 2/2] Update functional IMDS tests to return a region The region fetcher now reads placement/region, so the mocked IMDS response is a region rather than an availability zone. --- tests/functional/test_clidriver.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/functional/test_clidriver.py b/tests/functional/test_clidriver.py index 4e3730a731d0..11f18c964bac 100644 --- a/tests/functional/test_clidriver.py +++ b/tests/functional/test_clidriver.py @@ -66,9 +66,8 @@ def test_imds_region_is_used_as_fallback_wo_v2_support(self): # First response should be from the IMDS server for security token # if server supports IMDSv1 only there will be no response for token self.add_response(None) - # Then another response from the IMDS server for an availability - # zone. - self.add_response(b'us-mars-2a') + # Then another response from the IMDS server for the region. + self.add_response(b'us-mars-2') # Once a region is fetched form the IMDS server we need to mock an # XML response from ec2 so that the CLI driver doesn't throw an error # during parsing. @@ -84,9 +83,8 @@ def test_imds_region_is_used_as_fallback_with_v2_support(self): # First response should be from the IMDS server for security token # if server supports IMDSv2 it'll return token self.add_response(b'token') - # Then another response from the IMDS server for an availability - # zone. - self.add_response(b'us-mars-2a') + # Then another response from the IMDS server for the region. + self.add_response(b'us-mars-2') # Once a region is fetched form the IMDS server we need to mock an # XML response from ec2 so that the CLI driver doesn't throw an error # during parsing.