Support multiple ca certificates

Handle if there is ca certificates chain

Change-Id: I765e20b9f94cb51f52e5bd2ac427619338d25169
This commit is contained in:
Tao Zou 2022-05-12 16:14:01 +08:00
parent d761feadd7
commit 0f64b888ed
2 changed files with 155 additions and 28 deletions

View File

@ -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)

View File

@ -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 = []