1bbcc22d31
In order to support cert pinning in WCP, this change adds exact cert match for checking NSX manager authenticity. Setting "nsx_cert_der" enables this mode, where the pritotity is below ca cert and above thumbprints. Currently in nsxlib, the call chain to manage HTTPs connextion is: 1. NSXHTTPAdapter (subclass of urllib3 HTTPAdapter) 2. urllib3 PoolManager 3. urllib3 HTTPSConnectionPool 4. urllib3 HTTPSConnection In order to inject custom TLS cert validation, we have to override the connect() function in HTTPSConnection level. Introducing a child class of HTTPSConnectionPool is also needed to pass the new param. Pool manager only needs overrding two attrs to allow passing the new param and properly binding to the new child class of connection pool. When leaf cert verification is not used, the native urllib3 behavior will be kept to reduce regression risk. Change-Id: Icecf30b6df3b60fbeac20cf79586827f3370ce13
301 lines
15 KiB
Python
301 lines
15 KiB
Python
# Copyright 2016 VMware, 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.
|
|
|
|
from oslo_log import log
|
|
|
|
from requests import exceptions as requests_exceptions
|
|
|
|
from vmware_nsxlib.v3 import exceptions as v3_exceptions
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class ExceptionConfig(object):
|
|
|
|
def __init__(self):
|
|
# When hit during API call, these exceptions will mark
|
|
# endpoint as DOWN immediately
|
|
# This setting has no effect on keepalive validation
|
|
self.ground_triggers = [requests_exceptions.ConnectionError,
|
|
requests_exceptions.Timeout]
|
|
|
|
# When hit during API call, these exceptions will be
|
|
# retried with next available endpoint
|
|
# When hit during validation, these exception will not
|
|
# mark endpoint as DOWN
|
|
self.retriables = [v3_exceptions.APITransactionAborted,
|
|
v3_exceptions.CannotConnectToServer,
|
|
v3_exceptions.ServerBusy]
|
|
|
|
# When hit during API call, these exceptions will be retried
|
|
# after the endpoints are regenerated with up-to-date auth
|
|
# credentials / tokens
|
|
self.regenerate_triggers = [v3_exceptions.InvalidCredentials,
|
|
v3_exceptions.ClientCertificateNotTrusted,
|
|
v3_exceptions.BadXSRFToken]
|
|
|
|
def should_ground_endpoint(self, ex):
|
|
for exception in self.ground_triggers:
|
|
if isinstance(ex, exception):
|
|
return True
|
|
|
|
return False
|
|
|
|
def should_retry(self, ex):
|
|
for exception in self.retriables:
|
|
if isinstance(ex, exception):
|
|
return True
|
|
|
|
return False
|
|
|
|
def should_regenerate(self, ex):
|
|
for exception in self.regenerate_triggers:
|
|
if isinstance(ex, exception):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
class NsxLibConfig(object):
|
|
"""Class holding all the configuration parameters used by the nsxlib code.
|
|
|
|
:param nsx_api_managers: List of IP addresses of the NSX managers.
|
|
Each IP address should be of the form:
|
|
[<scheme>://]<ip_address>[:<port>]
|
|
If scheme is not provided https is used.
|
|
If port is not provided port 80 is used for http
|
|
and port 443 for https.
|
|
:param username: User name for the NSX manager
|
|
:param password: Password for the NSX manager
|
|
:param client_cert_provider: None, or ClientCertProvider object.
|
|
If specified, nsxlib will use client cert auth
|
|
instead of basic authentication.
|
|
:param insecure: If true, the NSX Manager server certificate is not
|
|
verified. If false the CA bundle specified via "ca_file"
|
|
will be used or if unset the "thumbprint" will be used.
|
|
If "thumbprint" is unset, the default system root CAs
|
|
will be used.
|
|
:param ca_file: Specify a CA bundle file to use in verifying the NSX
|
|
Manager server certificate. This option is ignored if
|
|
"insecure" is set to True. If "insecure" is set to False
|
|
and "ca_file" is unset, the "nsx_cert_der" will be used.
|
|
If "nsx_cert_der" is unset , "thumbprint" will be used.
|
|
If "thumbprint" is unset, the system root CAs will be
|
|
used to verify the server certificate.
|
|
:param thumbprint: Specify a thumbprint string to use in verifying the
|
|
NSX Manager server certificate. This option is ignored
|
|
if "insecure" is set to True, or defining either of
|
|
"ca_file", "nsx_cert_der".
|
|
:param token_provider: None, or instance of implemented AbstractJWTProvider
|
|
which will return the JSON Web Token used in the
|
|
requests in NSX for authorization.
|
|
|
|
:param concurrent_connections: Maximum concurrent connections to each NSX
|
|
manager.
|
|
:param retries: Maximum number of times to retry a HTTP connection.
|
|
:param http_timeout: The time in seconds before aborting a HTTP connection
|
|
to a NSX manager.
|
|
:param http_read_timeout: The time in seconds before aborting a HTTP read
|
|
response from a NSX manager.
|
|
:param conn_idle_timeout: The amount of time in seconds to wait before
|
|
ensuring connectivity to the NSX manager if no
|
|
manager connection has been used.
|
|
:param http_provider: HTTPProvider object, or None.
|
|
|
|
:param max_attempts: Maximum number of times to retry API requests upon
|
|
stale revision errors.
|
|
|
|
:param plugin_scope: The default scope for the v3 api-version tag
|
|
:param plugin_tag: The value for the v3 api-version tag
|
|
:param plugin_ver: The version of the plugin used as the 'os-api-version'
|
|
tag value in the v3 api-version tag
|
|
:param dns_nameservers: List of nameservers to configure for the DHCP
|
|
binding entries. These will be used if there are
|
|
no nameservers defined on the subnet.
|
|
:param dns_domain: Domain to use for building the hostnames.
|
|
:param allow_overwrite_header: If True, a default header of
|
|
X-Allow-Overwrite:true will be added to all
|
|
the requests, to allow admin user to update/
|
|
delete all entries.
|
|
:param rate_limit_retry: If True, the client will retry requests failed on
|
|
"Too many requests" error.
|
|
:param cluster_unavailable_retry: If True, skip fatal errors when no
|
|
endpoint in the NSX management cluster is
|
|
available to serve a request, and retry
|
|
the request instead. This setting can
|
|
not be False if single endpoint is
|
|
configured in the cluster, since there
|
|
will be no keepalive probes in this
|
|
case.
|
|
:param api_rate_limit_per_endpoint: If set to positive integer, API calls
|
|
sent to each endpoint will be limited
|
|
to a max rate of this value per second.
|
|
The rate limit is not enforced on
|
|
connection validations. This option
|
|
defaults to None, which disables rate
|
|
limit.
|
|
:param api_rate_mode: Algorithm used to adaptively adjust max API rate
|
|
limit. If not set, the max rate will not be
|
|
automatically changed. If set to 'AIMD', max API
|
|
rate will be increase by 1 after successful calls
|
|
that was blocked before sent, and will be decreased
|
|
by half after 429/503 error for each period.
|
|
The rate has hard max limit of min(100/s, param
|
|
api_rate_limit_per_endpoint).
|
|
:param api_log_mode: Option to collect API call logs within nsxlib.
|
|
When set to API_LOG_PER_CLUSTER, API calls sent to all
|
|
endpoints will be collected at one place.
|
|
When set to API_LOG_PER_ENDPOINT, API calls sent to
|
|
each endpoint will be collected individually.
|
|
By default, this option is disabled as set to None.
|
|
:param enable_health_check: Options to enable or disable health check for
|
|
all endpoints when initializing cluster API.
|
|
The checking including endpoint connection
|
|
validation and health check loop.
|
|
For some condition, eg election process. It
|
|
does not need to check the endpoint's
|
|
accessibility.
|
|
By default, this option is set to True.
|
|
:param nsx_cert_der: Specify one or a list of NSX manager leaf TLS
|
|
certificates in ASN1 / DER byte encoding to use for
|
|
verifying NSX Managers. This option is ignored
|
|
if "insecure" is set to True or "ca_file" is defined.
|
|
|
|
-- Additional parameters which are relevant only for the Policy manager:
|
|
:param allow_passthrough: If True, use nsx manager api for cases which are
|
|
not supported by the policy manager api.
|
|
:param realization_max_attempts: Maximum number of times to retry while
|
|
waiting for a resource to be realized.
|
|
:param realization_wait_sec: Number of seconds to wait between attempts
|
|
for a resource to be realized.
|
|
"""
|
|
|
|
def __init__(self,
|
|
nsx_api_managers=None,
|
|
username=None,
|
|
password=None,
|
|
client_cert_provider=None,
|
|
insecure=True,
|
|
ca_file=None,
|
|
thumbprint=None,
|
|
token_provider=None,
|
|
concurrent_connections=10,
|
|
retries=3,
|
|
http_timeout=10,
|
|
http_read_timeout=180,
|
|
conn_idle_timeout=10,
|
|
http_provider=None,
|
|
max_attempts=10,
|
|
plugin_scope=None,
|
|
plugin_tag=None,
|
|
plugin_ver=None,
|
|
dns_nameservers=None,
|
|
dns_domain='openstacklocal',
|
|
allow_overwrite_header=False,
|
|
rate_limit_retry=True,
|
|
cluster_unavailable_retry=False,
|
|
allow_passthrough=False,
|
|
realization_max_attempts=50,
|
|
realization_wait_sec=1.0,
|
|
api_rate_limit_per_endpoint=None,
|
|
api_rate_mode=None,
|
|
exception_config=None,
|
|
api_log_mode=None,
|
|
enable_health_check=True,
|
|
ssl_assert_hostname=None,
|
|
nsx_cert_der=None):
|
|
|
|
self.nsx_api_managers = nsx_api_managers
|
|
self._username = username
|
|
self._password = password
|
|
self._ca_file = ca_file
|
|
self._thumbprint = thumbprint
|
|
self.insecure = insecure
|
|
self.concurrent_connections = concurrent_connections
|
|
self.retries = retries
|
|
self.http_timeout = http_timeout
|
|
self.http_read_timeout = http_read_timeout
|
|
self.conn_idle_timeout = conn_idle_timeout
|
|
self.http_provider = http_provider
|
|
self.client_cert_provider = client_cert_provider
|
|
self.token_provider = token_provider
|
|
self.max_attempts = max_attempts
|
|
self.plugin_scope = plugin_scope
|
|
self.plugin_tag = plugin_tag
|
|
self.plugin_ver = plugin_ver
|
|
self.dns_nameservers = dns_nameservers or []
|
|
self.dns_domain = dns_domain
|
|
self.allow_overwrite_header = allow_overwrite_header
|
|
self.rate_limit_retry = rate_limit_retry
|
|
self.cluster_unavailable_retry = cluster_unavailable_retry
|
|
self.allow_passthrough = allow_passthrough
|
|
self.realization_max_attempts = realization_max_attempts
|
|
self.realization_wait_sec = realization_wait_sec
|
|
self.api_rate_limit_per_endpoint = api_rate_limit_per_endpoint
|
|
self.api_rate_mode = api_rate_mode
|
|
self.exception_config = exception_config or ExceptionConfig()
|
|
self.api_log_mode = api_log_mode
|
|
self.enable_health_check = enable_health_check
|
|
self.ssl_assert_hostname = ssl_assert_hostname
|
|
self._nsx_cert_der = nsx_cert_der
|
|
|
|
if len(nsx_api_managers) == 1 and not self.cluster_unavailable_retry:
|
|
LOG.warning("When only one endpoint is provided, keepalive probes"
|
|
" are disabled. For the system to be able to recover"
|
|
" from DOWN state, cluster_unavailable_retry is set"
|
|
" to True, overriding provided configuration")
|
|
self.cluster_unavailable_retry = True
|
|
|
|
if len(nsx_api_managers) > self.max_attempts:
|
|
LOG.warning("max_attempts setting (%d) is lower than amount of"
|
|
" endpoints (%d), which means that not all endpoints"
|
|
" will be probed in case of retriable error",
|
|
self.max_attempts, len(nsx_api_managers))
|
|
|
|
def extend(self, keepalive_section, validate_connection_method=None,
|
|
url_base=None):
|
|
if keepalive_section or validate_connection_method:
|
|
LOG.warning("keepalive_section and validate_connection_method are"
|
|
" no longer used to conduct keepalive probes. For"
|
|
" most efficient keepalive roundtrip, proxy health"
|
|
" API is always used.")
|
|
self.url_base = url_base
|
|
|
|
def _attribute_by_index(self, scalar_or_list, index):
|
|
if isinstance(scalar_or_list, list):
|
|
if not len(scalar_or_list):
|
|
return None
|
|
if len(scalar_or_list) > index:
|
|
return scalar_or_list[index]
|
|
# if not long enough - use the first one as default
|
|
return scalar_or_list[0]
|
|
# this is a scalar
|
|
return scalar_or_list
|
|
|
|
def username(self, index):
|
|
return self._attribute_by_index(self._username, index)
|
|
|
|
def password(self, index):
|
|
return self._attribute_by_index(self._password, index)
|
|
|
|
def ca_file(self, index):
|
|
return self._attribute_by_index(self._ca_file, index)
|
|
|
|
def thumbprint(self, index):
|
|
return self._attribute_by_index(self._thumbprint, index)
|
|
|
|
def nsx_cert_der(self, index):
|
|
return self._attribute_by_index(self._nsx_cert_der, index)
|