Support multiple ca certificates
Handle if there is ca certificates chain Change-Id: I765e20b9f94cb51f52e5bd2ac427619338d25169
This commit is contained in:
parent
d761feadd7
commit
0f64b888ed
|
@ -17,6 +17,7 @@ import unittest
|
|||
from unittest import mock
|
||||
from urllib import parse as urlparse
|
||||
|
||||
import requests
|
||||
from requests import codes
|
||||
from requests import exceptions as requests_exceptions
|
||||
from requests import models
|
||||
|
@ -580,3 +581,100 @@ class ClusteredAPITestCase(nsxlib_testcase.NsxClientTestCase):
|
|||
enable_health_check=False)
|
||||
self.assertEqual(cluster.ClusterHealth.GREEN, api.health)
|
||||
validate_fn.assert_not_called()
|
||||
|
||||
|
||||
class ProviderTestCase(unittest.TestCase):
|
||||
|
||||
cert1 = ("""-----BEGIN CERTIFICATE-----
|
||||
MIICWDCCAcGgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQENBQAw
|
||||
ZjEVMBMGA1UEAxMMaW50ZXJtZWRpYXRlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT
|
||||
CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh
|
||||
biBEaWVnbzAeFw0xNDA4MjgwMjEwNDhaFw0yNDA4MjUwMjEwNDhaMG4xHTAbBgNV
|
||||
BAMTFGludGVybWVkaWF0ZS1zZXJ2aWNlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT
|
||||
CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh
|
||||
biBEaWVnbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqpJZygd+w1faLOr1
|
||||
iOAmbBhx5SZWcTCZ/ZjHQTJM7GuPT624QkqsixFghRKdDROwpwnAP7gMRukLqiy4
|
||||
+kRuGT5OfyGggL95i2xqA+zehjj08lSTlvGHpePJgCyTavIy5+Ljsj4DKnKyuhxm
|
||||
biXTRrH83NDgixVkObTEmh/OVK0CAwEAATANBgkqhkiG9w0BAQ0FAAOBgQBa0Npw
|
||||
UkzjaYEo1OUE1sTI6Mm4riTIHMak4/nswKh9hYup//WVOlr/RBSBtZ7Q/BwbjobN
|
||||
3bfAtV7eSAqBsfxYXyof7G1ALANQERkq3+oyLP1iVt08W1WOUlIMPhdCF/QuCwy6
|
||||
x9MJLhUCGLJPM+O2rAPWVD9wCmvq10ALsiH3yA==
|
||||
-----END CERTIFICATE-----""")
|
||||
cert2 = ("""-----BEGIN CERTIFICATE-----
|
||||
MIICVzCCAcCgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQENBQAw
|
||||
WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAw
|
||||
DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTQw
|
||||
ODI4MDIwNDA4WhcNMjQwODI1MDIwNDA4WjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh
|
||||
dGUxDDAKBgNVBAoTA29yZzERMA8GA1UECxMIb3JnLXVuaXQxCzAJBgNVBAYTAlVT
|
||||
MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmK
|
||||
FGIbljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT
|
||||
21H2qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwID
|
||||
AQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAPIWSkLX
|
||||
QRMApOjjyC+tMxumT5e2pMqChHmxobQK4NMdrf2VCx+cRT6EmY8sK3/Xl/X8UBQ+
|
||||
9n5zXb1ZwhW/sTWgUvmOceJ4/XVs9FkdWOOn1J0XBch9ZIiFe/s5ASIgG7fUdcUF
|
||||
9mAWS6FK2ca3xIh5kIupCXOFa0dPvlw/YUFT
|
||||
-----END CERTIFICATE-----""")
|
||||
cert3 = ("""-----BEGIN CERTIFICATE-----
|
||||
MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE
|
||||
BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU
|
||||
ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2
|
||||
NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM
|
||||
MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U
|
||||
ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL
|
||||
urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy
|
||||
2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF
|
||||
1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE
|
||||
FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn
|
||||
VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE
|
||||
BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS
|
||||
b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB
|
||||
AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi
|
||||
hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY
|
||||
w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn
|
||||
-----END CERTIFICATE-----""")
|
||||
|
||||
def test_select_cert_one_cert(self):
|
||||
with mock.patch.object(requests.Session, 'request',
|
||||
return_value=get_sess_create_resp()):
|
||||
provider = cluster.Provider('9.8.7.6', 'https://9.8.7.6',
|
||||
None, None, "ca_file")
|
||||
provider._get_ca_files = mock.Mock()
|
||||
provider._get_ca_files.return_value = [self.cert1]
|
||||
provider.select_cert()
|
||||
self.assertEqual(provider.ca_file, "ca_file")
|
||||
|
||||
def test_self_signed_cert(self):
|
||||
provider = cluster.Provider('9.8.7.6', 'https://9.8.7.6',
|
||||
None, None, "ca_file")
|
||||
self.assertEqual(provider._self_signed_cert(self.cert1), False)
|
||||
self.assertEqual(provider._self_signed_cert(self.cert2), False)
|
||||
self.assertEqual(provider._self_signed_cert(self.cert3), True)
|
||||
|
||||
def test_select_cert_chain(self):
|
||||
# cert3 is self signed certificate
|
||||
# two ca chains will be put into two files
|
||||
# and second chain will be used
|
||||
with mock.patch.object(requests.Session, 'request',
|
||||
return_value=get_sess_create_resp()):
|
||||
provider = cluster.Provider('9.8.7.6', 'https://9.8.7.6',
|
||||
None, None, "ca_file")
|
||||
provider._get_ca_files = mock.Mock()
|
||||
provider._get_ca_files.return_value = [self.cert3,
|
||||
self.cert1,
|
||||
self.cert2, self.cert3]
|
||||
provider._verify_cert = mock.Mock()
|
||||
ssl_exception = requests.exceptions.SSLError()
|
||||
provider._verify_cert.side_effect = [ssl_exception,
|
||||
mock.DEFAULT]
|
||||
provider.select_cert()
|
||||
ca_file0 = '/tmp/ca_cert_9.8.7.6_0.pem'
|
||||
ca_file1 = '/tmp/ca_cert_9.8.7.6_1.pem'
|
||||
self.assertEqual(provider._verify_cert.call_count, 2)
|
||||
provider._verify_cert.assert_any_call([self.cert3],
|
||||
ca_file0)
|
||||
provider._verify_cert.assert_called_with([self.cert1,
|
||||
self.cert2,
|
||||
self.cert3],
|
||||
ca_file1)
|
||||
self.assertEqual(provider.ca_file, ca_file1)
|
||||
|
|
|
@ -28,12 +28,15 @@ from urllib import parse as urlparse
|
|||
import urllib3
|
||||
|
||||
import eventlet
|
||||
import OpenSSL
|
||||
import requests
|
||||
|
||||
from eventlet import greenpool
|
||||
from eventlet import pools
|
||||
import OpenSSL
|
||||
from OpenSSL.crypto import FILETYPE_PEM
|
||||
from OpenSSL.crypto import load_certificate
|
||||
from oslo_log import log
|
||||
from oslo_service import loopingcall
|
||||
import requests
|
||||
from requests import adapters
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.packages.urllib3.util.retry import Retry
|
||||
|
@ -45,7 +48,6 @@ from vmware_nsxlib.v3.debug_retry import RetryDebug
|
|||
from vmware_nsxlib.v3 import exceptions
|
||||
from vmware_nsxlib.v3 import utils
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
# disable warning message for each HTTP retry
|
||||
|
@ -389,12 +391,27 @@ class Provider(object):
|
|||
def __str__(self):
|
||||
return str(self.url)
|
||||
|
||||
def _verify_cert(self, buffs, ca_file):
|
||||
with open(ca_file, 'w') as fname:
|
||||
for buff in buffs:
|
||||
fname.writelines(buff)
|
||||
session = requests.Session()
|
||||
retry_strategy = CAVerifyRetry(total=6, backoff_factor=1,
|
||||
method_whitelist=["GET"])
|
||||
adaptor = HTTPAdapter(max_retries=retry_strategy)
|
||||
session.mount('https://', adaptor)
|
||||
session.verify = ca_file
|
||||
session.get(self.url, timeout=60)
|
||||
|
||||
def select_cert(self):
|
||||
# If two ca certs in one file which only 'Serial Number' are different,
|
||||
# ssl verify process will break if the first cert is not enabled in
|
||||
# the nsxt. Put only one ca cert in one file and verify it by GET
|
||||
# operation. Switch between the certificates after bootup need to
|
||||
# reboot client
|
||||
# If two certificate chains in one file which only 'Serial Number' are
|
||||
# different, ssl verify process will break if the first certificate
|
||||
# chain not enabled in the nsxt. Put only one certificate chain in one
|
||||
# file and verify it by GET operation. Switch between the certificates
|
||||
# after bootup need to reboot client.
|
||||
# Treat single certificate(self signed certificate) as a certificate
|
||||
# chain. Root CA is also self signed certificate. So we separate the
|
||||
# ca certificate chains by finding the self signed certificate
|
||||
if not self.ca_file:
|
||||
return
|
||||
|
||||
|
@ -408,27 +425,39 @@ class Provider(object):
|
|||
return
|
||||
|
||||
base_file = '/tmp/ca_cert'
|
||||
for index, buff in enumerate(ca_content):
|
||||
ca_chains = []
|
||||
index = 0
|
||||
for buff in ca_content:
|
||||
ca_file = '{}_{}_{}.pem'.format(base_file, self.id, str(index))
|
||||
try:
|
||||
with open(ca_file, 'w') as fname:
|
||||
fname.writelines(buff)
|
||||
session = requests.Session()
|
||||
retry_strategy = CAVerifyRetry(total=6, backoff_factor=1,
|
||||
method_whitelist=["GET"])
|
||||
adaptor = HTTPAdapter(max_retries=retry_strategy)
|
||||
session.mount('https://', adaptor)
|
||||
session.verify = ca_file
|
||||
session.get(self.url, timeout=60)
|
||||
self.ca_file = ca_file
|
||||
break
|
||||
except requests.exceptions.SSLError as e:
|
||||
LOG.debug("verification for ca_file %s failed. Error: %s",
|
||||
ca_file, e)
|
||||
continue
|
||||
except IOError as e:
|
||||
LOG.debug("write ca_file %s failed. Error: %s",
|
||||
ca_file, e)
|
||||
ca_chains.append(buff)
|
||||
if self._self_signed_cert(buff):
|
||||
index += 1
|
||||
try:
|
||||
self._verify_cert(ca_chains, ca_file)
|
||||
self.ca_file = ca_file
|
||||
break
|
||||
except requests.exceptions.SSLError as e:
|
||||
LOG.debug("verification for ca_file %s failed. Error: %s",
|
||||
ca_file, e)
|
||||
continue
|
||||
except IOError as e:
|
||||
LOG.debug("write ca_file %s failed. Error: %s",
|
||||
ca_file, e)
|
||||
finally:
|
||||
ca_chains = []
|
||||
|
||||
def _self_signed_cert(self, buff):
|
||||
# Return True if self signed cert,
|
||||
# return False if not
|
||||
# Root CA is a self signed certificate
|
||||
certificate = ""
|
||||
for line in buff:
|
||||
certificate += line
|
||||
cert = load_certificate(FILETYPE_PEM, certificate)
|
||||
issuer = cert.get_issuer()
|
||||
subject = cert.get_subject()
|
||||
LOG.debug("check certificate issuer %s, subject %s", issuer, subject)
|
||||
return issuer == subject
|
||||
|
||||
def _get_ca_files(self, ca_file):
|
||||
files = []
|
||||
|
|
Loading…
Reference in New Issue