Skip to content

Commit d2229a5

Browse files
committed
New SSL configuration options:
- `trust` and the matching enum have been removed. - `trusted_certificates` have been added. - `ssl_context` has been added
1 parent ea5ed6d commit d2229a5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+300
-165
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ repos:
2929
name: unasync
3030
entry: bin/make-unasync
3131
language: system
32-
files: "^(neo4j/_async|tests/unit/async_|testkitbackend/_async)/.*"
32+
files: "^(neo4j/_async|tests/(unit|integration)/async_|testkitbackend/_async)/.*"

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Neo4j Driver Change Log
1+
# Neo4j Driver Change Log (breaking/major changes only)
22

33
## Version 5.0
44

@@ -11,6 +11,10 @@
1111
- `ResultSummary.server.version_info` has been removed.
1212
Use `ResultSummary.server.agent`, `ResultSummary.server.protocol_version`,
1313
or call the `dbms.components` procedure instead.
14+
- SSL configuration options have been changed:
15+
- `trust` has been removed.
16+
Use `trusted_certificates` instead which expects `None` or a `list`. See the
17+
API documentation for more details.
1418

1519
## Version 4.4
1620

bin/make-unasync

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ import unasync
3434
ROOT_DIR = Path(__file__).parents[1].absolute()
3535
ASYNC_DIR = ROOT_DIR / "neo4j" / "_async"
3636
SYNC_DIR = ROOT_DIR / "neo4j" / "_sync"
37-
ASYNC_TEST_DIR = ROOT_DIR / "tests" / "unit" / "async_"
38-
SYNC_TEST_DIR = ROOT_DIR / "tests" / "unit" / "sync"
37+
ASYNC_UNIT_TEST_DIR = ROOT_DIR / "tests" / "unit" / "async_"
38+
SYNC_UNIT_TEST_DIR = ROOT_DIR / "tests" / "unit" / "sync"
39+
ASYNC_INTEGRATION_TEST_DIR = ROOT_DIR / "tests" / "integration" / "async_"
40+
SYNC_INTEGRATION_TEST_DIR = ROOT_DIR / "tests" / "integration" / "sync"
3941
ASYNC_TESTKIT_BACKEND_DIR = ROOT_DIR / "testkitbackend" / "_async"
4042
SYNC_TESTKIT_BACKEND_DIR = ROOT_DIR / "testkitbackend" / "_sync"
4143
UNASYNC_SUFFIX = ".unasync"
@@ -220,8 +222,13 @@ def apply_unasync(files):
220222
additional_replacements=additional_main_replacements,
221223
),
222224
CustomRule(
223-
fromdir=str(ASYNC_TEST_DIR),
224-
todir=str(SYNC_TEST_DIR),
225+
fromdir=str(ASYNC_UNIT_TEST_DIR),
226+
todir=str(SYNC_UNIT_TEST_DIR),
227+
additional_replacements=additional_test_replacements,
228+
),
229+
CustomRule(
230+
fromdir=str(ASYNC_INTEGRATION_TEST_DIR),
231+
todir=str(SYNC_INTEGRATION_TEST_DIR),
225232
additional_replacements=additional_test_replacements,
226233
),
227234
CustomRule(
@@ -233,7 +240,8 @@ def apply_unasync(files):
233240

234241
if not files:
235242
paths = list(ASYNC_DIR.rglob("*"))
236-
paths += list(ASYNC_TEST_DIR.rglob("*"))
243+
paths += list(ASYNC_UNIT_TEST_DIR.rglob("*"))
244+
paths += list(ASYNC_INTEGRATION_TEST_DIR.rglob("*"))
237245
paths += list(ASYNC_TESTKIT_BACKEND_DIR.rglob("*"))
238246
else:
239247
paths = [ROOT_DIR / Path(f) for f in files]

docs/source/api.rst

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ Additional configuration can be provided via the :class:`neo4j.Driver` construct
164164
+ :ref:`max-connection-pool-size-ref`
165165
+ :ref:`max-transaction-retry-time-ref`
166166
+ :ref:`resolver-ref`
167-
+ :ref:`trust-ref`
167+
+ :ref:`ssl-context-ref`
168+
+ :ref:`trusted-certificates-ref`
168169
+ :ref:`user-agent-ref`
169170

170171

@@ -195,6 +196,8 @@ The maximum amount of time in seconds to wait for a TCP connection to be establi
195196
-------------
196197
Specify whether to use an encrypted connection between the driver and server.
197198

199+
This setting does not have any effect if a custom ``ssl_context`` is configured.
200+
198201
:Type: ``bool``
199202
:Default: ``False``
200203

@@ -270,33 +273,49 @@ For example:
270273
resolver=custom_resolver)
271274
272275
273-
:Default: ``None``
276+
:Default: :const:`None`
274277

275278

276-
.. _trust-ref:
279+
.. _ssl-context-ref:
277280

278-
``trust``
279-
---------
280-
Specify how to determine the authenticity of encryption certificates provided by the Neo4j instance on connection.
281+
``ssl_context``
282+
---------------
283+
Specify a custom SSL context to use for wrapping connections.
284+
285+
If give, ``encrypted`` and ``trusted_certificates`` have no effect.
286+
287+
:Type: :class:`ssl.SSLContext` or :const:`None`
288+
:Default: :const:`None`
281289

282-
This setting does not have any effect if ``encrypted`` is set to ``False``.
283290

284-
:Type: ``neo4j.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES``, ``neo4j.TRUST_ALL_CERTIFICATES``
291+
.. _trusted-certificates-ref:
292+
293+
``trusted_certificates``
294+
------------------------
295+
Specify how to determine the authenticity of encryption certificates provided by the Neo4j instance on connection.
296+
297+
This setting does not have any effect if ``encrypted`` is set to ``False`` or a
298+
custom ``ssl_context`` is configured.
285299

286-
.. py:attribute:: neo4j.TRUST_ALL_CERTIFICATES
300+
:Type: :class:`list` or :const:`None`
287301

288-
Trust any server certificate (default). This ensures that communication
289-
is encrypted but does not verify the server certificate against a
290-
certificate authority. This option is primarily intended for use with
291-
the default auto-generated server certificate.
302+
**None**
303+
Trust server certificates that can be verified against the system
304+
certificate authority. This option is primarily intended for use with
305+
full certificates.
292306

293-
.. py:attribute:: neo4j.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES
307+
**[] (empty list)**
308+
Trust any server certificate (default). This ensures that communication
309+
is encrypted but does not verify the server certificate against a
310+
certificate authority. This option is primarily intended for use with
311+
the default auto-generated server certificate.
294312

295-
Trust server certificates that can be verified against the system
296-
certificate authority. This option is primarily intended for use with
297-
full certificates.
313+
**["<path>", ...]**
314+
Trust server certificates that can be verified against the certificate
315+
authority at the specified paths. This option is primarily intended for
316+
self-signed and custom certificates.
298317

299-
:Default: ``neo4j.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES``.
318+
:Default: :const:`None`
300319

301320

302321
.. _user-agent-ref:
@@ -534,7 +553,7 @@ context of the impersonated user. For this, the user for which the
534553
session = driver.session(impersonated_user="alice")
535554
536555
537-
:Default: ``None``
556+
:Default: :const:`None`
538557

539558

540559
.. _default-access-mode-ref:
@@ -864,7 +883,7 @@ The core types with their general mappings are listed below:
864883
+------------------------+---------------------------------------------------------------------------------------------------------------------------+
865884
| Cypher Type | Python Type |
866885
+========================+===========================================================================================================================+
867-
| Null | ``None`` |
886+
| Null | :const:`None` |
868887
+------------------------+---------------------------------------------------------------------------------------------------------------------------+
869888
| Boolean | ``bool`` |
870889
+------------------------+---------------------------------------------------------------------------------------------------------------------------+

neo4j/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@
5353
"SessionConfig",
5454
"SummaryCounters",
5555
"Transaction",
56-
"TRUST_ALL_CERTIFICATES",
57-
"TRUST_SYSTEM_CA_SIGNED_CERTIFICATES",
5856
"unit_of_work",
5957
"Version",
6058
"WorkspaceConfig",
@@ -105,8 +103,6 @@
105103
READ_ACCESS,
106104
ServerInfo,
107105
SYSTEM_DATABASE,
108-
TRUST_ALL_CERTIFICATES,
109-
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
110106
Version,
111107
WRITE_ACCESS,
112108
)

neo4j/_async/driver.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@
2020

2121
from .._async_compat.util import AsyncUtil
2222
from ..addressing import Address
23-
from ..api import (
24-
READ_ACCESS,
25-
TRUST_ALL_CERTIFICATES,
26-
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
27-
)
23+
from ..api import READ_ACCESS
2824
from ..conf import (
2925
Config,
3026
PoolConfig,
@@ -71,17 +67,7 @@ def driver(cls, uri, *, auth=None, **config):
7167

7268
driver_type, security_type, parsed = parse_neo4j_uri(uri)
7369

74-
if "trust" in config.keys():
75-
if config.get("trust") not in [TRUST_ALL_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES]:
76-
from neo4j.exceptions import ConfigurationError
77-
raise ConfigurationError("The config setting `trust` values are {!r}".format(
78-
[
79-
TRUST_ALL_CERTIFICATES,
80-
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
81-
]
82-
))
83-
84-
if security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE] and ("encrypted" in config.keys() or "trust" in config.keys()):
70+
if security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE] and ("encrypted" in config.keys() or "trusted_certificates" in config.keys()):
8571
from neo4j.exceptions import ConfigurationError
8672
raise ConfigurationError("The config settings 'encrypted' and 'trust' can only be used with the URI schemes {!r}. Use the other URI schemes {!r} for setting encryption settings.".format(
8773
[
@@ -100,7 +86,7 @@ def driver(cls, uri, *, auth=None, **config):
10086
config["encrypted"] = True
10187
elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE:
10288
config["encrypted"] = True
103-
config["trust"] = TRUST_ALL_CERTIFICATES
89+
config["trusted_certificates"] = []
10490

10591
if driver_type == DRIVER_BOLT:
10692
return cls.bolt_driver(parsed.netloc, auth=auth, **config)

neo4j/_sync/driver.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@
2020

2121
from .._async_compat.util import Util
2222
from ..addressing import Address
23-
from ..api import (
24-
READ_ACCESS,
25-
TRUST_ALL_CERTIFICATES,
26-
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
27-
)
23+
from ..api import READ_ACCESS
2824
from ..conf import (
2925
Config,
3026
PoolConfig,
@@ -71,17 +67,7 @@ def driver(cls, uri, *, auth=None, **config):
7167

7268
driver_type, security_type, parsed = parse_neo4j_uri(uri)
7369

74-
if "trust" in config.keys():
75-
if config.get("trust") not in [TRUST_ALL_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES]:
76-
from neo4j.exceptions import ConfigurationError
77-
raise ConfigurationError("The config setting `trust` values are {!r}".format(
78-
[
79-
TRUST_ALL_CERTIFICATES,
80-
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
81-
]
82-
))
83-
84-
if security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE] and ("encrypted" in config.keys() or "trust" in config.keys()):
70+
if security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE] and ("encrypted" in config.keys() or "trusted_certificates" in config.keys()):
8571
from neo4j.exceptions import ConfigurationError
8672
raise ConfigurationError("The config settings 'encrypted' and 'trust' can only be used with the URI schemes {!r}. Use the other URI schemes {!r} for setting encryption settings.".format(
8773
[
@@ -100,7 +86,7 @@ def driver(cls, uri, *, auth=None, **config):
10086
config["encrypted"] = True
10187
elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE:
10288
config["encrypted"] = True
103-
config["trust"] = TRUST_ALL_CERTIFICATES
89+
config["trusted_certificates"] = []
10490

10591
if driver_type == DRIVER_BOLT:
10692
return cls.bolt_driver(parsed.netloc, auth=auth, **config)

neo4j/api.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@
5151

5252
URI_SCHEME_BOLT_ROUTING = "bolt+routing"
5353

54-
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES = "TRUST_SYSTEM_CA_SIGNED_CERTIFICATES" # Default
55-
TRUST_ALL_CERTIFICATES = "TRUST_ALL_CERTIFICATES"
56-
5754
SYSTEM_DATABASE = "system"
5855
DEFAULT_DATABASE = None # Must be a non string hashable value
5956

neo4j/conf.py

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222

2323
from .api import (
2424
DEFAULT_DATABASE,
25-
TRUST_ALL_CERTIFICATES,
26-
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
2725
WRITE_ACCESS,
2826
)
2927
from .exceptions import ConfigurationError
@@ -181,10 +179,6 @@ class PoolConfig(Config):
181179
connection_timeout = 30.0 # seconds
182180
# The maximum amount of time to wait for a TCP connection to be established.
183181

184-
#: Trust
185-
trust = TRUST_SYSTEM_CA_SIGNED_CERTIFICATES
186-
# Specify how to determine the authenticity of encryption certificates provided by the Neo4j instance on connection.
187-
188182
#: Custom Resolver
189183
resolver = None
190184
# Custom resolver function, returning list of resolved addresses.
@@ -193,6 +187,20 @@ class PoolConfig(Config):
193187
encrypted = False
194188
# Specify whether to use an encrypted connection between the driver and server.
195189

190+
#: SSL Certificates to Trust
191+
trusted_certificates = None
192+
# Specify how to determine the authenticity of encryption certificates
193+
# provided by the Neo4j instance on connection.
194+
# * None: Use system trust store.
195+
# * []: Trust any certificate.
196+
# * ["<path>", ...]: Trust the specified certificate(s).
197+
198+
#: Custom SSL context to use for wrapping sockets
199+
ssl_context = None
200+
# Use any custom SSL context to wrap sockets.
201+
# Overwrites `trusted_certificates` and `encrypted`.
202+
# The use of this option is strongly discouraged.
203+
196204
#: User Agent (Python Driver Specific)
197205
user_agent = get_user_agent()
198206
# Specify the client agent name.
@@ -210,24 +218,23 @@ class PoolConfig(Config):
210218
# Specify whether TCP keep-alive should be enabled.
211219

212220
def get_ssl_context(self):
221+
if self.ssl_context is not None:
222+
return self.ssl_context
223+
213224
if not self.encrypted:
214225
return None
215226

216227
import ssl
217228

218-
ssl_context = None
219-
220229
# SSL stands for Secure Sockets Layer and was originally created by Netscape.
221230
# SSLv2 and SSLv3 are the 2 versions of this protocol (SSLv1 was never publicly released).
222231
# After SSLv3, SSL was renamed to TLS.
223232
# TLS stands for Transport Layer Security and started with TLSv1.0 which is an upgraded version of SSLv3.
224-
225233
# SSLv2 - (Disabled)
226234
# SSLv3 - (Disabled)
227235
# TLS 1.0 - Released in 1999, published as RFC 2246. (Disabled)
228236
# TLS 1.1 - Released in 2006, published as RFC 4346. (Disabled)
229237
# TLS 1.2 - Released in 2008, published as RFC 5246.
230-
231238
# https://docs.python.org/3.7/library/ssl.html#ssl.PROTOCOL_TLS_CLIENT
232239
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
233240

@@ -236,15 +243,28 @@ def get_ssl_context(self):
236243
ssl_context.options |= ssl.OP_NO_TLSv1 # Python 3.2
237244
ssl_context.options |= ssl.OP_NO_TLSv1_1 # Python 3.4
238245

239-
240-
if self.trust == TRUST_ALL_CERTIFICATES:
241-
ssl_context.check_hostname = False
242-
# https://docs.python.org/3.7/library/ssl.html#ssl.CERT_NONE
243-
ssl_context.verify_mode = ssl.CERT_NONE
244-
245-
# Must be load_default_certs, not set_default_verify_paths to work
246-
# on Windows with system CAs.
247-
ssl_context.load_default_certs()
246+
if self.trusted_certificates is None:
247+
# trust system CA certificates
248+
ssl_context.check_hostname = True
249+
ssl_context.verify_mode = ssl.CERT_REQUIRED
250+
# Must be load_default_certs, not set_default_verify_paths to
251+
# work on Windows with system CAs.
252+
ssl_context.load_default_certs()
253+
else:
254+
self.trusted_certificates = tuple(self.trusted_certificates)
255+
if not self.trusted_certificates:
256+
# trust any certificate
257+
ssl_context.check_hostname = False
258+
# https://docs.python.org/3.7/library/ssl.html#ssl.CERT_NONE
259+
ssl_context.verify_mode = ssl.CERT_NONE
260+
else:
261+
# trust the specified certificate(s)
262+
ssl_context.check_hostname = True
263+
ssl_context.verify_mode = ssl.CERT_REQUIRED
264+
# Must be load_default_certs, not set_default_verify_paths to
265+
# work on Windows with system CAs.
266+
for cert in self.trusted_certificates:
267+
ssl_context.load_verify_locations(cert)
248268

249269
return ssl_context
250270

testkit/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ RUN apt-get update && \
3232
# Install our own CAs on the image.
3333
# Assumes Linux Debian based image.
3434
COPY CAs/* /usr/local/share/ca-certificates/
35+
# Store custom CAs somewhere where the backend can find them later.
36+
COPY CustomCAs/* /usr/local/share/custom-ca-certificates/
3537
RUN update-ca-certificates
3638

3739
# Install pyenv

0 commit comments

Comments
 (0)