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