diff --git a/CHANGELOG.md b/CHANGELOG.md index afafbe49..4723457e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ ## 1.34.0 [unreleased] +### Breaking Changes +1. [#509](https://github.com/influxdata/influxdb-client-python/pull/509): Rename `key_file` to `cert_key_file` inside the central [configuration class](https://github.com/influxdata/influxdb-client-python/blob/d011df72b528a45d305aa8accbe879b31be3280e/influxdb_client/configuration.py#L92) + ### Features 1. [#510](https://github.com/influxdata/influxdb-client-python/pull/510): Allow to use client's optional configs for initialization from file or environment properties +2. [#509](https://github.com/influxdata/influxdb-client-python/pull/509): MTLS support for the InfluxDB Python client ### Bug Fixes 1. [#512](https://github.com/influxdata/influxdb-client-python/pull/512): Exception propagation for asynchronous `QueryApi` [async/await] diff --git a/README.rst b/README.rst index 6d4358fb..88e05c16 100644 --- a/README.rst +++ b/README.rst @@ -197,6 +197,9 @@ The following options are supported: - ``timeout`` - socket timeout in ms (default value is 10000) - ``verify_ssl`` - set this to false to skip verifying SSL certificate when calling API from https server - ``ssl_ca_cert`` - set this to customize the certificate file to verify the peer +- ``cert_file`` - path to the certificate that will be used for mTLS authentication +- ``cert_key_file`` - path to the file contains private key for mTLS certificate +- ``cert_key_password`` - string or function which returns password for decrypting the mTLS private key - ``connection_pool_maxsize`` - set the number of connections to save that can be reused by urllib3 - ``auth_basic`` - enable http basic authentication when talking to a InfluxDB 1.8.x without authentication but is accessed via reverse proxy with basic authentication (defaults to false) - ``profilers`` - set the list of enabled `Flux profilers `_ @@ -226,6 +229,9 @@ Supported properties are: - ``INFLUXDB_V2_TIMEOUT`` - socket timeout in ms (default value is 10000) - ``INFLUXDB_V2_VERIFY_SSL`` - set this to false to skip verifying SSL certificate when calling API from https server - ``INFLUXDB_V2_SSL_CA_CERT`` - set this to customize the certificate file to verify the peer +- ``INFLUXDB_V2_CERT_FILE`` - path to the certificate that will be used for mTLS authentication +- ``INFLUXDB_V2_CERT_KEY_FILE`` - path to the file contains private key for mTLS certificate +- ``INFLUXDB_V2_CERT_KEY_PASSWORD`` - string or function which returns password for decrypting the mTLS private key - ``INFLUXDB_V2_CONNECTION_POOL_MAXSIZE`` - set the number of connections to save that can be reused by urllib3 - ``INFLUXDB_V2_AUTH_BASIC`` - enable http basic authentication when talking to a InfluxDB 1.8.x without authentication but is accessed via reverse proxy with basic authentication (defaults to false) - ``INFLUXDB_V2_PROFILERS`` - set the list of enabled `Flux profilers `_ diff --git a/influxdb_client/_async/rest.py b/influxdb_client/_async/rest.py index dcf678d0..8db95e91 100644 --- a/influxdb_client/_async/rest.py +++ b/influxdb_client/_async/rest.py @@ -83,15 +83,19 @@ def __init__(self, configuration, pools_size=4, maxsize=None, **kwargs): if maxsize is None: maxsize = configuration.connection_pool_maxsize - ssl_context = ssl.create_default_context(cafile=configuration.ssl_ca_cert) - if configuration.cert_file: - ssl_context.load_cert_chain( - configuration.cert_file, keyfile=configuration.key_file - ) - - if not configuration.verify_ssl: - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE + if configuration.ssl_context is None: + ssl_context = ssl.create_default_context(cafile=configuration.ssl_ca_cert) + if configuration.cert_file: + ssl_context.load_cert_chain( + certfile=configuration.cert_file, keyfile=configuration.cert_key_file, + password=configuration.cert_key_password + ) + + if not configuration.verify_ssl: + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + else: + ssl_context = configuration.ssl_context connector = aiohttp.TCPConnector( limit=maxsize, diff --git a/influxdb_client/_sync/rest.py b/influxdb_client/_sync/rest.py index d3ac55c6..bac94f9e 100644 --- a/influxdb_client/_sync/rest.py +++ b/influxdb_client/_sync/rest.py @@ -101,9 +101,11 @@ def __init__(self, configuration, pools_size=4, maxsize=None, retries=False): cert_reqs=cert_reqs, ca_certs=ca_certs, cert_file=configuration.cert_file, - key_file=configuration.key_file, + key_file=configuration.cert_key_file, + key_password=configuration.cert_key_password, proxy_url=configuration.proxy, proxy_headers=configuration.proxy_headers, + ssl_context=configuration.ssl_context, **addition_pool_args ) else: @@ -113,7 +115,9 @@ def __init__(self, configuration, pools_size=4, maxsize=None, retries=False): cert_reqs=cert_reqs, ca_certs=ca_certs, cert_file=configuration.cert_file, - key_file=configuration.key_file, + key_file=configuration.cert_key_file, + key_password=configuration.cert_key_password, + ssl_context=configuration.ssl_context, **addition_pool_args ) diff --git a/influxdb_client/client/_base.py b/influxdb_client/client/_base.py index 95aef3db..de2910da 100644 --- a/influxdb_client/client/_base.py +++ b/influxdb_client/client/_base.py @@ -60,6 +60,10 @@ def __init__(self, url, token, debug=None, timeout=10_000, enable_gzip=False, or self.conf.enable_gzip = enable_gzip self.conf.verify_ssl = kwargs.get('verify_ssl', True) self.conf.ssl_ca_cert = kwargs.get('ssl_ca_cert', None) + self.conf.cert_file = kwargs.get('cert_file', None) + self.conf.cert_key_file = kwargs.get('cert_key_file', None) + self.conf.cert_key_password = kwargs.get('cert_key_password', None) + self.conf.ssl_context = kwargs.get('ssl_context', None) self.conf.proxy = kwargs.get('proxy', None) self.conf.proxy_headers = kwargs.get('proxy_headers', None) self.conf.connection_pool_maxsize = kwargs.get('connection_pool_maxsize', self.conf.connection_pool_maxsize) @@ -142,6 +146,18 @@ def _has_section(key: str): if _has_option('ssl_ca_cert'): ssl_ca_cert = _config_value('ssl_ca_cert') + cert_file = None + if _has_option('cert_file'): + cert_file = _config_value('cert_file') + + cert_key_file = None + if _has_option('cert_key_file'): + cert_key_file = _config_value('cert_key_file') + + cert_key_password = None + if _has_option('cert_key_password'): + cert_key_password = _config_value('cert_key_password') + connection_pool_maxsize = None if _has_option('connection_pool_maxsize'): connection_pool_maxsize = _config_value('connection_pool_maxsize') @@ -168,6 +184,7 @@ def _has_section(key: str): return cls(url, token, debug=debug, timeout=_to_int(timeout), org=org, default_tags=default_tags, enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl), ssl_ca_cert=ssl_ca_cert, + cert_file=cert_file, cert_key_file=cert_key_file, cert_key_password=cert_key_password, connection_pool_maxsize=_to_int(connection_pool_maxsize), auth_basic=_to_bool(auth_basic), profilers=profilers, proxy=proxy, **kwargs) @@ -179,6 +196,9 @@ def _from_env_properties(cls, debug=None, enable_gzip=False, **kwargs): org = os.getenv('INFLUXDB_V2_ORG', "my-org") verify_ssl = os.getenv('INFLUXDB_V2_VERIFY_SSL', "True") ssl_ca_cert = os.getenv('INFLUXDB_V2_SSL_CA_CERT', None) + cert_file = os.getenv('INFLUXDB_V2_CERT_FILE', None) + cert_key_file = os.getenv('INFLUXDB_V2_CERT_KEY_FILE', None) + cert_key_password = os.getenv('INFLUXDB_V2_CERT_KEY_PASSWORD', None) connection_pool_maxsize = os.getenv('INFLUXDB_V2_CONNECTION_POOL_MAXSIZE', None) auth_basic = os.getenv('INFLUXDB_V2_AUTH_BASIC', "False") @@ -195,6 +215,7 @@ def _from_env_properties(cls, debug=None, enable_gzip=False, **kwargs): return cls(url, token, debug=debug, timeout=_to_int(timeout), org=org, default_tags=default_tags, enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl), ssl_ca_cert=ssl_ca_cert, + cert_file=cert_file, cert_key_file=cert_key_file, cert_key_password=cert_key_password, connection_pool_maxsize=_to_int(connection_pool_maxsize), auth_basic=_to_bool(auth_basic), profilers=profilers, **kwargs) diff --git a/influxdb_client/client/influxdb_client.py b/influxdb_client/client/influxdb_client.py index 042087da..91030774 100644 --- a/influxdb_client/client/influxdb_client.py +++ b/influxdb_client/client/influxdb_client.py @@ -40,6 +40,12 @@ def __init__(self, url, token: str = None, debug=None, timeout=10_000, enable_gz :param org: organization name (used as a default in Query, Write and Delete API) :key bool verify_ssl: Set this to false to skip verifying SSL certificate when calling API from https server. :key str ssl_ca_cert: Set this to customize the certificate file to verify the peer. + :key str cert_file: Path to the certificate that will be used for mTLS authentication. + :key str cert_key_file: Path to the file contains private key for mTLS certificate. + :key str cert_key_password: String or function which returns password for decrypting the mTLS private key. + :key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake. + Be aware that only delivered certificate/ key files or an SSL Context are + possible. :key str proxy: Set this to configure the http proxy to be used (ex. http://localhost:3128) :key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy authentication. @@ -89,6 +95,9 @@ def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gz authentication. :key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests except batching writes. As a default there is no one retry strategy. + :key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake. + Be aware that only delivered certificate/ key files or an SSL Context are + possible. The supported formats: - https://docs.python.org/3/library/configparser.html @@ -102,6 +111,9 @@ def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gz - timeout, - verify_ssl - ssl_ca_cert + - cert_file + - cert_key_file + - cert_key_password - connection_pool_maxsize - auth_basic - profilers @@ -177,6 +189,9 @@ def from_env_properties(cls, debug=None, enable_gzip=False, **kwargs): authentication. :key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests except batching writes. As a default there is no one retry strategy. + :key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake. + Be aware that only delivered certificate/ key files or an SSL Context are + possible. Supported environment properties: - INFLUXDB_V2_URL @@ -185,6 +200,9 @@ def from_env_properties(cls, debug=None, enable_gzip=False, **kwargs): - INFLUXDB_V2_TIMEOUT - INFLUXDB_V2_VERIFY_SSL - INFLUXDB_V2_SSL_CA_CERT + - INFLUXDB_V2_CERT_FILE + - INFLUXDB_V2_CERT_KEY_FILE + - INFLUXDB_V2_CERT_KEY_PASSWORD - INFLUXDB_V2_CONNECTION_POOL_MAXSIZE - INFLUXDB_V2_AUTH_BASIC - INFLUXDB_V2_PROFILERS diff --git a/influxdb_client/client/influxdb_client_async.py b/influxdb_client/client/influxdb_client_async.py index cdd8eade..96953cb3 100644 --- a/influxdb_client/client/influxdb_client_async.py +++ b/influxdb_client/client/influxdb_client_async.py @@ -32,6 +32,12 @@ def __init__(self, url, token: str = None, org: str = None, debug=None, timeout= supports the Gzip compression. :key bool verify_ssl: Set this to false to skip verifying SSL certificate when calling API from https server. :key str ssl_ca_cert: Set this to customize the certificate file to verify the peer. + :key str cert_file: Path to the certificate that will be used for mTLS authentication. + :key str cert_key_file: Path to the file contains private key for mTLS certificate. + :key str cert_key_password: String or function which returns password for decrypting the mTLS private key. + :key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake. + Be aware that only delivered certificate/ key files or an SSL Context are + possible. :key str proxy: Set this to configure the http proxy to be used (ex. http://localhost:3128) :key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy authentication. @@ -105,6 +111,9 @@ def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gz authentication. :key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests except batching writes. As a default there is no one retry strategy. + :key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake. + Be aware that only delivered certificate/ key files or an SSL Context are + possible. The supported formats: - https://docs.python.org/3/library/configparser.html @@ -118,6 +127,9 @@ def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gz - timeout, - verify_ssl - ssl_ca_cert + - cert_file + - cert_key_file + - cert_key_password - connection_pool_maxsize - auth_basic - profilers @@ -193,6 +205,10 @@ def from_env_properties(cls, debug=None, enable_gzip=False, **kwargs): authentication. :key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests except batching writes. As a default there is no one retry strategy. + :key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake. + Be aware that only delivered certificate/ key files or an SSL Context are + possible. + Supported environment properties: - INFLUXDB_V2_URL @@ -201,6 +217,9 @@ def from_env_properties(cls, debug=None, enable_gzip=False, **kwargs): - INFLUXDB_V2_TIMEOUT - INFLUXDB_V2_VERIFY_SSL - INFLUXDB_V2_SSL_CA_CERT + - INFLUXDB_V2_CERT_FILE + - INFLUXDB_V2_CERT_KEY_FILE + - INFLUXDB_V2_CERT_KEY_PASSWORD - INFLUXDB_V2_CONNECTION_POOL_MAXSIZE - INFLUXDB_V2_AUTH_BASIC - INFLUXDB_V2_PROFILERS diff --git a/influxdb_client/configuration.py b/influxdb_client/configuration.py index e9d95c78..3c118565 100644 --- a/influxdb_client/configuration.py +++ b/influxdb_client/configuration.py @@ -89,10 +89,15 @@ def __init__(self): # client certificate file self.cert_file = None # client key file - self.key_file = None + self.cert_key_file = None + # client key file password + self.cert_key_password = None # Set this to True/False to enable/disable SSL hostname verification. self.assert_hostname = None + # Set this to specify a custom ssl context to inject this context inside the urllib3 connection pool. + self.ssl_context = None + # urllib3 connection pool's maximum number of connections saved # per pool. urllib3 uses 1 connection as default value, but this is # not the best value when you are making a lot of possibly parallel diff --git a/tests/config-ssl-mtls-certs.ini b/tests/config-ssl-mtls-certs.ini new file mode 100644 index 00000000..0b3d6360 --- /dev/null +++ b/tests/config-ssl-mtls-certs.ini @@ -0,0 +1,14 @@ +[influx2] +url=http://localhost:8086 +org=my-org +token=my-token +timeout=6000 +ssl_ca_cert=/path/to/my/cert +cert_file=/path/to/my/cert +cert_key_file=/path/to/my/key +cert_key_password=test + +[tags] +id = 132-987-655 +customer = California Miner +data_center = ${env.data_center} \ No newline at end of file diff --git a/tests/test_InfluxDBClient.py b/tests/test_InfluxDBClient.py index c4b9b2a5..5202d7a9 100644 --- a/tests/test_InfluxDBClient.py +++ b/tests/test_InfluxDBClient.py @@ -3,6 +3,7 @@ import json import logging import os +import ssl import threading import unittest from io import StringIO @@ -57,6 +58,18 @@ def test_certificate_file(self): self.assertTrue(ping) + def test_certificate_context(self): + self._start_http_server() + + ssl_context = ssl.create_default_context(cafile=f"{os.path.dirname(__file__)}/server.pem") + + self.client = InfluxDBClient(f"https://localhost:{self.httpd.server_address[1]}", + token="my-token", verify_ssl=True, + ssl_context=ssl_context) + ping = self.client.ping() + + self.assertTrue(ping) + def test_init_from_ini_file(self): self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config.ini') @@ -141,6 +154,75 @@ def test_init_from_env_ssl_ca_cert(self): self.assertEqual("/my/custom/path/to/cert", self.client.api_client.configuration.ssl_ca_cert) + def test_init_from_env_ssl_cert_file(self): + os.environ["INFLUXDB_V2_CERT_FILE"] = "/my/custom/path" + self.client = InfluxDBClient.from_env_properties() + + self.assertEqual("/my/custom/path", self.client.api_client.configuration.cert_file) + + def test_init_from_file_ssl_cert_file_default(self): + self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config.ini') + + self.assertIsNone(self.client.api_client.configuration.cert_file) + + def test_init_from_file_ssl_cert_file(self): + self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config-ssl-mtls-certs.ini') + + self.assertEqual("/path/to/my/cert", self.client.api_client.configuration.cert_file) + + def test_init_from_env_ssl_cert_file_default(self): + if os.getenv("INFLUXDB_V2_CERT_FILE"): + del os.environ["INFLUXDB_V2_CERT_FILE"] + self.client = InfluxDBClient.from_env_properties() + + self.assertIsNone(self.client.api_client.configuration.cert_file) + + def test_init_from_env_ssl_cert_key(self): + os.environ["INFLUXDB_V2_CERT_KEY_FILE"] = "/my/custom/path" + self.client = InfluxDBClient.from_env_properties() + + self.assertEqual("/my/custom/path", self.client.api_client.configuration.cert_key_file) + + def test_init_from_file_ssl_cert_key_default(self): + self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config.ini') + + self.assertIsNone(self.client.api_client.configuration.cert_key_file) + + def test_init_from_file_ssl_cert_key(self): + self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config-ssl-mtls-certs.ini') + + self.assertEqual("/path/to/my/key", self.client.api_client.configuration.cert_key_file) + + def test_init_from_env_ssl_cert_key_default(self): + if os.getenv("INFLUXDB_V2_CERT_KEY_FILE"): + del os.environ["INFLUXDB_V2_CERT_KEY_FILE"] + self.client = InfluxDBClient.from_env_properties() + + self.assertIsNone(self.client.api_client.configuration.cert_key_file) + + def test_init_from_env_ssl_key_password(self): + os.environ["INFLUXDB_V2_CERT_KEY_PASSWORD"] = "test" + self.client = InfluxDBClient.from_env_properties() + + self.assertEqual("test", self.client.api_client.configuration.cert_key_password) + + def test_init_from_file_ssl_key_password_default(self): + self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config.ini') + + self.assertIsNone(self.client.api_client.configuration.cert_key_password) + + def test_init_from_file_ssl_key_password(self): + self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config-ssl-mtls-certs.ini') + + self.assertEqual("test", self.client.api_client.configuration.cert_key_password) + + def test_init_from_env_ssl_key_password_default(self): + if os.getenv("INFLUXDB_V2_CERT_KEY_PASSWORD"): + del os.environ["INFLUXDB_V2_CERT_KEY_PASSWORD"] + self.client = InfluxDBClient.from_env_properties() + + self.assertIsNone(self.client.api_client.configuration.cert_key_password) + def test_init_from_env_connection_pool_maxsize(self): os.environ["INFLUXDB_V2_CONNECTION_POOL_MAXSIZE"] = "29" self.client = InfluxDBClient.from_env_properties() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy