Skip to content

Commit 6c0ff5c

Browse files
authored
Merge pull request #9535 from aws/md5_fallback_v2
Add SHA256 fallback if MD5 isn't available on the system
2 parents 0689552 + ffef793 commit 6c0ff5c

4 files changed

Lines changed: 47 additions & 11 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "enhancement",
3+
"category": "``cloudformation``",
4+
"description": "High-level Cloudformation commands such as ``deploy`` and ``package`` will now fallback to SHA256 for checksumming if MD5 isn't present on the host"
5+
}

awscli/customizations/s3uploader.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313

14-
import hashlib
1514
import logging
1615
import os
1716
import sys
1817
import threading
1918

2019
import botocore
2120
import botocore.exceptions
21+
from botocore.compat import get_md5
2222
from s3transfer.manager import TransferManager
2323
from s3transfer.subscribers import BaseSubscriber
2424

@@ -27,6 +27,16 @@
2727
LOG = logging.getLogger(__name__)
2828

2929

30+
def _get_checksum():
31+
hashlib_params = {"usedforsecurity": False}
32+
try:
33+
checksum = get_md5(**hashlib_params)
34+
except botocore.exceptions.MD5UnavailableError:
35+
import hashlib
36+
checksum = hashlib.sha256(**hashlib_params)
37+
return checksum
38+
39+
3040
class NoSuchBucketError(Exception):
3141
def __init__(self, **kwargs):
3242
msg = self.fmt.format(**kwargs)
@@ -134,7 +144,7 @@ def upload(self, file_name, remote_path):
134144

135145
def upload_with_dedup(self, file_name, extension=None):
136146
"""
137-
Makes and returns name of the S3 object based on the file's MD5 sum
147+
Makes and returns name of the S3 object based on the file's checksum
138148
139149
:param file_name: file to upload
140150
:param extension: String of file extension to append to the object
@@ -146,8 +156,8 @@ def upload_with_dedup(self, file_name, extension=None):
146156
# and re-upload only if necessary. So the template points to same file
147157
# in multiple places, this will upload only once
148158

149-
filemd5 = self.file_checksum(file_name)
150-
remote_path = filemd5
159+
file_checksum = self.file_checksum(file_name)
160+
remote_path = file_checksum
151161
if extension:
152162
remote_path = remote_path + "." + extension
153163

@@ -175,7 +185,7 @@ def make_url(self, obj_path):
175185

176186
def file_checksum(self, file_name):
177187
with open(file_name, "rb") as file_handle:
178-
md5 = hashlib.md5()
188+
checksum = _get_checksum()
179189
# Read file in chunks of 4096 bytes
180190
block_size = 4096
181191

@@ -185,13 +195,13 @@ def file_checksum(self, file_name):
185195

186196
buf = file_handle.read(block_size)
187197
while len(buf) > 0:
188-
md5.update(buf)
198+
checksum.update(buf)
189199
buf = file_handle.read(block_size)
190200

191201
# Restore file cursor's position
192202
file_handle.seek(curpos)
193203

194-
return md5.hexdigest()
204+
return checksum.hexdigest()
195205

196206
def to_path_style_s3_url(self, key, version=None):
197207
"""

awscli/examples/cloudformation/_package_description.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ current working directory. The exception is ``AWS::ApiGateway::RestApi``;
4949
if you don't specify a ``BodyS3Location``, this command will not upload an artifact to S3.
5050

5151
Before the command uploads artifacts, it checks if the artifacts are already
52-
present in the S3 bucket to prevent unnecessary uploads. The command uses MD5
53-
checksums to compare files. If the values match, the command doesn't upload the
54-
artifacts. Use the ``--force-upload flag`` to skip this check and always upload the
55-
artifacts.
52+
present in the S3 bucket to prevent unnecessary uploads. If the values match, the
53+
command doesn't upload the artifacts. Use the ``--force-upload flag`` to skip this
54+
check and always upload the artifacts. The command uses MD5 checksums to compare
55+
files by default. If MD5 is not available in the environment, a SHA256 checksum is used.
5656

tests/unit/customizations/test_s3uploader.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,27 @@ def test_file_checksum(self):
348348
finally:
349349
shutil.rmtree(tempdir)
350350

351+
@mock.patch("awscli.customizations.s3uploader.get_md5")
352+
def test_file_checksum_fips_fallback(self, get_md5_mock):
353+
num_chars = 4096*5
354+
data = ''.join(random.choice(string.ascii_uppercase)
355+
for _ in range(num_chars)).encode('utf-8')
356+
checksum = hashlib.sha256(usedforsecurity=False)
357+
checksum.update(data)
358+
expected_checksum = checksum.hexdigest()
359+
360+
tempdir = tempfile.mkdtemp()
361+
get_md5_mock.side_effect = botocore.exceptions.MD5UnavailableError()
362+
try:
363+
filename = os.path.join(tempdir, 'tempfile')
364+
with open(filename, 'wb') as f:
365+
f.write(data)
366+
367+
actual_checksum = self.s3uploader.file_checksum(filename)
368+
self.assertEqual(expected_checksum, actual_checksum)
369+
finally:
370+
shutil.rmtree(tempdir)
371+
351372
def test_make_url(self):
352373
path = "Hello/how/are/you"
353374
expected = f"s3://{self.bucket_name}/{path}"

0 commit comments

Comments
 (0)