Skip to content

Commit 95477f8

Browse files
authored
Merge pull request #121 from InfluxCommunity/fix/117-ssl-config
fix: issue 117 ssl config
2 parents 19c1c25 + d6ae2ba commit 95477f8

File tree

6 files changed

+401
-7
lines changed

6 files changed

+401
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## 0.12.0 [unreleased]
44

5+
### Bug Fixes
6+
7+
1. [#121](https://github.com/InfluxCommunity/influxdb3-python/pull/121): Fix use of arguments `verify_ssl` and `ssl_ca_cert` in `QueryApi`.
8+
59
## 0.11.0 [2025-02-27]
610

711
### Bug Fixes

Examples/basic_ssl_example.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import os
2+
import time
3+
4+
import pyarrow
5+
6+
from config import Config
7+
from influxdb_client_3 import InfluxDBClient3
8+
9+
bad_cert = """-----BEGIN CERTIFICATE-----
10+
MIIFDTCCAvWgAwIBAgIUYzpfisy9xLrhiZd+D9vOdzC3+iswDQYJKoZIhvcNAQEL
11+
BQAwFjEUMBIGA1UEAwwLdGVzdGhvc3QuaW8wHhcNMjUwMjI4MTM1NTMyWhcNMzUw
12+
MjI2MTM1NTMyWjAWMRQwEgYDVQQDDAt0ZXN0aG9zdC5pbzCCAiIwDQYJKoZIhvcN
13+
AQEBBQADggIPADCCAgoCggIBAN1lwqXYP8UMvjb56SpUEj2OpoEDRfLeWrEiHkOl
14+
xoymvJGaXZNEpDXo2TTdysCoYWEjz9IY6GlqSo2Yssf5BZkQwMOw7MdyRwCigzrh
15+
OAKbyCfsvEgfNFrXEdSDpaxW++5SToeErudYXc+sBfnI1NB4W3GBGqqIvx8fqaB3
16+
1EU9ql2sKKxI0oYIQD/If9rQEyLFKeWdD8iT6YST1Vugkvd34NPmaqV5+pjdSb4z
17+
a8olavwUoslqFUeILqIq+WZZbOlgCcJYKcBAmELRnsxGaABRtMwMZx+0D+oKo4Kl
18+
QQtOcER+RHkBHyYFghZIBnzudfbP9NadknOz3AilJbJolXfXJqeQhRD8Ob49kkhe
19+
OwjAppHnaZGWjYZMLIfnwwXBwkS7bSwF16Wot83cpL46Xvg6xcl12An4JaoF798Q
20+
cXyYrWCgvbqjVR7694gxqLGzk138AKTDSbER1h1rfqCqkk7soE0oWCs7jiCk2XvD
21+
49qVfHtd50KYJ4/yP1XL0PmLL0Hw1kvOxLVkFENc1zkoYXJRt2Ec6j9dajmGlsFn
22+
0bLLap6UIlIGQFuvcLf4bvsIi9FICy2jBjaIdM4UAWbReG+52+180HEleAwi5bAN
23+
HY61WVXc4X+N0E2y8HWc1QaRioU7R4XZ5HXKs7OTWkKFZUU2JDFHAKdiiAU78qLU
24+
7GApAgMBAAGjUzBRMB0GA1UdDgQWBBT2vPFo0mzh9ls4xJUiAgSK+B5LpTAfBgNV
25+
HSMEGDAWgBT2vPFo0mzh9ls4xJUiAgSK+B5LpTAPBgNVHRMBAf8EBTADAQH/MA0G
26+
CSqGSIb3DQEBCwUAA4ICAQC4TJNPx476qhiMi8anISv9lo9cnLju+qNhcz7wupBH
27+
3Go6bVQ7TCbSt2QpAyY64mdnRqHsXeGvZXCnabOpeKRDeAPBtRjc6yNKuXybqFtn
28+
W3PZEs/OYc659TUA+MoBzSXYStN9yiiYXyVFqVn+Rw6kM9tKh0GgAU7f5P+8IGuR
29+
gXJbCjkbdJO7JUiVGEEmkjUHyqFxMHaZ8V6uazs52qIFyt7OYQTeV9HdoW8D9vAt
30+
GfzYwzRDzbsZeIJqqDzLe7NOyxEyqZHCbtNpGcOyaLOl7ZBS52WsqaUZtL+9PjqD
31+
2TWj4WUFkOWQpTvWKHqM6//Buv4GjnTBShQKm+h+rxcGkdRMF6/sKwxPbr39P3RJ
32+
TMfJA3u5UuowT44VaA2jkQzqIbxH9+3EA+0qPbqPJchOSr0pHSncqvR9FYcr7ayN
33+
b6UDFnjeliyEqqksUO0arbvaO9FfB0kH8lU1NOKaQNO++Xj69GZMC6s721cNdad0
34+
qqcdtyXWeOBBchguYDrSUIgLnUTHEwwzOmcNQ36hO5eX282BJy3ZLT3JU6MJopjz
35+
vkbDDAxSrpZMcaoAWSrxgJAETeYiO4YbfORIzPkwdUkEIr6XY02Pi7MdkDGQ5hiB
36+
TavA8+oXRa4b9BR3bCWcg8S/t4uOTTLkeTcQbONPh5A5IRySLCU+CwqB+/+VlO8X
37+
Aw==
38+
-----END CERTIFICATE-----"""
39+
40+
41+
def write_cert(cert, file_name):
42+
f = open(file_name, "w")
43+
f.write(cert)
44+
f.close()
45+
46+
47+
def remove_cert(file_name):
48+
os.remove(file_name)
49+
50+
51+
def print_results(results: list):
52+
print("%-6s%-6s%-6s%-24s" % ("id", "speed", "ticks", "time"))
53+
for result in results:
54+
print("%-6s%-6.2f%-6i%-24s" % (result['id'], result['speed'], result['ticks'], result['time']))
55+
56+
57+
def main() -> None:
58+
print("Main")
59+
temp_cert_file = "temp_cert.pem"
60+
conf = Config()
61+
62+
write_and_query_with_explicit_sys_cert(conf)
63+
64+
write_cert(bad_cert, temp_cert_file)
65+
query_with_verify_ssl_off(conf, temp_cert_file)
66+
remove_cert(temp_cert_file)
67+
68+
69+
def write_and_query_with_explicit_sys_cert(conf):
70+
print("\nwrite and query with typical linux system cert\n")
71+
with InfluxDBClient3(token=conf.token,
72+
host=conf.host,
73+
org=conf.org,
74+
database=conf.database,
75+
ssl_ca_cert="/etc/ssl/certs/ca-certificates.crt",
76+
verify_ssl=True) as _client:
77+
now = time.time_ns()
78+
lp = f"escooter,id=zx80 speed=3.14,ticks=42i {now - (10 * 1_000_000_000)}"
79+
_client.write(lp)
80+
81+
query = "SELECT * FROM \"escooter\" ORDER BY time DESC"
82+
reader: pyarrow.Table = _client.query(query, mode="")
83+
print_results(reader.to_pylist())
84+
85+
86+
def query_with_verify_ssl_off(conf, cert):
87+
print("\nquerying with verify_ssl off\n")
88+
89+
# Note that the passed root cert above is bad
90+
# Switch verify_ssl to True to throw SSL_ERROR_SSL
91+
with InfluxDBClient3(token=conf.token,
92+
host=conf.host,
93+
org=conf.org,
94+
database=conf.database,
95+
ssl_ca_cert=cert,
96+
verify_ssl=False) as _client:
97+
98+
query = "SELECT * FROM \"escooter\" ORDER BY time DESC"
99+
reader: pyarrow.Table = _client.query(query, mode="")
100+
print_results(reader.to_pylist())
101+
102+
103+
if __name__ == "__main__":
104+
main()

influxdb_client_3/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import pyarrow as pa
33
import importlib.util
44

5-
from influxdb_client_3.query.query_api import QueryApi as _QueryApi
5+
from influxdb_client_3.query.query_api import QueryApi as _QueryApi, QueryApiOptionsBuilder
66
from influxdb_client_3.read_file import UploadFile
77
from influxdb_client_3.write_client import InfluxDBClient as _InfluxDBClient, WriteOptions, Point
88
from influxdb_client_3.write_client.client.exceptions import InfluxDBError
@@ -165,9 +165,18 @@ def __init__(
165165
connection_string = f"grpc+tls://{hostname}:{port}"
166166
else:
167167
connection_string = f"grpc+tcp://{hostname}:{port}"
168+
169+
q_opts_builder = QueryApiOptionsBuilder()
170+
kw_keys = kwargs.keys()
171+
if kw_keys.__contains__('ssl_ca_cert'):
172+
q_opts_builder.root_certs(kwargs.get('ssl_ca_cert', None))
173+
if kw_keys.__contains__('verify_ssl'):
174+
q_opts_builder.tls_verify(kwargs.get('verify_ssl', True))
175+
if kw_keys.__contains__('proxy'):
176+
q_opts_builder.proxy(kwargs.get('proxy', None))
168177
self._query_api = _QueryApi(connection_string=connection_string, token=token,
169178
flight_client_options=flight_client_options,
170-
proxy=kwargs.get("proxy", None))
179+
proxy=kwargs.get("proxy", None), options=q_opts_builder.build())
171180

172181
def write(self, record=None, database=None, **kwargs):
173182
"""

influxdb_client_3/query/query_api.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,96 @@
77
from influxdb_client_3.version import USER_AGENT
88

99

10+
class QueryApiOptions(object):
11+
"""
12+
Structure for encapsulating options for the QueryApi
13+
14+
Attributes
15+
----------
16+
tls_root_certs (bytes): contents of an SSL root certificate or chain read as bytes
17+
tls_verify (bool): whether to verify SSL certificates or not
18+
proxy (str): URL to a proxy server
19+
flight_client_options (dict): base set of flight client options passed to internal pyarrow.flight.FlightClient
20+
"""
21+
tls_root_certs: bytes = None
22+
tls_verify: bool = None
23+
proxy: str = None
24+
flight_client_options: dict = None
25+
26+
def __init__(self, root_certs_path, verify, proxy, flight_client_options):
27+
"""
28+
Initialize a set of QueryApiOptions
29+
30+
:param root_certs_path: path to a certificate .pem file.
31+
:param verify: whether to verify SSL certificates or not.
32+
:param proxy: URL of a proxy server, if required.
33+
:param flight_client_options: set of flight_client_options
34+
to be passed to internal pyarrow.flight.FlightClient.
35+
"""
36+
if root_certs_path:
37+
self.tls_root_certs = self._read_certs(root_certs_path)
38+
self.tls_verify = verify
39+
self.proxy = proxy
40+
self.flight_client_options = flight_client_options
41+
42+
def _read_certs(self, path):
43+
with open(path, "rb") as certs_file:
44+
return certs_file.read()
45+
46+
47+
class QueryApiOptionsBuilder(object):
48+
"""
49+
Helper class to make adding QueryApiOptions more dynamic.
50+
51+
Example:
52+
53+
.. code-block:: python
54+
55+
options = QueryApiOptionsBuilder()\
56+
.proxy("http://internal.tunnel.proxy:8080") \
57+
.root_certs("/home/fred/.etc/ssl/alt_certs.pem") \
58+
.tls_verify(True) \
59+
.build()
60+
61+
client = QueryApi(connection, token, None, None, options)
62+
"""
63+
_root_certs_path = None
64+
_tls_verify = True
65+
_proxy = None
66+
_flight_client_options = None
67+
68+
def root_certs(self, path):
69+
self._root_certs_path = path
70+
return self
71+
72+
def tls_verify(self, verify):
73+
self._tls_verify = verify
74+
return self
75+
76+
def proxy(self, proxy):
77+
self._proxy = proxy
78+
return self
79+
80+
def flight_client_options(self, flight_client_options):
81+
self._flight_client_options = flight_client_options
82+
return self
83+
84+
def build(self):
85+
"""Build a QueryApiOptions object with previously set values"""
86+
return QueryApiOptions(
87+
root_certs_path=self._root_certs_path,
88+
verify=self._tls_verify,
89+
proxy=self._proxy,
90+
flight_client_options=self._flight_client_options
91+
)
92+
93+
1094
class QueryApi(object):
1195
"""
1296
Implementation for '/api/v2/query' endpoint.
1397
1498
Example:
99+
15100
.. code-block:: python
16101
17102
from influxdb_client import InfluxDBClient
@@ -26,7 +111,7 @@ def __init__(self,
26111
connection_string,
27112
token,
28113
flight_client_options,
29-
proxy=None) -> None:
114+
proxy=None, options=None) -> None:
30115
"""
31116
Initialize defaults.
32117
@@ -37,6 +122,15 @@ def __init__(self,
37122
self._token = token
38123
self._flight_client_options = flight_client_options or {}
39124
self._proxy = proxy
125+
if options:
126+
if options.flight_client_options:
127+
self._flight_client_options = options.flight_client_options
128+
if options.tls_root_certs:
129+
self._flight_client_options["tls_root_certs"] = options.tls_root_certs
130+
if options.proxy:
131+
self._proxy = options.proxy
132+
if options.tls_verify is not None:
133+
self._flight_client_options["disable_server_verification"] = not options.tls_verify
40134
self._flight_client_options["generic_options"] = [
41135
("grpc.secondary_user_agent", USER_AGENT)
42136
]

tests/test_influxdb_client_3_integration.py

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import pyarrow
99
import pytest
10+
from pyarrow._flight import FlightError
1011

1112
from influxdb_client_3 import InfluxDBClient3, InfluxDBError, write_client_options, WriteOptions
1213

@@ -122,7 +123,6 @@ def error(conf, data, exception: InfluxDBError):
122123
database=self.database,
123124
token=self.token,
124125
write_client_options=wc_opts) as w_client:
125-
126126
for i in range(0, data_set_size):
127127
w_client.write(f'{measurement},location=harfa val={i}i {now - (i * 1_000_000_000)}')
128128

@@ -134,7 +134,6 @@ def error(conf, data, exception: InfluxDBError):
134134
database=self.database,
135135
token=self.token,
136136
write_client_options=wc_opts) as r_client:
137-
138137
query = f"SELECT * FROM \"{measurement}\" WHERE time >= now() - interval '3 minute'"
139138
reader: pyarrow.Table = r_client.query(query)
140139
list_results = reader.to_pylist()
@@ -165,7 +164,6 @@ def test_batch_write_closed(self):
165164
token=self.token,
166165
write_client_options=wc_opts,
167166
debug=True) as w_client:
168-
169167
for i in range(0, data_size):
170168
w_client.write(f'{measurement},location=harfa val={i}i {now - (i * 1_000_000_000)}')
171169

@@ -177,10 +175,77 @@ def test_batch_write_closed(self):
177175
database=self.database,
178176
token=self.token,
179177
write_client_options=wc_opts) as r_client:
180-
181178
logging.info("PREPARING QUERY")
182179

183180
query = f"SELECT * FROM \"{measurement}\" WHERE time >= now() - interval '3 hours'"
184181
reader: pyarrow.Table = r_client.query(query, mode="")
185182
list_results = reader.to_pylist()
186183
self.assertEqual(data_size, len(list_results))
184+
185+
test_cert = """-----BEGIN CERTIFICATE-----
186+
MIIDUzCCAjugAwIBAgIUZB55ULutbc9gy6xLp1BkTQU7siowDQYJKoZIhvcNAQEL
187+
BQAwNjE0MDIGA1UEAwwraW5mbHV4ZGIzLWNsdXN0ZXJlZC1zd2FuLmJyYW1ib3Jh
188+
LnpvbmEtYi5ldTAeFw0yNTAyMTgxNTIyMTJaFw0yNjAyMTgxNTIyMTJaMDYxNDAy
189+
BgNVBAMMK2luZmx1eGRiMy1jbHVzdGVyZWQtc3dhbi5icmFtYm9yYS56b25hLWIu
190+
ZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCugeNrx0ZfyyP8H4e0
191+
zDSkKWnEXlVdjMi+ZSHhMbjvvqMkUQGLc/W59AEmMJ0Uiljka9d+F7jdu+oqDq9p
192+
4kGPhO3Oh7zIG0IGbncj8AwIXMGDNkNyL8s7C1+LoYotlSWDpWwkEKXUeAzdqS63
193+
CSJFqSJM2dss8qe9BpM6zHWJAKS1I30QT3SXQFEsF5m2F62dXCEEI6pO7jlik8/w
194+
aI47dTM20QyimVzea48SC/ELO/T4AjbmMeBGlTyCm39KOElOKRTJvB4KESEWaL3r
195+
EvPZbTh+72PUyrjxiDa56+RmtDPo7EN3uxuRVFX/HWiNnFk7orQLKZg5Kr8wE46R
196+
KmVvAgMBAAGjWTBXMDYGA1UdEQQvMC2CK2luZmx1eGRiMy1jbHVzdGVyZWQtc3dh
197+
bi5icmFtYm9yYS56b25hLWIuZXUwHQYDVR0OBBYEFH8et6JCzGD7Ny84aNRtq5Nj
198+
hvS/MA0GCSqGSIb3DQEBCwUAA4IBAQCuDwARea/Xr3+hmte9A0H+XB8wMPAJ64e8
199+
QA0qi0oy0gGdLfQHhsBWWmKSYLv7HygTNzb+7uFOTtq1UPLt18F+POPeLIj74QZV
200+
z89Pbo1TwUMzQ2pgbu0yRvraXIpqXGrPm5GWYp5mopX0rBWKdimbmEMkhZA0sVeH
201+
IdKIRUY6EyIVG+Z/nbuVqUlgnIWOMp0yg4RRC91zHy3Xvykf3Vai25H/jQpa6cbU
202+
//MIodzUIqT8Tja5cHXE51bLdUkO1rtNKdM7TUdjzkZ+bAOpqKl+c0FlYZI+F7Ly
203+
+MdCcNgKFc8o8jGiyP6uyAJeg+tSICpFDw00LyuKmU62c7VKuyo7
204+
-----END CERTIFICATE-----"""
205+
206+
def create_test_cert(self, cert_file):
207+
f = open(cert_file, "w")
208+
f.write(self.test_cert)
209+
f.close()
210+
211+
def remove_test_cert(self, cert_file):
212+
os.remove(cert_file)
213+
214+
def test_queries_w_bad_cert(self):
215+
cert_file = "test_cert.pem"
216+
self.create_test_cert(cert_file)
217+
with InfluxDBClient3(host=self.host,
218+
database=self.database,
219+
token=self.token,
220+
verify_ssl=True,
221+
ssl_ca_cert=cert_file,
222+
debug=True) as client:
223+
try:
224+
query = "SELECT table_name FROM information_schema.tables"
225+
client.query(query, mode="")
226+
assert False, "query should throw SSL_ERROR"
227+
except FlightError as fe:
228+
assert str(fe).__contains__('SSL_ERROR_SSL')
229+
finally:
230+
self.remove_test_cert(cert_file)
231+
232+
def test_verify_ssl_false(self):
233+
cert_file = "test_cert.pem"
234+
self.create_test_cert(cert_file)
235+
measurement = f'test{random_hex(6)}'
236+
237+
with InfluxDBClient3(host=self.host,
238+
database=self.database,
239+
token=self.token,
240+
verify_ssl=False,
241+
ssl_ca_cert=cert_file,
242+
debug=True) as client:
243+
try:
244+
now = time.time_ns()
245+
client.write(f'{measurement},location=harfa val=42i {now - 1_000_000_000}')
246+
query = f"SELECT * FROM \"{measurement}\""
247+
reader: pyarrow.Table = client.query(query, mode="")
248+
list_results = reader.to_pylist()
249+
assert len(list_results) > 0
250+
finally:
251+
self.remove_test_cert(cert_file)

0 commit comments

Comments
 (0)