Skip to content

Commit 72cba71

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Warn when the provided checksum algorithm does not match the detected" into stable/2023.2
2 parents 7a402c5 + 38acd0a commit 72cba71

File tree

2 files changed

+64
-2
lines changed

2 files changed

+64
-2
lines changed

ironic_python_agent/extensions/standby.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,25 @@ def __init__(self, image_info, time_obj=None):
477477
self._expected_hash_value,
478478
image_info)
479479

480+
# NOTE(dtantsur): verify that the user's input does not obviously
481+
# contradict the actual value. It is especially easy to make such
482+
# a mistake when providing a checksum URL.
483+
if algo:
484+
try:
485+
detected_algo = _get_algorithm_by_length(
486+
self._expected_hash_value)
487+
except ValueError:
488+
pass # an exotic algorithm?
489+
else:
490+
if detected_algo.name != algo:
491+
LOG.warning("Provided checksum algorithm %(provided)s "
492+
"does not match the detected algorithm "
493+
"%(detected)s. It may be a sign of a user "
494+
"error when providing the algorithm or the "
495+
"checksum URL.",
496+
{'provided': algo,
497+
'detected': detected_algo.name})
498+
480499
details = []
481500
for url in image_info['urls']:
482501
try:

ironic_python_agent/tests/unit/extensions/test_standby.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -620,18 +620,21 @@ def test_verify_image_success_without_md5(self, requests_mock,
620620
image_download.verify_image(image_location)
621621
hashlib_mock.assert_called_with('sha512')
622622

623+
@mock.patch.object(standby.LOG, 'warning', autospec=True)
623624
@mock.patch('hashlib.new', autospec=True)
624625
@mock.patch('builtins.open', autospec=True)
625626
@mock.patch('requests.get', autospec=True)
626627
def test_verify_image_success_with_md5_fallback(self, requests_mock,
627-
open_mock, hash_mock):
628+
open_mock, hash_mock,
629+
warn_mock):
628630
CONF.set_override('md5_enabled', True)
629631
image_info = _build_fake_image_info()
630632
image_info['os_hash_algo'] = 'algo-beyond-milky-way'
631633
image_info['os_hash_value'] = 'mysterious-alien-codes'
632634
image_info['checksum'] = 'd41d8cd98f00b204e9800998ecf8427e'
633635
response = requests_mock.return_value
634636
response.status_code = 200
637+
hash_mock.return_value.name = 'md5'
635638
hexdigest_mock = hash_mock.return_value.hexdigest
636639
hexdigest_mock.return_value = image_info['checksum']
637640
image_location = '/foo/bar'
@@ -643,7 +646,11 @@ def test_verify_image_success_with_md5_fallback(self, requests_mock,
643646
hash_mock.assert_has_calls([
644647
mock.call('md5'),
645648
mock.call().__bool__(),
649+
mock.call('md5'),
646650
mock.call().hexdigest()])
651+
warn_mock.assert_called_once_with(
652+
mock.ANY,
653+
{'provided': 'algo-beyond-milky-way', 'detected': 'md5'})
647654

648655
@mock.patch('hashlib.new', autospec=True)
649656
@mock.patch('builtins.open', autospec=True)
@@ -1706,7 +1713,39 @@ def test_download_image_retries_success(self, sleep_mock, requests_mock,
17061713
sleep_mock.assert_called_with(10)
17071714
self.assertEqual(2, sleep_mock.call_count)
17081715

1709-
def test_download_image_and_checksum(self, requests_mock, hash_mock):
1716+
@mock.patch.object(standby.LOG, 'warning', autospec=True)
1717+
def test_download_image_and_checksum(self, warn_mock, requests_mock,
1718+
hash_mock):
1719+
content = ['SpongeBob', 'SquarePants']
1720+
fake_cs = "019fe036425da1c562f2e9f5299820bf"
1721+
cs_response = mock.Mock()
1722+
cs_response.status_code = 200
1723+
cs_response.text = fake_cs + '\n'
1724+
response = mock.Mock()
1725+
response.status_code = 200
1726+
response.iter_content.return_value = content
1727+
requests_mock.side_effect = [cs_response, response]
1728+
1729+
image_info = _build_fake_image_info()
1730+
image_info['os_hash_algo'] = 'sha512'
1731+
image_info['os_hash_value'] = 'http://example.com/checksum'
1732+
hash_mock.return_value.hexdigest.return_value = fake_cs
1733+
hash_mock.return_value.name = 'sha512'
1734+
image_download = standby.ImageDownload(image_info)
1735+
1736+
self.assertEqual(content, list(image_download))
1737+
requests_mock.assert_has_calls([
1738+
mock.call('http://example.com/checksum', cert=None, verify=True,
1739+
stream=True, proxies={}, timeout=60),
1740+
mock.call(image_info['urls'][0], cert=None, verify=True,
1741+
stream=True, proxies={}, timeout=60),
1742+
])
1743+
self.assertEqual(fake_cs, image_download._hash_algo.hexdigest())
1744+
warn_mock.assert_not_called()
1745+
1746+
@mock.patch.object(standby.LOG, 'warning', autospec=True)
1747+
def test_download_image_and_checksum_warning_on_mismatch(
1748+
self, warn_mock, requests_mock, hash_mock):
17101749
content = ['SpongeBob', 'SquarePants']
17111750
fake_cs = "019fe036425da1c562f2e9f5299820bf"
17121751
cs_response = mock.Mock()
@@ -1721,6 +1760,7 @@ def test_download_image_and_checksum(self, requests_mock, hash_mock):
17211760
image_info['os_hash_algo'] = 'sha512'
17221761
image_info['os_hash_value'] = 'http://example.com/checksum'
17231762
hash_mock.return_value.hexdigest.return_value = fake_cs
1763+
hash_mock.return_value.name = 'md5'
17241764
image_download = standby.ImageDownload(image_info)
17251765

17261766
self.assertEqual(content, list(image_download))
@@ -1731,6 +1771,9 @@ def test_download_image_and_checksum(self, requests_mock, hash_mock):
17311771
stream=True, proxies={}, timeout=60),
17321772
])
17331773
self.assertEqual(fake_cs, image_download._hash_algo.hexdigest())
1774+
warn_mock.assert_called_once_with(
1775+
mock.ANY,
1776+
{'provided': 'sha512', 'detected': 'md5'})
17341777

17351778
def test_download_image_and_checksum_md5(self, requests_mock, hash_mock):
17361779

0 commit comments

Comments
 (0)