Skip to content

feat: MTLS support for the InfluxDB Python client #509

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://docs.influxdata.com/influxdb/v2.0/reference/flux/stdlib/profiler/>`_
Expand Down Expand Up @@ -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 <https://docs.influxdata.com/influxdb/v2.0/reference/flux/stdlib/profiler/>`_
Expand Down
22 changes: 13 additions & 9 deletions influxdb_client/_async/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 6 additions & 2 deletions influxdb_client/_sync/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
)

Expand Down
21 changes: 21 additions & 0 deletions influxdb_client/client/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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')
Expand All @@ -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)

Expand All @@ -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")

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

Expand Down
18 changes: 18 additions & 0 deletions influxdb_client/client/influxdb_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
19 changes: 19 additions & 0 deletions influxdb_client/client/influxdb_client_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
7 changes: 6 additions & 1 deletion influxdb_client/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions tests/config-ssl-mtls-certs.ini
Original file line number Diff line number Diff line change
@@ -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}
Loading