e915ad3a15
This patch adds the option to use SSL certificate validation on the backend controller using SSH-style sticky authentication, individual trusted certificates, and/or certificate authorities. Also adds caching of connections to deal with increased overhead of TLS/SSL handshake. Default is now sticky-style enforcement. Partial-Bug: 1188189 Implements: blueprint bsn-certificate-enforcement Change-Id: If0bab196495c4944a53e0e394c956cca36269883
187 lines
6.0 KiB
Python
187 lines
6.0 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2013 Big Switch Networks, Inc. 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. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License 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.
|
|
#
|
|
# @author: Kevin Benton, <kevin.benton@bigswitch.com>
|
|
#
|
|
|
|
import json
|
|
|
|
from neutron.openstack.common import log as logging
|
|
from neutron.plugins.bigswitch import servermanager
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class HTTPResponseMock():
|
|
status = 200
|
|
reason = 'OK'
|
|
|
|
def __init__(self, sock, debuglevel=0, strict=0, method=None,
|
|
buffering=False):
|
|
pass
|
|
|
|
def read(self):
|
|
return "{'status': '200 OK'}"
|
|
|
|
def getheader(self, header):
|
|
return None
|
|
|
|
|
|
class HTTPResponseMock404(HTTPResponseMock):
|
|
status = 404
|
|
reason = 'Not Found'
|
|
|
|
def read(self):
|
|
return "{'status': '%s 404 Not Found'}" % servermanager.NXNETWORK
|
|
|
|
|
|
class HTTPResponseMock500(HTTPResponseMock):
|
|
status = 500
|
|
reason = 'Internal Server Error'
|
|
|
|
def __init__(self, sock, debuglevel=0, strict=0, method=None,
|
|
buffering=False, errmsg='500 Internal Server Error'):
|
|
self.errmsg = errmsg
|
|
|
|
def read(self):
|
|
return "{'status': '%s'}" % self.errmsg
|
|
|
|
|
|
class HTTPConnectionMock(object):
|
|
|
|
def __init__(self, server, port, timeout):
|
|
self.response = None
|
|
self.broken = False
|
|
# Port 9000 is the broken server
|
|
if port == 9000:
|
|
self.broken = True
|
|
errmsg = "This server is broken, please try another"
|
|
self.response = HTTPResponseMock500(None, errmsg=errmsg)
|
|
|
|
def request(self, action, uri, body, headers):
|
|
LOG.debug(_("Request: action=%(action)s, uri=%(uri)r, "
|
|
"body=%(body)s, headers=%(headers)s"),
|
|
{'action': action, 'uri': uri,
|
|
'body': body, 'headers': headers})
|
|
if self.broken and "ExceptOnBadServer" in uri:
|
|
raise Exception("Broken server got an unexpected request")
|
|
if self.response:
|
|
return
|
|
|
|
# detachment may return 404 and plugin shouldn't die
|
|
if uri.endswith('attachment') and action == 'DELETE':
|
|
self.response = HTTPResponseMock404(None)
|
|
else:
|
|
self.response = HTTPResponseMock(None)
|
|
|
|
# Port creations/updates must contain binding information
|
|
if ('port' in uri and 'attachment' not in uri
|
|
and 'binding' not in body and action in ('POST', 'PUT')):
|
|
errmsg = "Port binding info missing in port request '%s'" % body
|
|
self.response = HTTPResponseMock500(None, errmsg=errmsg)
|
|
return
|
|
|
|
return
|
|
|
|
def getresponse(self):
|
|
return self.response
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
|
|
class HTTPConnectionMock404(HTTPConnectionMock):
|
|
|
|
def __init__(self, server, port, timeout):
|
|
self.response = HTTPResponseMock404(None)
|
|
self.broken = True
|
|
|
|
|
|
class HTTPConnectionMock500(HTTPConnectionMock):
|
|
|
|
def __init__(self, server, port, timeout):
|
|
self.response = HTTPResponseMock500(None)
|
|
self.broken = True
|
|
|
|
|
|
class VerifyMultiTenantFloatingIP(HTTPConnectionMock):
|
|
|
|
def request(self, action, uri, body, headers):
|
|
# Only handle network update requests
|
|
if 'network' in uri and 'tenant' in uri and 'ports' not in uri:
|
|
req = json.loads(body)
|
|
if 'network' not in req or 'floatingips' not in req['network']:
|
|
msg = _("No floating IPs in request"
|
|
"uri=%(uri)s, body=%(body)s") % {'uri': uri,
|
|
'body': body}
|
|
raise Exception(msg)
|
|
distinct_tenants = []
|
|
for flip in req['network']['floatingips']:
|
|
if flip['tenant_id'] not in distinct_tenants:
|
|
distinct_tenants.append(flip['tenant_id'])
|
|
if len(distinct_tenants) < 2:
|
|
msg = _("Expected floating IPs from multiple tenants."
|
|
"uri=%(uri)s, body=%(body)s") % {'uri': uri,
|
|
'body': body}
|
|
raise Exception(msg)
|
|
super(VerifyMultiTenantFloatingIP,
|
|
self).request(action, uri, body, headers)
|
|
|
|
|
|
class HTTPSMockBase(HTTPConnectionMock):
|
|
expected_cert = ''
|
|
combined_cert = None
|
|
|
|
def __init__(self, host, port=None, key_file=None, cert_file=None,
|
|
strict=None, timeout=None, source_address=None):
|
|
self.host = host
|
|
super(HTTPSMockBase, self).__init__(host, port, timeout)
|
|
|
|
def request(self, method, url, body=None, headers={}):
|
|
self.connect()
|
|
super(HTTPSMockBase, self).request(method, url, body, headers)
|
|
|
|
|
|
class HTTPSNoValidation(HTTPSMockBase):
|
|
|
|
def connect(self):
|
|
if self.combined_cert:
|
|
raise Exception('combined_cert set on NoValidation')
|
|
|
|
|
|
class HTTPSCAValidation(HTTPSMockBase):
|
|
expected_cert = 'DUMMYCERTIFICATEAUTHORITY'
|
|
|
|
def connect(self):
|
|
contents = get_cert_contents(self.combined_cert)
|
|
if self.expected_cert not in contents:
|
|
raise Exception('No dummy CA cert in cert_file')
|
|
|
|
|
|
class HTTPSHostValidation(HTTPSMockBase):
|
|
expected_cert = 'DUMMYCERTFORHOST%s'
|
|
|
|
def connect(self):
|
|
contents = get_cert_contents(self.combined_cert)
|
|
expected = self.expected_cert % self.host
|
|
if expected not in contents:
|
|
raise Exception(_('No host cert for %(server)s in cert %(cert)s'),
|
|
{'server': self.host, 'cert': contents})
|
|
|
|
|
|
def get_cert_contents(path):
|
|
raise Exception('METHOD MUST BE MOCKED FOR TEST')
|