From d7b196eb4ae327bffb3dcc3bc816ee745cb3d36a Mon Sep 17 00:00:00 2001 From: shreyaskommuri Date: Wed, 6 May 2026 10:00:42 -0700 Subject: [PATCH] Fix CallerReference collision in unique_string() using uuid4 unique_string() previously combined a 1-second Unix timestamp with random.randint(1, 1000000). Two invocations within the same second had a 1-in-1,000,000 chance of generating an identical CallerReference, causing CloudFront to silently drop the duplicate create-invalidation or create-distribution request. The Mersenne Twister RNG used by the random module is also not appropriate for generating values that are expected to be unique and unpredictable. Replace the implementation with uuid.uuid4(), which provides 122 bits of cryptographically random entropy and guarantees uniqueness regardless of call frequency. uuid is part of the Python standard library and requires no new dependencies. Fixes: #10281 --- awscli/customizations/cloudfront.py | 5 +-- tests/unit/customizations/test_cloudfront.py | 40 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 tests/unit/customizations/test_cloudfront.py diff --git a/awscli/customizations/cloudfront.py b/awscli/customizations/cloudfront.py index 72668d2e08cc..368d521fc8e9 100644 --- a/awscli/customizations/cloudfront.py +++ b/awscli/customizations/cloudfront.py @@ -11,8 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import sys -import time -import random +import uuid import rsa from botocore.utils import parse_to_aware_datetime @@ -63,7 +62,7 @@ def register(event_handler): def unique_string(prefix='cli'): - return '%s-%s-%s' % (prefix, int(time.time()), random.randint(1, 1000000)) + return '%s-%s' % (prefix, uuid.uuid4()) def _add_paths(argument_table, **kwargs): diff --git a/tests/unit/customizations/test_cloudfront.py b/tests/unit/customizations/test_cloudfront.py new file mode 100644 index 000000000000..f0a12fb6271d --- /dev/null +++ b/tests/unit/customizations/test_cloudfront.py @@ -0,0 +1,40 @@ +# Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +import re + +from awscli.testutils import unittest +from awscli.customizations.cloudfront import unique_string + + +class TestUniqueString(unittest.TestCase): + + UUID_RE = re.compile( + r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' + ) + + def test_default_prefix(self): + result = unique_string() + self.assertTrue(result.startswith('cli-'), result) + + def test_custom_prefix(self): + result = unique_string(prefix='myprefix') + self.assertTrue(result.startswith('myprefix-'), result) + + def test_suffix_is_uuid4(self): + result = unique_string() + suffix = result[len('cli-'):] + self.assertRegex(suffix, self.UUID_RE) + + def test_values_are_unique(self): + results = {unique_string() for _ in range(1000)} + self.assertEqual(len(results), 1000)