From 18c7ae89efc0a49a142fc5cd5bf0797fc3deb5f9 Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 24 Sep 2020 11:07:09 +0200 Subject: [PATCH 1/3] feat: added possibility to specify certificate file path to verify the peer --- README.rst | 2 ++ influxdb_client/client/influxdb_client.py | 15 +++++++++--- tests/config-ssl-ca-cert.ini | 11 +++++++++ tests/test_InfluxDBClient.py | 30 ++++++++++++++++++++--- 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tests/config-ssl-ca-cert.ini diff --git a/README.rst b/README.rst index 7ff0a5a8..55fa35ac 100644 --- a/README.rst +++ b/README.rst @@ -170,6 +170,7 @@ The following options are supported: - ``token`` - the token to use for the authorization - ``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 .. code-block:: python @@ -195,6 +196,7 @@ Supported properties are: - ``INFLUXDB_V2_TOKEN`` - the token to use for the authorization - ``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 .. code-block:: python diff --git a/influxdb_client/client/influxdb_client.py b/influxdb_client/client/influxdb_client.py index deab2cab..64775aef 100644 --- a/influxdb_client/client/influxdb_client.py +++ b/influxdb_client/client/influxdb_client.py @@ -33,6 +33,7 @@ def __init__(self, url, token, debug=None, timeout=10000, enable_gzip=False, org supports the Gzip compression. :param org: organization name (used as a default in query and write 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 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. @@ -52,6 +53,7 @@ def __init__(self, url, token, debug=None, timeout=10000, enable_gzip=False, org conf.enable_gzip = enable_gzip conf.debug = debug conf.verify_ssl = kwargs.get('verify_ssl', True) + conf.ssl_ca_cert = kwargs.get('ssl_ca_cert', None) auth_token = self.token auth_header_name = "Authorization" @@ -73,6 +75,7 @@ def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gz - token - timeout, - verify_ssl + - ssl_ca_cert """ config = configparser.ConfigParser() config.read(config_file) @@ -94,6 +97,10 @@ def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gz if config.has_option('influx2', 'verify_ssl'): verify_ssl = config['influx2']['verify_ssl'] + ssl_ca_cert = None + if config.has_option('influx2', 'ssl_ca_cert'): + ssl_ca_cert = config['influx2']['ssl_ca_cert'] + default_tags = None if config.has_section('tags'): @@ -101,10 +108,10 @@ def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gz if timeout: return cls(url, token, debug=debug, timeout=int(timeout), org=org, default_tags=default_tags, - enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl)) + enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl), ssl_ca_cert=ssl_ca_cert) return cls(url, token, debug=debug, org=org, default_tags=default_tags, enable_gzip=enable_gzip, - verify_ssl=_to_bool(verify_ssl)) + verify_ssl=_to_bool(verify_ssl), ssl_ca_cert=ssl_ca_cert) @classmethod def from_env_properties(cls, debug=None, enable_gzip=False): @@ -117,12 +124,14 @@ def from_env_properties(cls, debug=None, enable_gzip=False): - INFLUXDB_V2_TOKEN - INFLUXDB_V2_TIMEOUT - INFLUXDB_V2_VERIFY_SSL + - INFLUXDB_V2_SSL_CA_CERT """ url = os.getenv('INFLUXDB_V2_URL', "http://localhost:8086") token = os.getenv('INFLUXDB_V2_TOKEN', "my-token") timeout = os.getenv('INFLUXDB_V2_TIMEOUT', "10000") 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) default_tags = dict() @@ -131,7 +140,7 @@ def from_env_properties(cls, debug=None, enable_gzip=False): default_tags[key[16:].lower()] = value return cls(url, token, debug=debug, timeout=int(timeout), org=org, default_tags=default_tags, - enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl)) + enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl), ssl_ca_cert=ssl_ca_cert) def write_api(self, write_options=WriteOptions(), point_settings=PointSettings()) -> WriteApi: """ diff --git a/tests/config-ssl-ca-cert.ini b/tests/config-ssl-ca-cert.ini new file mode 100644 index 00000000..71722e6a --- /dev/null +++ b/tests/config-ssl-ca-cert.ini @@ -0,0 +1,11 @@ +[influx2] +url=http://localhost:8086 +org=my-org +token=my-token +timeout=6000 +ssl_ca_cert=/path/to/my/cert + +[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 34918d09..6d8aab71 100644 --- a/tests/test_InfluxDBClient.py +++ b/tests/test_InfluxDBClient.py @@ -60,16 +60,40 @@ def test_init_from_file_ssl(self): self.assertFalse(self.client.api_client.configuration.verify_ssl) def test_init_from_env_ssl_default(self): - del os.environ["INFLUXDB_V2_VERIFY_SSL"] + if os.getenv("INFLUXDB_V2_VERIFY_SSL"): + del os.environ["INFLUXDB_V2_VERIFY_SSL"] self.client = InfluxDBClient.from_env_properties() self.assertTrue(self.client.api_client.configuration.verify_ssl) def test_init_from_env_ssl(self): - os.environ["INFLUXDB_V2_VERIFY_SSL"] = "False" + os.environ["INFLUXDB_V2_SSL_CA_CERT"] = "/my/custom/path" self.client = InfluxDBClient.from_env_properties() - self.assertFalse(self.client.api_client.configuration.verify_ssl) + self.assertEqual("/my/custom/path", self.client.api_client.configuration.ssl_ca_cert) + + def test_init_from_file_ssl_ca_cert_default(self): + self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config.ini') + + self.assertIsNone(self.client.api_client.configuration.ssl_ca_cert) + + def test_init_from_file_ssl_ca_cert(self): + self.client = InfluxDBClient.from_config_file(f'{os.path.dirname(__file__)}/config-ssl-ca-cert.ini') + + self.assertEqual("/path/to/my/cert", self.client.api_client.configuration.ssl_ca_cert) + + def test_init_from_env_ssl_ca_cert_default(self): + if os.getenv("INFLUXDB_V2_SSL_CA_CERT"): + del os.environ["INFLUXDB_V2_SSL_CA_CERT"] + self.client = InfluxDBClient.from_env_properties() + + self.assertIsNone(self.client.api_client.configuration.ssl_ca_cert) + + def test_init_from_env_ssl_ca_cert(self): + os.environ["INFLUXDB_V2_SSL_CA_CERT"] = "/my/custom/path/to/cert" + self.client = InfluxDBClient.from_env_properties() + + self.assertEqual("/my/custom/path/to/cert", self.client.api_client.configuration.ssl_ca_cert) class ServerWithSelfSingedSSL(http.server.SimpleHTTPRequestHandler): From 3c8ddc3e66180ae8d2ba844166ef562f37a20fa8 Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 24 Sep 2020 11:11:59 +0200 Subject: [PATCH 2/3] docs: updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e82d3f..0ba083f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features 1. [#152](https://github.com/influxdata/influxdb-client-python/pull/152): WriteApi supports generic Iterable type +1. [#158](https://github.com/influxdata/influxdb-client-python/pull/158): Added possibility to specify certificate file path to verify the peer ### API 1. [#151](https://github.com/influxdata/influxdb-client-python/pull/151): Default port changed from 9999 -> 8086 From 365840083d587bd2b7441ad0044b68d652a44c8f Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 24 Sep 2020 11:16:35 +0200 Subject: [PATCH 3/3] chore: add test to `ssl_ca_cert` --- tests/test_InfluxDBClient.py | 38 ++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/tests/test_InfluxDBClient.py b/tests/test_InfluxDBClient.py index 6d8aab71..241fa9a4 100644 --- a/tests/test_InfluxDBClient.py +++ b/tests/test_InfluxDBClient.py @@ -25,24 +25,22 @@ def test_TrailingSlashInUrl(self): self.assertEqual('http://localhost:8086', self.client.api_client.configuration.host) def test_ConnectToSelfSignedServer(self): - import http.server - import ssl + self._start_http_server() - # Disable unverified HTTPS requests - import urllib3 - urllib3.disable_warnings() + self.client = InfluxDBClient(f"https://localhost:{self.httpd.server_address[1]}", + token="my-token", verify_ssl=False) + health = self.client.health() - # Configure HTTP server - self.httpd = http.server.HTTPServer(('localhost', 0), ServerWithSelfSingedSSL) - self.httpd.socket = ssl.wrap_socket(self.httpd.socket, certfile=f'{os.path.dirname(__file__)}/server.pem', - server_side=True) + self.assertEqual(health.message, 'ready for queries and writes') + self.assertEqual(health.status, "pass") + self.assertEqual(health.name, "influxdb") - # Start server at background - self.httpd_thread = threading.Thread(target=self.httpd.serve_forever) - self.httpd_thread.start() + def test_certificate_file(self): + self._start_http_server() self.client = InfluxDBClient(f"https://localhost:{self.httpd.server_address[1]}", - token="my-token", verify_ssl=False) + token="my-token", verify_ssl=True, + ssl_ca_cert=f'{os.path.dirname(__file__)}/server.pem') health = self.client.health() self.assertEqual(health.message, 'ready for queries and writes') @@ -95,6 +93,20 @@ 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 _start_http_server(self): + import http.server + import ssl + # Disable unverified HTTPS requests + import urllib3 + urllib3.disable_warnings() + # Configure HTTP server + self.httpd = http.server.HTTPServer(('localhost', 0), ServerWithSelfSingedSSL) + self.httpd.socket = ssl.wrap_socket(self.httpd.socket, certfile=f'{os.path.dirname(__file__)}/server.pem', + server_side=True) + # Start server at background + self.httpd_thread = threading.Thread(target=self.httpd.serve_forever) + self.httpd_thread.start() + class ServerWithSelfSingedSSL(http.server.SimpleHTTPRequestHandler): def _set_headers(self):