Skip to content

New SSL configuration options: #639

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 1 commit into from
Jan 11, 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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ repos:
name: unasync
entry: bin/make-unasync
language: system
files: "^(neo4j/_async|tests/unit/async_|testkitbackend/_async)/.*"
files: "^(neo4j/_async|tests/(unit|integration)/async_|testkitbackend/_async)/.*"
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Neo4j Driver Change Log
# Neo4j Driver Change Log (breaking/major changes only)

## Version 5.0

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

## Version 4.4

Expand Down
18 changes: 13 additions & 5 deletions bin/make-unasync
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ import unasync
ROOT_DIR = Path(__file__).parents[1].absolute()
ASYNC_DIR = ROOT_DIR / "neo4j" / "_async"
SYNC_DIR = ROOT_DIR / "neo4j" / "_sync"
ASYNC_TEST_DIR = ROOT_DIR / "tests" / "unit" / "async_"
SYNC_TEST_DIR = ROOT_DIR / "tests" / "unit" / "sync"
ASYNC_UNIT_TEST_DIR = ROOT_DIR / "tests" / "unit" / "async_"
SYNC_UNIT_TEST_DIR = ROOT_DIR / "tests" / "unit" / "sync"
ASYNC_INTEGRATION_TEST_DIR = ROOT_DIR / "tests" / "integration" / "async_"
SYNC_INTEGRATION_TEST_DIR = ROOT_DIR / "tests" / "integration" / "sync"
ASYNC_TESTKIT_BACKEND_DIR = ROOT_DIR / "testkitbackend" / "_async"
SYNC_TESTKIT_BACKEND_DIR = ROOT_DIR / "testkitbackend" / "_sync"
UNASYNC_SUFFIX = ".unasync"
Expand Down Expand Up @@ -220,8 +222,13 @@ def apply_unasync(files):
additional_replacements=additional_main_replacements,
),
CustomRule(
fromdir=str(ASYNC_TEST_DIR),
todir=str(SYNC_TEST_DIR),
fromdir=str(ASYNC_UNIT_TEST_DIR),
todir=str(SYNC_UNIT_TEST_DIR),
additional_replacements=additional_test_replacements,
),
CustomRule(
fromdir=str(ASYNC_INTEGRATION_TEST_DIR),
todir=str(SYNC_INTEGRATION_TEST_DIR),
additional_replacements=additional_test_replacements,
),
CustomRule(
Expand All @@ -233,7 +240,8 @@ def apply_unasync(files):

if not files:
paths = list(ASYNC_DIR.rglob("*"))
paths += list(ASYNC_TEST_DIR.rglob("*"))
paths += list(ASYNC_UNIT_TEST_DIR.rglob("*"))
paths += list(ASYNC_INTEGRATION_TEST_DIR.rglob("*"))
paths += list(ASYNC_TESTKIT_BACKEND_DIR.rglob("*"))
else:
paths = [ROOT_DIR / Path(f) for f in files]
Expand Down
59 changes: 39 additions & 20 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ Additional configuration can be provided via the :class:`neo4j.Driver` construct
+ :ref:`max-connection-pool-size-ref`
+ :ref:`max-transaction-retry-time-ref`
+ :ref:`resolver-ref`
+ :ref:`trust-ref`
+ :ref:`ssl-context-ref`
+ :ref:`trusted-certificates-ref`
+ :ref:`user-agent-ref`


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

This setting does not have any effect if a custom ``ssl_context`` is configured.

:Type: ``bool``
:Default: ``False``

Expand Down Expand Up @@ -270,33 +273,49 @@ For example:
resolver=custom_resolver)


:Default: ``None``
:Default: :const:`None`


.. _trust-ref:
.. _ssl-context-ref:

``trust``
---------
Specify how to determine the authenticity of encryption certificates provided by the Neo4j instance on connection.
``ssl_context``
---------------
Specify a custom SSL context to use for wrapping connections.

If give, ``encrypted`` and ``trusted_certificates`` have no effect.

:Type: :class:`ssl.SSLContext` or :const:`None`
:Default: :const:`None`

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

:Type: ``neo4j.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES``, ``neo4j.TRUST_ALL_CERTIFICATES``
.. _trusted-certificates-ref:

``trusted_certificates``
------------------------
Specify how to determine the authenticity of encryption certificates provided by the Neo4j instance on connection.

This setting does not have any effect if ``encrypted`` is set to ``False`` or a
custom ``ssl_context`` is configured.

.. py:attribute:: neo4j.TRUST_ALL_CERTIFICATES
:Type: :class:`list` or :const:`None`

Trust any server certificate (default). This ensures that communication
is encrypted but does not verify the server certificate against a
certificate authority. This option is primarily intended for use with
the default auto-generated server certificate.
**None** (default)
Trust server certificates that can be verified against the system
certificate authority. This option is primarily intended for use with
full certificates.

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

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

:Default: ``neo4j.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES``.
:Default: :const:`None`


.. _user-agent-ref:
Expand Down Expand Up @@ -534,7 +553,7 @@ context of the impersonated user. For this, the user for which the
session = driver.session(impersonated_user="alice")


:Default: ``None``
:Default: :const:`None`


.. _default-access-mode-ref:
Expand Down Expand Up @@ -864,7 +883,7 @@ The core types with their general mappings are listed below:
+------------------------+---------------------------------------------------------------------------------------------------------------------------+
| Cypher Type | Python Type |
+========================+===========================================================================================================================+
| Null | ``None`` |
| Null | :const:`None` |
+------------------------+---------------------------------------------------------------------------------------------------------------------------+
| Boolean | ``bool`` |
+------------------------+---------------------------------------------------------------------------------------------------------------------------+
Expand Down
4 changes: 0 additions & 4 deletions neo4j/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@
"SessionConfig",
"SummaryCounters",
"Transaction",
"TRUST_ALL_CERTIFICATES",
"TRUST_SYSTEM_CA_SIGNED_CERTIFICATES",
"unit_of_work",
"Version",
"WorkspaceConfig",
Expand Down Expand Up @@ -105,8 +103,6 @@
READ_ACCESS,
ServerInfo,
SYSTEM_DATABASE,
TRUST_ALL_CERTIFICATES,
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
Version,
WRITE_ACCESS,
)
Expand Down
20 changes: 3 additions & 17 deletions neo4j/_async/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@

from .._async_compat.util import AsyncUtil
from ..addressing import Address
from ..api import (
READ_ACCESS,
TRUST_ALL_CERTIFICATES,
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
)
from ..api import READ_ACCESS
from ..conf import (
Config,
PoolConfig,
Expand Down Expand Up @@ -71,17 +67,7 @@ def driver(cls, uri, *, auth=None, **config):

driver_type, security_type, parsed = parse_neo4j_uri(uri)

if "trust" in config.keys():
if config.get("trust") not in [TRUST_ALL_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES]:
from neo4j.exceptions import ConfigurationError
raise ConfigurationError("The config setting `trust` values are {!r}".format(
[
TRUST_ALL_CERTIFICATES,
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
]
))

if security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE] and ("encrypted" in config.keys() or "trust" in config.keys()):
if security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE] and ("encrypted" in config.keys() or "trusted_certificates" in config.keys()):
from neo4j.exceptions import ConfigurationError
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(
[
Expand All @@ -100,7 +86,7 @@ def driver(cls, uri, *, auth=None, **config):
config["encrypted"] = True
elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE:
config["encrypted"] = True
config["trust"] = TRUST_ALL_CERTIFICATES
config["trusted_certificates"] = []

if driver_type == DRIVER_BOLT:
return cls.bolt_driver(parsed.netloc, auth=auth, **config)
Expand Down
20 changes: 3 additions & 17 deletions neo4j/_sync/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@

from .._async_compat.util import Util
from ..addressing import Address
from ..api import (
READ_ACCESS,
TRUST_ALL_CERTIFICATES,
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
)
from ..api import READ_ACCESS
from ..conf import (
Config,
PoolConfig,
Expand Down Expand Up @@ -71,17 +67,7 @@ def driver(cls, uri, *, auth=None, **config):

driver_type, security_type, parsed = parse_neo4j_uri(uri)

if "trust" in config.keys():
if config.get("trust") not in [TRUST_ALL_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES]:
from neo4j.exceptions import ConfigurationError
raise ConfigurationError("The config setting `trust` values are {!r}".format(
[
TRUST_ALL_CERTIFICATES,
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
]
))

if security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE] and ("encrypted" in config.keys() or "trust" in config.keys()):
if security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE] and ("encrypted" in config.keys() or "trusted_certificates" in config.keys()):
from neo4j.exceptions import ConfigurationError
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(
[
Expand All @@ -100,7 +86,7 @@ def driver(cls, uri, *, auth=None, **config):
config["encrypted"] = True
elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE:
config["encrypted"] = True
config["trust"] = TRUST_ALL_CERTIFICATES
config["trusted_certificates"] = []

if driver_type == DRIVER_BOLT:
return cls.bolt_driver(parsed.netloc, auth=auth, **config)
Expand Down
3 changes: 0 additions & 3 deletions neo4j/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@

URI_SCHEME_BOLT_ROUTING = "bolt+routing"

TRUST_SYSTEM_CA_SIGNED_CERTIFICATES = "TRUST_SYSTEM_CA_SIGNED_CERTIFICATES" # Default
TRUST_ALL_CERTIFICATES = "TRUST_ALL_CERTIFICATES"

SYSTEM_DATABASE = "system"
DEFAULT_DATABASE = None # Must be a non string hashable value

Expand Down
58 changes: 39 additions & 19 deletions neo4j/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@

from .api import (
DEFAULT_DATABASE,
TRUST_ALL_CERTIFICATES,
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
WRITE_ACCESS,
)
from .exceptions import ConfigurationError
Expand Down Expand Up @@ -181,10 +179,6 @@ class PoolConfig(Config):
connection_timeout = 30.0 # seconds
# The maximum amount of time to wait for a TCP connection to be established.

#: Trust
trust = TRUST_SYSTEM_CA_SIGNED_CERTIFICATES
# Specify how to determine the authenticity of encryption certificates provided by the Neo4j instance on connection.

#: Custom Resolver
resolver = None
# Custom resolver function, returning list of resolved addresses.
Expand All @@ -193,6 +187,20 @@ class PoolConfig(Config):
encrypted = False
# Specify whether to use an encrypted connection between the driver and server.

#: SSL Certificates to Trust
trusted_certificates = None
# Specify how to determine the authenticity of encryption certificates
# provided by the Neo4j instance on connection.
# * None: Use system trust store. (default)
# * []: Trust any certificate.
# * ["<path>", ...]: Trust the specified certificate(s).

#: Custom SSL context to use for wrapping sockets
ssl_context = None
# Use any custom SSL context to wrap sockets.
# Overwrites `trusted_certificates` and `encrypted`.
# The use of this option is strongly discouraged.

#: User Agent (Python Driver Specific)
user_agent = get_user_agent()
# Specify the client agent name.
Expand All @@ -210,24 +218,23 @@ class PoolConfig(Config):
# Specify whether TCP keep-alive should be enabled.

def get_ssl_context(self):
if self.ssl_context is not None:
return self.ssl_context

if not self.encrypted:
return None

import ssl

ssl_context = None

# SSL stands for Secure Sockets Layer and was originally created by Netscape.
# SSLv2 and SSLv3 are the 2 versions of this protocol (SSLv1 was never publicly released).
# After SSLv3, SSL was renamed to TLS.
# TLS stands for Transport Layer Security and started with TLSv1.0 which is an upgraded version of SSLv3.

# SSLv2 - (Disabled)
# SSLv3 - (Disabled)
# TLS 1.0 - Released in 1999, published as RFC 2246. (Disabled)
# TLS 1.1 - Released in 2006, published as RFC 4346. (Disabled)
# TLS 1.2 - Released in 2008, published as RFC 5246.

# https://docs.python.org/3.7/library/ssl.html#ssl.PROTOCOL_TLS_CLIENT
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

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


if self.trust == TRUST_ALL_CERTIFICATES:
ssl_context.check_hostname = False
# https://docs.python.org/3.7/library/ssl.html#ssl.CERT_NONE
ssl_context.verify_mode = ssl.CERT_NONE

# Must be load_default_certs, not set_default_verify_paths to work
# on Windows with system CAs.
ssl_context.load_default_certs()
if self.trusted_certificates is None:
# trust system CA certificates
ssl_context.check_hostname = True
ssl_context.verify_mode = ssl.CERT_REQUIRED
# Must be load_default_certs, not set_default_verify_paths to
# work on Windows with system CAs.
ssl_context.load_default_certs()
else:
self.trusted_certificates = tuple(self.trusted_certificates)
if not self.trusted_certificates:
# trust any certificate
ssl_context.check_hostname = False
# https://docs.python.org/3.7/library/ssl.html#ssl.CERT_NONE
ssl_context.verify_mode = ssl.CERT_NONE
else:
# trust the specified certificate(s)
ssl_context.check_hostname = True
ssl_context.verify_mode = ssl.CERT_REQUIRED
# Must be load_default_certs, not set_default_verify_paths to
# work on Windows with system CAs.
for cert in self.trusted_certificates:
ssl_context.load_verify_locations(cert)

return ssl_context

Expand Down
2 changes: 2 additions & 0 deletions testkit/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ RUN apt-get update && \
# Install our own CAs on the image.
# Assumes Linux Debian based image.
COPY CAs/* /usr/local/share/ca-certificates/
# Store custom CAs somewhere where the backend can find them later.
COPY CustomCAs/* /usr/local/share/custom-ca-certificates/
RUN update-ca-certificates

# Install pyenv
Expand Down
Loading