Browse Source

Replace httplib2 with requests in metadata agent

httplib2 uses PROTOCOL_TLSv1 which in fact restricts
connection to TLS v1.0 only.
This patch moves to use requests library that uses
PROTOCOL_TLS, that supports all versions supported
SSL library.
Same change was made a while ago in neutron [0].

[0] https://review.opendev.org/#/c/593641/

Co-Authored-By: James Page <james.page@ubuntu.com>

Change-Id: I0f57a1706d41836b35ffd33bb154af6a5667b7cd
Closes-Bug: #1837870
(cherry picked from commit c3b9cdccd6)
changes/04/673504/3
Maciej Józefczyk 2 years ago
parent
commit
64122cdc68
  1. 62
      networking_ovn/agent/metadata/server.py
  2. 44
      networking_ovn/tests/unit/agent/metadata/test_server.py
  3. 1
      requirements.txt

62
networking_ovn/agent/metadata/server.py

@ -15,8 +15,8 @@
import hashlib
import hmac
import httplib2
from neutron.agent.linux import utils as agent_utils
from neutron.common import ipv6_utils
from neutron.conf.agent.metadata import config
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
@ -24,6 +24,7 @@ from neutron_lib.callbacks import resources
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import encodeutils
import requests
import six
import six.moves.urllib.parse as urlparse
import webob
@ -91,15 +92,15 @@ class MetadataProxyHandler(object):
def _proxy_request(self, instance_id, tenant_id, req):
headers = {
'X-Forwarded-For': req.headers.get('X-Forwarded-For'),
'X-Instance-ID': str(instance_id),
'X-Tenant-ID': str(tenant_id),
'X-Instance-ID': instance_id,
'X-Tenant-ID': tenant_id,
'X-Instance-ID-Signature': self._sign_instance_id(instance_id)
}
nova_host_port = '%s:%s' % (self.conf.nova_metadata_host,
self.conf.nova_metadata_port)
LOG.debug('Request to Nova at %s', nova_host_port)
LOG.debug(headers)
nova_host_port = ipv6_utils.valid_ipv6_url(
self.conf.nova_metadata_host,
self.conf.nova_metadata_port)
url = urlparse.urlunsplit((
self.conf.nova_metadata_protocol,
nova_host_port,
@ -107,43 +108,50 @@ class MetadataProxyHandler(object):
req.query_string,
''))
h = httplib2.Http(
ca_certs=self.conf.auth_ca_cert,
disable_ssl_certificate_validation=self.conf.nova_metadata_insecure
)
disable_ssl_certificate_validation = self.conf.nova_metadata_insecure
if self.conf.auth_ca_cert and not disable_ssl_certificate_validation:
verify_cert = self.conf.auth_ca_cert
else:
verify_cert = not disable_ssl_certificate_validation
client_cert = None
if self.conf.nova_client_cert and self.conf.nova_client_priv_key:
h.add_certificate(self.conf.nova_client_priv_key,
self.conf.nova_client_cert,
nova_host_port)
resp, content = h.request(url, method=req.method, headers=headers,
body=req.body)
if resp.status == 200:
req.response.content_type = resp['content-type']
req.response.body = content
client_cert = (self.conf.nova_client_cert,
self.conf.nova_client_priv_key)
resp = requests.request(method=req.method, url=url,
headers=headers,
data=req.body,
cert=client_cert,
verify=verify_cert)
if resp.status_code == 200:
req.response.content_type = resp.headers['content-type']
req.response.body = resp.content
LOG.debug(str(resp))
return req.response
elif resp.status == 403:
elif resp.status_code == 403:
LOG.warning(
'The remote metadata server responded with Forbidden. This '
'response usually occurs when shared secrets do not match.'
)
return webob.exc.HTTPForbidden()
elif resp.status == 400:
elif resp.status_code == 400:
return webob.exc.HTTPBadRequest()
elif resp.status == 404:
elif resp.status_code == 404:
return webob.exc.HTTPNotFound()
elif resp.status == 409:
elif resp.status_code == 409:
return webob.exc.HTTPConflict()
elif resp.status == 500:
elif resp.status_code == 500:
msg = _(
'Remote metadata server experienced an internal server error.'
)
LOG.debug(msg)
LOG.warning(msg)
explanation = six.text_type(msg)
return webob.exc.HTTPInternalServerError(explanation=explanation)
else:
raise Exception(_('Unexpected response code: %s') % resp.status)
raise Exception(_('Unexpected response code: %s') %
resp.status_code)
def _sign_instance_id(self, instance_id):
secret = self.conf.metadata_proxy_shared_secret

44
networking_ovn/tests/unit/agent/metadata/test_server.py

@ -140,37 +140,29 @@ class TestMetadataProxyHandler(base.BaseTestCase):
req = mock.Mock(path_info='/the_path', query_string='', headers=hdrs,
method=method, body=body)
resp = mock.MagicMock(status=response_code)
resp = mock.MagicMock(status_code=response_code)
resp.status.__str__.side_effect = AttributeError
resp.content = 'content'
req.response = resp
with mock.patch.object(self.handler, '_sign_instance_id') as sign:
sign.return_value = 'signed'
with mock.patch('httplib2.Http') as mock_http:
resp.__getitem__.return_value = "text/plain"
mock_http.return_value.request.return_value = (resp, 'content')
with mock.patch('requests.request') as mock_request:
resp.headers = {'content-type': 'text/plain'}
mock_request.return_value = resp
retval = self.handler._proxy_request('the_id', 'tenant_id',
req)
mock_http.assert_called_once_with(
ca_certs=None, disable_ssl_certificate_validation=True)
mock_http.assert_has_calls([
mock.call().add_certificate(
self.fake_conf.nova_client_priv_key,
self.fake_conf.nova_client_cert,
"%s:%s" % (self.fake_conf.nova_metadata_host,
self.fake_conf.nova_metadata_port)
),
mock.call().request(
'http://9.9.9.9:8775/the_path',
method=method,
headers={
'X-Forwarded-For': '8.8.8.8',
'X-Instance-ID-Signature': 'signed',
'X-Instance-ID': 'the_id',
'X-Tenant-ID': 'tenant_id'
},
body=body
)]
)
mock_request.assert_called_once_with(
method=method, url='http://9.9.9.9:8775/the_path',
headers={
'X-Forwarded-For': '8.8.8.8',
'X-Instance-ID-Signature': 'signed',
'X-Instance-ID': 'the_id',
'X-Tenant-ID': 'tenant_id'
},
data=body,
cert=(self.fake_conf.nova_client_cert,
self.fake_conf.nova_client_priv_key),
verify=False)
return retval

1
requirements.txt

@ -13,3 +13,4 @@ pyOpenSSL>=16.2.0 # Apache-2.0
tenacity>=3.2.1 # Apache-2.0
Babel!=2.4.0,>=2.3.4 # BSD
six>=1.10.0 # MIT
requests!=2.20.0,>=2.14.2 # Apache-2.0

Loading…
Cancel
Save