Replace md5 for fips

md5 is not an approved algorithm in FIPS mode, and trying to
instantiate a hashlib.md5() will fail when the system is running in
FIPS mode.

md5 is allowed when in a non-security context.  There is a plan to
add a keyword parameter (usedforsecurity) to hashlib.md5() to annotate
whether or not the instance is being used in a security context.

In the case where it is not, the instantiation of md5 will be allowed.
See https://bugs.python.org/issue9216 for more details.

Some downstream python versions already support this parameter.  To
support these versions, a new encapsulation of md5() has been added to
oslo_utils.  See https://review.opendev.org/#/c/750031/

In this case, md5 is used to generate etags and to check file integrity when
uploading certs. fingerprints when ssh keys are
being generated and imported.  Without this patch, these operations
fail on FIPS enabled systems.

Change-Id: Ib189c6f67946851d37c31a6a8d657460c15f491e
This commit is contained in:
Ade Lee 2021-06-25 13:11:11 -04:00 committed by Douglas Mendizábal
parent e647f6d71a
commit db7a633a4f
7 changed files with 36 additions and 29 deletions

View File

@ -91,7 +91,7 @@ oslo.reports==1.18.0
oslo.serialization==2.28.1 oslo.serialization==2.28.1
oslo.service==1.30.0 oslo.service==1.30.0
oslo.upgradecheck==1.3.0 oslo.upgradecheck==1.3.0
oslo.utils==4.5.0 oslo.utils==4.7.0
oslotest==3.2.0 oslotest==3.2.0
packaging==20.4 packaging==20.4
paramiko==2.4.1 paramiko==2.4.1

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import hashlib
import io import io
import os import os
import re import re
@ -24,6 +23,7 @@ import flask
import jinja2 import jinja2
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils.secretutils import md5
import webob import webob
from werkzeug import exceptions from werkzeug import exceptions
@ -56,7 +56,7 @@ SYSTEMD_TEMPLATE = JINJA_ENV.get_template(SYSTEMD_CONF)
class Wrapped(object): class Wrapped(object):
def __init__(self, stream_): def __init__(self, stream_):
self.stream = stream_ self.stream = stream_
self.hash = hashlib.md5() # nosec self.hash = md5(usedforsecurity=False) # nosec
def read(self, line): def read(self, line):
block = self.stream.read(line) block = self.stream.read(line)
@ -86,7 +86,8 @@ class Loadbalancer(object):
cfg = file.read() cfg = file.read()
resp = webob.Response(cfg, content_type='text/plain') resp = webob.Response(cfg, content_type='text/plain')
resp.headers['ETag'] = ( resp.headers['ETag'] = (
hashlib.md5(octavia_utils.b(cfg)).hexdigest()) # nosec md5(octavia_utils.b(cfg),
usedforsecurity=False).hexdigest()) # nosec
return resp return resp
def upload_haproxy_config(self, amphora_id, lb_id): def upload_haproxy_config(self, amphora_id, lb_id):
@ -415,9 +416,10 @@ class Loadbalancer(object):
with open(cert_path, 'r') as crt_file: with open(cert_path, 'r') as crt_file:
cert = crt_file.read() cert = crt_file.read()
md5 = hashlib.md5(octavia_utils.b(cert)).hexdigest() # nosec md5sum = md5(octavia_utils.b(cert),
resp = webob.Response(json=dict(md5sum=md5)) usedforsecurity=False).hexdigest() # nosec
resp.headers['ETag'] = md5 resp = webob.Response(json=dict(md5sum=md5sum))
resp.headers['ETag'] = md5sum
return resp return resp
def delete_certificate(self, lb_id, filename): def delete_certificate(self, lb_id, filename):

View File

@ -21,6 +21,7 @@ import warnings
from oslo_context import context as oslo_context from oslo_context import context as oslo_context
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils.secretutils import md5
import requests import requests
import simplejson import simplejson
from stevedore import driver as stevedore_driver from stevedore import driver as stevedore_driver
@ -468,20 +469,21 @@ class HaproxyAmphoraLoadBalancerDriver(
if amphora and obj_id: if amphora and obj_id:
for cert in certs: for cert in certs:
pem = cert_parser.build_pem(cert) pem = cert_parser.build_pem(cert)
md5 = hashlib.md5(pem).hexdigest() # nosec md5sum = md5(pem, usedforsecurity=False).hexdigest() # nosec
name = '{id}.pem'.format(id=cert.id) name = '{id}.pem'.format(id=cert.id)
cert_filename_list.append( cert_filename_list.append(
os.path.join( os.path.join(
CONF.haproxy_amphora.base_cert_dir, obj_id, name)) CONF.haproxy_amphora.base_cert_dir, obj_id, name))
self._upload_cert(amphora, obj_id, pem, md5, name) self._upload_cert(amphora, obj_id, pem, md5sum, name)
if certs: if certs:
# Build and upload the crt-list file for haproxy # Build and upload the crt-list file for haproxy
crt_list = "\n".join(cert_filename_list) crt_list = "\n".join(cert_filename_list)
crt_list = f'{crt_list}\n'.encode('utf-8') crt_list = f'{crt_list}\n'.encode('utf-8')
md5 = hashlib.md5(crt_list).hexdigest() # nosec md5sum = md5(crt_list,
usedforsecurity=False).hexdigest() # nosec
name = '{id}.pem'.format(id=listener.id) name = '{id}.pem'.format(id=listener.id)
self._upload_cert(amphora, obj_id, crt_list, md5, name) self._upload_cert(amphora, obj_id, crt_list, md5sum, name)
return {'tls_cert': tls_cert, 'sni_certs': sni_certs} return {'tls_cert': tls_cert, 'sni_certs': sni_certs}
def _process_secret(self, listener, secret_ref, amphora=None, obj_id=None): def _process_secret(self, listener, secret_ref, amphora=None, obj_id=None):
@ -497,13 +499,13 @@ class HaproxyAmphoraLoadBalancerDriver(
secret = secret.encode('utf-8') secret = secret.encode('utf-8')
except AttributeError: except AttributeError:
pass pass
md5 = hashlib.md5(secret).hexdigest() # nosec md5sum = md5(secret, usedforsecurity=False).hexdigest() # nosec
id = hashlib.sha1(secret).hexdigest() # nosec id = hashlib.sha1(secret).hexdigest() # nosec
name = '{id}.pem'.format(id=id) name = '{id}.pem'.format(id=id)
if amphora and obj_id: if amphora and obj_id:
self._upload_cert( self._upload_cert(
amphora, obj_id, pem=secret, md5=md5, name=name) amphora, obj_id, pem=secret, md5sum=md5sum, name=name)
return name return name
def _process_listener_pool_certs(self, listener, amphora, obj_id): def _process_listener_pool_certs(self, listener, amphora, obj_id):
@ -536,10 +538,11 @@ class HaproxyAmphoraLoadBalancerDriver(
pem = pem.encode('utf-8') pem = pem.encode('utf-8')
except AttributeError: except AttributeError:
pass pass
md5 = hashlib.md5(pem).hexdigest() # nosec md5sum = md5(pem, usedforsecurity=False).hexdigest() # nosec
name = '{id}.pem'.format(id=tls_cert.id) name = '{id}.pem'.format(id=tls_cert.id)
if amphora and obj_id: if amphora and obj_id:
self._upload_cert(amphora, obj_id, pem=pem, md5=md5, name=name) self._upload_cert(amphora, obj_id, pem=pem,
md5sum=md5sum, name=name)
pool_cert_dict['client_cert'] = os.path.join( pool_cert_dict['client_cert'] = os.path.join(
CONF.haproxy_amphora.base_cert_dir, obj_id, name) CONF.haproxy_amphora.base_cert_dir, obj_id, name)
if pool.ca_tls_certificate_id: if pool.ca_tls_certificate_id:
@ -555,10 +558,10 @@ class HaproxyAmphoraLoadBalancerDriver(
return pool_cert_dict return pool_cert_dict
def _upload_cert(self, amp, listener_id, pem, md5, name): def _upload_cert(self, amp, listener_id, pem, md5sum, name):
try: try:
if self.clients[amp.api_version].get_cert_md5sum( if self.clients[amp.api_version].get_cert_md5sum(
amp, listener_id, name, ignore=(404,)) == md5: amp, listener_id, name, ignore=(404,)) == md5sum:
return return
except exc.NotFound: except exc.NotFound:
pass pass

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import hashlib
import os import os
import random import random
import socket import socket
@ -23,6 +22,7 @@ from unittest import mock
import fixtures import fixtures
from oslo_config import fixture as oslo_fixture from oslo_config import fixture as oslo_fixture
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils.secretutils import md5
from oslo_utils import uuidutils from oslo_utils import uuidutils
from octavia.amphorae.backends.agent import api_server from octavia.amphorae.backends.agent import api_server
@ -862,8 +862,8 @@ class TestServerTestCase(base.TestCase):
rv = self.centos_app.get('/' + api_server.VERSION + rv = self.centos_app.get('/' + api_server.VERSION +
'/loadbalancer/123/certificates/test.pem') '/loadbalancer/123/certificates/test.pem')
self.assertEqual(200, rv.status_code) self.assertEqual(200, rv.status_code)
self.assertEqual(dict(md5sum=hashlib.md5(octavia_utils. self.assertEqual(dict(md5sum=md5(octavia_utils.b(CONTENT),
b(CONTENT)).hexdigest()), usedforsecurity=False).hexdigest()),
jsonutils.loads(rv.data.decode('utf-8'))) jsonutils.loads(rv.data.decode('utf-8')))
def test_ubuntu_upload_certificate_md5(self): def test_ubuntu_upload_certificate_md5(self):

View File

@ -17,6 +17,7 @@ from unittest import mock
from oslo_config import cfg from oslo_config import cfg
from oslo_config import fixture as oslo_fixture from oslo_config import fixture as oslo_fixture
from oslo_utils.secretutils import md5
from oslo_utils import uuidutils from oslo_utils import uuidutils
import requests import requests
import requests_mock import requests_mock
@ -342,7 +343,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
mock_oslo.return_value = fake_context mock_oslo.return_value = fake_context
self.driver.cert_manager.get_secret.reset_mock() self.driver.cert_manager.get_secret.reset_mock()
self.driver.cert_manager.get_secret.return_value = fake_secret self.driver.cert_manager.get_secret.return_value = fake_secret
ref_md5 = hashlib.md5(fake_secret).hexdigest() # nosec ref_md5 = md5(fake_secret, usedforsecurity=False).hexdigest() # nosec
ref_id = hashlib.sha1(fake_secret).hexdigest() # nosec ref_id = hashlib.sha1(fake_secret).hexdigest() # nosec
ref_name = '{id}.pem'.format(id=ref_id) ref_name = '{id}.pem'.format(id=ref_id)
@ -356,7 +357,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
fake_context, sample_listener.client_ca_tls_certificate_id) fake_context, sample_listener.client_ca_tls_certificate_id)
mock_upload_cert.assert_called_once_with( mock_upload_cert.assert_called_once_with(
self.amp, sample_listener.id, pem=fake_secret, self.amp, sample_listener.id, pem=fake_secret,
md5=ref_md5, name=ref_name) md5sum=ref_md5, name=ref_name)
self.assertEqual(ref_name, result) self.assertEqual(ref_name, result)
@mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.' @mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.'
@ -406,7 +407,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
mock_load_certs.return_value = pool_data mock_load_certs.return_value = pool_data
fake_pem = b'fake pem' fake_pem = b'fake pem'
mock_build_pem.return_value = fake_pem mock_build_pem.return_value = fake_pem
ref_md5 = hashlib.md5(fake_pem).hexdigest() # nosec ref_md5 = md5(fake_pem, usedforsecurity=False).hexdigest() # nosec
ref_name = '{id}.pem'.format(id=pool_cert.id) ref_name = '{id}.pem'.format(id=pool_cert.id)
ref_path = '{cert_dir}/{list_id}/{name}'.format( ref_path = '{cert_dir}/{list_id}/{name}'.format(
cert_dir=fake_cert_dir, list_id=sample_listener.id, name=ref_name) cert_dir=fake_cert_dir, list_id=sample_listener.id, name=ref_name)
@ -437,7 +438,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
mock_build_pem.assert_called_once_with(pool_cert) mock_build_pem.assert_called_once_with(pool_cert)
mock_upload_cert.assert_called_once_with( mock_upload_cert.assert_called_once_with(
self.amp, sample_listener.id, pem=fake_pem, self.amp, sample_listener.id, pem=fake_pem,
md5=ref_md5, name=ref_name) md5sum=ref_md5, name=ref_name)
mock_secret.assert_has_calls(secret_calls) mock_secret.assert_has_calls(secret_calls)
self.assertEqual(ref_result, result) self.assertEqual(ref_result, result)

View File

@ -17,6 +17,7 @@ from unittest import mock
from oslo_config import cfg from oslo_config import cfg
from oslo_config import fixture as oslo_fixture from oslo_config import fixture as oslo_fixture
from oslo_utils.secretutils import md5
from oslo_utils import uuidutils from oslo_utils import uuidutils
import requests import requests
import requests_mock import requests_mock
@ -343,7 +344,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
mock_oslo.return_value = fake_context mock_oslo.return_value = fake_context
self.driver.cert_manager.get_secret.reset_mock() self.driver.cert_manager.get_secret.reset_mock()
self.driver.cert_manager.get_secret.return_value = fake_secret self.driver.cert_manager.get_secret.return_value = fake_secret
ref_md5 = hashlib.md5(fake_secret).hexdigest() # nosec ref_md5 = md5(fake_secret, usedforsecurity=False).hexdigest() # nosec
ref_id = hashlib.sha1(fake_secret).hexdigest() # nosec ref_id = hashlib.sha1(fake_secret).hexdigest() # nosec
ref_name = '{id}.pem'.format(id=ref_id) ref_name = '{id}.pem'.format(id=ref_id)
@ -357,7 +358,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
fake_context, sample_listener.client_ca_tls_certificate_id) fake_context, sample_listener.client_ca_tls_certificate_id)
mock_upload_cert.assert_called_once_with( mock_upload_cert.assert_called_once_with(
self.amp, sample_listener.id, pem=fake_secret, self.amp, sample_listener.id, pem=fake_secret,
md5=ref_md5, name=ref_name) md5sum=ref_md5, name=ref_name)
self.assertEqual(ref_name, result) self.assertEqual(ref_name, result)
@mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.' @mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.'
@ -407,7 +408,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
mock_load_certs.return_value = pool_data mock_load_certs.return_value = pool_data
fake_pem = b'fake pem' fake_pem = b'fake pem'
mock_build_pem.return_value = fake_pem mock_build_pem.return_value = fake_pem
ref_md5 = hashlib.md5(fake_pem).hexdigest() # nosec ref_md5 = md5(fake_pem, usedforsecurity=False).hexdigest() # nosec
ref_name = '{id}.pem'.format(id=pool_cert.id) ref_name = '{id}.pem'.format(id=pool_cert.id)
ref_path = '{cert_dir}/{lb_id}/{name}'.format( ref_path = '{cert_dir}/{lb_id}/{name}'.format(
cert_dir=fake_cert_dir, lb_id=sample_listener.load_balancer.id, cert_dir=fake_cert_dir, lb_id=sample_listener.load_balancer.id,
@ -439,7 +440,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
mock_build_pem.assert_called_once_with(pool_cert) mock_build_pem.assert_called_once_with(pool_cert)
mock_upload_cert.assert_called_once_with( mock_upload_cert.assert_called_once_with(
self.amp, sample_listener.load_balancer.id, pem=fake_pem, self.amp, sample_listener.load_balancer.id, pem=fake_pem,
md5=ref_md5, name=ref_name) md5sum=ref_md5, name=ref_name)
mock_secret.assert_has_calls(secret_calls) mock_secret.assert_has_calls(secret_calls)
self.assertEqual(ref_result, result) self.assertEqual(ref_result, result)

View File

@ -26,7 +26,7 @@ oslo.policy>=3.6.2 # Apache-2.0
oslo.reports>=1.18.0 # Apache-2.0 oslo.reports>=1.18.0 # Apache-2.0
oslo.serialization>=2.28.1 # Apache-2.0 oslo.serialization>=2.28.1 # Apache-2.0
oslo.upgradecheck>=1.3.0 # Apache-2.0 oslo.upgradecheck>=1.3.0 # Apache-2.0
oslo.utils>=4.5.0 # Apache-2.0 oslo.utils>=4.7.0 # Apache-2.0
pyasn1!=0.2.3,>=0.1.8 # BSD pyasn1!=0.2.3,>=0.1.8 # BSD
pyasn1-modules>=0.0.6 # BSD pyasn1-modules>=0.0.6 # BSD
python-barbicanclient>=4.5.2 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0