Skip to content

Commit 3bdd5c5

Browse files
authored
Re-design SSL config options (#666)
To avoid magic values (e.g., like it was before: an empty list would mean "trust any certificate"), we introduce helper classes for configuring the driver when it comes to trusted certificates.
1 parent aaf17e6 commit 3bdd5c5

File tree

14 files changed

+221
-95
lines changed

14 files changed

+221
-95
lines changed

CHANGELOG.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
or call the `dbms.components` procedure instead.
1616
- SSL configuration options have been changed:
1717
- `trust` has been deprecated and will be removed in a future release.
18-
Use `trusted_certificates` instead which expects `None` or a `list`. See the
19-
API documentation for more details.
18+
Use `trusted_certificates` instead.
19+
See the API documentation for more details.
2020
- `neo4j.time` module:
2121
- `Duration`
2222
- The constructor does not accept `subseconds` anymore.

docs/source/api.rst

+5-16
Original file line numberDiff line numberDiff line change
@@ -335,25 +335,14 @@ Specify how to determine the authenticity of encryption certificates provided by
335335
This setting does not have any effect if ``encrypted`` is set to ``False`` or a
336336
custom ``ssl_context`` is configured.
337337

338-
:Type: :class:`list` or :const:`None`
338+
:Type: :class:`.TrustSystemCAs`, :class:`.TrustAll`, or :class:`.TrustCustomCAs`
339+
:Default: :const:`neo4j.TrustSystemCAs()`
339340

340-
**None** (default)
341-
Trust server certificates that can be verified against the system
342-
certificate authority. This option is primarily intended for use with
343-
full certificates.
341+
.. autoclass:: neo4j.TrustSystemCAs
344342

345-
**[] (empty list)**
346-
Trust any server certificate. This ensures that communication
347-
is encrypted but does not verify the server certificate against a
348-
certificate authority. This option is primarily intended for use with
349-
the default auto-generated server certificate.
343+
.. autoclass:: neo4j.TrustAll
350344

351-
**["<path>", ...]**
352-
Trust server certificates that can be verified against the certificate
353-
authority at the specified paths. This option is primarily intended for
354-
self-signed and custom certificates.
355-
356-
:Default: :const:`None`
345+
.. autoclass:: neo4j.TrustCustomCAs
357346

358347
.. versionadded:: 5.0
359348

neo4j/__init__.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
"AsyncBoltDriver",
2323
"AsyncDriver",
2424
"AsyncGraphDatabase",
25-
"AsyncNeo4jDriver",
2625
"AsyncManagedTransaction",
26+
"AsyncNeo4jDriver",
2727
"AsyncResult",
2828
"AsyncSession",
2929
"AsyncTransaction",
@@ -58,6 +58,9 @@
5858
"Transaction",
5959
"TRUST_ALL_CERTIFICATES",
6060
"TRUST_SYSTEM_CA_SIGNED_CERTIFICATES",
61+
"TrustAll",
62+
"TrustCustomCAs",
63+
"TrustSystemCAs",
6164
"unit_of_work",
6265
"Version",
6366
"WorkspaceConfig",
@@ -79,6 +82,11 @@
7982
AsyncSession,
8083
AsyncTransaction,
8184
)
85+
from ._conf import (
86+
TrustAll,
87+
TrustCustomCAs,
88+
TrustSystemCAs,
89+
)
8290
from ._sync.driver import (
8391
BoltDriver,
8492
Driver,

neo4j/_async/driver.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
# limitations under the License.
1717

1818

19-
import asyncio
20-
2119
from .._async_compat.util import AsyncUtil
20+
from .._conf import (
21+
TrustAll,
22+
TrustStore,
23+
)
2224
from ..addressing import Address
2325
from ..api import (
2426
READ_ACCESS,
@@ -31,10 +33,6 @@
3133
SessionConfig,
3234
WorkspaceConfig,
3335
)
34-
from ..exceptions import (
35-
ServiceUnavailable,
36-
SessionExpired,
37-
)
3836
from ..meta import (
3937
deprecation_warn,
4038
experimental,
@@ -66,7 +64,6 @@ def driver(cls, uri, *, auth=None, **config):
6664
DRIVER_NEO4j,
6765
parse_neo4j_uri,
6866
parse_routing_context,
69-
SECURITY_TYPE_NOT_SECURE,
7067
SECURITY_TYPE_SECURE,
7168
SECURITY_TYPE_SELF_SIGNED_CERTIFICATE,
7269
URI_SCHEME_BOLT,
@@ -94,6 +91,17 @@ def driver(cls, uri, *, auth=None, **config):
9491
)
9592
)
9693

94+
if ("trusted_certificates" in config.keys()
95+
and not isinstance(config["trusted_certificates"],
96+
TrustStore)):
97+
raise ConnectionError(
98+
"The config setting `trusted_certificates` must be of type "
99+
"neo4j.TrustAll, neo4j.TrustCustomCAs, or"
100+
"neo4j.TrustSystemCAs but was {}".format(
101+
type(config["trusted_certificates"])
102+
)
103+
)
104+
97105
if (security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE]
98106
and ("encrypted" in config.keys()
99107
or "trust" in config.keys()
@@ -125,7 +133,7 @@ def driver(cls, uri, *, auth=None, **config):
125133
config["encrypted"] = True
126134
elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE:
127135
config["encrypted"] = True
128-
config["trusted_certificates"] = []
136+
config["trusted_certificates"] = TrustAll()
129137

130138
if driver_type == DRIVER_BOLT:
131139
if parse_routing_context(parsed.query):

neo4j/_conf.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [http://neo4j.com]
3+
#
4+
# This file is part of Neo4j.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
19+
class TrustStore:
20+
# Base class for trust stores. For internal type-checking only.
21+
pass
22+
23+
24+
class TrustSystemCAs(TrustStore):
25+
"""Used to configure the driver to trust system CAs (default).
26+
27+
Trust server certificates that can be verified against the system
28+
certificate authority. This option is primarily intended for use with
29+
full certificates.
30+
31+
For example::
32+
33+
driver = neo4j.GraphDatabase.driver(
34+
url, auth=auth, trusted_certificates=neo4j.TrustSystemCAs()
35+
)
36+
"""
37+
pass
38+
39+
40+
class TrustAll(TrustStore):
41+
"""Used to configure the driver to trust all certificates.
42+
43+
Trust any server certificate. This ensures that communication
44+
is encrypted but does not verify the server certificate against a
45+
certificate authority. This option is primarily intended for use with
46+
the default auto-generated server certificate.
47+
48+
49+
For example::
50+
51+
driver = neo4j.GraphDatabase.driver(
52+
url, auth=auth, trusted_certificates=neo4j.TrustAll()
53+
)
54+
"""
55+
pass
56+
57+
58+
class TrustCustomCAs(TrustStore):
59+
"""Used to configure the driver to trust custom CAs.
60+
61+
Trust server certificates that can be verified against the certificate
62+
authority at the specified paths. This option is primarily intended for
63+
self-signed and custom certificates.
64+
65+
:param certificates (str): paths to the certificates to trust.
66+
Those are not the certificates you expect to see from the server but
67+
the CA certificates you expect to be used to sign the server's
68+
certificate.
69+
70+
For example::
71+
72+
driver = neo4j.GraphDatabase.driver(
73+
url, auth=auth,
74+
trusted_certificates=neo4j.TrustCustomCAs(
75+
"/path/to/ca1.crt", "/path/to/ca2.crt",
76+
)
77+
)
78+
"""
79+
def __init__(self, *certificates):
80+
self.certs = certificates

neo4j/_sync/driver.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
# limitations under the License.
1717

1818

19-
import asyncio
20-
2119
from .._async_compat.util import Util
20+
from .._conf import (
21+
TrustAll,
22+
TrustStore,
23+
)
2224
from ..addressing import Address
2325
from ..api import (
2426
READ_ACCESS,
@@ -31,10 +33,6 @@
3133
SessionConfig,
3234
WorkspaceConfig,
3335
)
34-
from ..exceptions import (
35-
ServiceUnavailable,
36-
SessionExpired,
37-
)
3836
from ..meta import (
3937
deprecation_warn,
4038
experimental,
@@ -66,7 +64,6 @@ def driver(cls, uri, *, auth=None, **config):
6664
DRIVER_NEO4j,
6765
parse_neo4j_uri,
6866
parse_routing_context,
69-
SECURITY_TYPE_NOT_SECURE,
7067
SECURITY_TYPE_SECURE,
7168
SECURITY_TYPE_SELF_SIGNED_CERTIFICATE,
7269
URI_SCHEME_BOLT,
@@ -94,6 +91,17 @@ def driver(cls, uri, *, auth=None, **config):
9491
)
9592
)
9693

94+
if ("trusted_certificates" in config.keys()
95+
and not isinstance(config["trusted_certificates"],
96+
TrustStore)):
97+
raise ConnectionError(
98+
"The config setting `trusted_certificates` must be of type "
99+
"neo4j.TrustAll, neo4j.TrustCustomCAs, or"
100+
"neo4j.TrustSystemCAs but was {}".format(
101+
type(config["trusted_certificates"])
102+
)
103+
)
104+
97105
if (security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE]
98106
and ("encrypted" in config.keys()
99107
or "trust" in config.keys()
@@ -125,7 +133,7 @@ def driver(cls, uri, *, auth=None, **config):
125133
config["encrypted"] = True
126134
elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE:
127135
config["encrypted"] = True
128-
config["trusted_certificates"] = []
136+
config["trusted_certificates"] = TrustAll()
129137

130138
if driver_type == DRIVER_BOLT:
131139
if parse_routing_context(parsed.query):

neo4j/conf.py

+25-21
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818

1919
from abc import ABCMeta
2020
from collections.abc import Mapping
21-
from warnings import warn
2221

22+
from ._conf import (
23+
TrustAll,
24+
TrustCustomCAs,
25+
TrustSystemCAs,
26+
)
2327
from .api import (
2428
DEFAULT_DATABASE,
2529
TRUST_ALL_CERTIFICATES,
@@ -205,9 +209,9 @@ def __iter__(self):
205209

206210
def _trust_to_trusted_certificates(pool_config, trust):
207211
if trust == TRUST_SYSTEM_CA_SIGNED_CERTIFICATES:
208-
pool_config.trusted_certificates = None
212+
pool_config.trusted_certificates = TrustSystemCAs()
209213
elif trust == TRUST_ALL_CERTIFICATES:
210-
pool_config.trusted_certificates = []
214+
pool_config.trusted_certificates = TrustAll()
211215

212216

213217
class PoolConfig(Config):
@@ -241,12 +245,13 @@ class PoolConfig(Config):
241245
# Specify whether to use an encrypted connection between the driver and server.
242246

243247
#: SSL Certificates to Trust
244-
trusted_certificates = None
248+
trusted_certificates = TrustSystemCAs()
245249
# Specify how to determine the authenticity of encryption certificates
246250
# provided by the Neo4j instance on connection.
247-
# * None: Use system trust store. (default)
248-
# * []: Trust any certificate.
249-
# * ["<path>", ...]: Trust the specified certificate(s).
251+
# * `neo4j.TrustSystemCAs()`: Use system trust store. (default)
252+
# * `neo4j.TrustAll()`: Trust any certificate.
253+
# * `neo4j.TrustCustomCAs("<path>", ...)`:
254+
# Trust the specified certificate(s).
250255

251256
#: Custom SSL context to use for wrapping sockets
252257
ssl_context = None
@@ -296,26 +301,25 @@ def get_ssl_context(self):
296301
ssl_context.options |= ssl.OP_NO_TLSv1 # Python 3.2
297302
ssl_context.options |= ssl.OP_NO_TLSv1_1 # Python 3.4
298303

299-
if self.trusted_certificates is None:
304+
if isinstance(self.trusted_certificates, TrustAll):
305+
# trust any certificate
306+
ssl_context.check_hostname = False
307+
# https://docs.python.org/3.7/library/ssl.html#ssl.CERT_NONE
308+
ssl_context.verify_mode = ssl.CERT_NONE
309+
elif isinstance(self.trusted_certificates, TrustCustomCAs):
310+
# trust the specified certificate(s)
311+
ssl_context.check_hostname = True
312+
ssl_context.verify_mode = ssl.CERT_REQUIRED
313+
for cert in self.trusted_certificates.certs:
314+
ssl_context.load_verify_locations(cert)
315+
else:
316+
# default
300317
# trust system CA certificates
301318
ssl_context.check_hostname = True
302319
ssl_context.verify_mode = ssl.CERT_REQUIRED
303320
# Must be load_default_certs, not set_default_verify_paths to
304321
# work on Windows with system CAs.
305322
ssl_context.load_default_certs()
306-
else:
307-
self.trusted_certificates = tuple(self.trusted_certificates)
308-
if not self.trusted_certificates:
309-
# trust any certificate
310-
ssl_context.check_hostname = False
311-
# https://docs.python.org/3.7/library/ssl.html#ssl.CERT_NONE
312-
ssl_context.verify_mode = ssl.CERT_NONE
313-
else:
314-
# trust the specified certificate(s)
315-
ssl_context.check_hostname = True
316-
ssl_context.verify_mode = ssl.CERT_REQUIRED
317-
for cert in self.trusted_certificates:
318-
ssl_context.load_verify_locations(cert)
319323

320324
return ssl_context
321325

testkitbackend/_async/requests.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,14 @@ async def NewDriver(backend, data):
103103
if "encrypted" in data:
104104
kwargs["encrypted"] = data["encrypted"]
105105
if "trustedCertificates" in data:
106-
kwargs["trusted_certificates"] = data["trustedCertificates"]
107-
if isinstance(kwargs["trusted_certificates"], list):
108-
kwargs["trusted_certificates"] = [
109-
"/usr/local/share/custom-ca-certificates/" + cert
110-
for cert in kwargs["trusted_certificates"]
111-
]
106+
if data["trustedCertificates"] is None:
107+
kwargs["trusted_certificates"] = neo4j.TrustSystemCAs()
108+
elif not data["trustedCertificates"]:
109+
kwargs["trusted_certificates"] = neo4j.TrustAll()
110+
else:
111+
cert_paths = ("/usr/local/share/custom-ca-certificates/" + cert
112+
for cert in data["trustedCertificates"])
113+
kwargs["trusted_certificates"] = neo4j.TrustCustomCAs(*cert_paths)
112114

113115
data.mark_item_as_read("domainNameResolverRegistered")
114116
driver = neo4j.AsyncGraphDatabase.driver(

0 commit comments

Comments
 (0)