Skip to content

Commit

Permalink
Mature feature: mTLS (#1118)
Browse files Browse the repository at this point in the history
  • Loading branch information
robsdedude authored Nov 12, 2024
1 parent 3313018 commit 5238238
Show file tree
Hide file tree
Showing 16 changed files with 67 additions and 164 deletions.
7 changes: 2 additions & 5 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -653,16 +653,13 @@ Specify a client certificate or certificate provider for mutual TLS (mTLS) authe
This setting does not have any effect if ``encrypted`` is set to ``False``
(and the URI scheme is ``bolt://`` or ``neo4j://``) or a custom ``ssl_context`` is configured.

**This is a preview** (see :ref:`filter-warnings-ref`).
It might be changed without following the deprecation policy.
See also
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features

:Type: :class:`.ClientCertificate`, :class:`.ClientCertificateProvider` or :data:`None`.
:Default: :data:`None`

.. versionadded:: 5.19

.. versionchanged:: 5.27 Stabilized from preview.

.. autoclass:: neo4j.auth_management.ClientCertificate
:members:

Expand Down
7 changes: 2 additions & 5 deletions docs/source/async_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -446,16 +446,13 @@ Specify a client certificate or certificate provider for mutual TLS (mTLS) authe
This setting does not have any effect if ``encrypted`` is set to ``False``
(and the URI scheme is ``bolt://`` or ``neo4j://``) or a custom ``ssl_context`` is configured.

**This is a preview** (see :ref:`filter-warnings-ref`).
It might be changed without following the deprecation policy.
See also
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features

:Type: :class:`.ClientCertificate`, :class:`.AsyncClientCertificateProvider` or :data:`None`.
:Default: :data:`None`

.. versionadded:: 5.19

.. versionchanged:: 5.27 Stabilized from preview.

.. autoclass:: neo4j.auth_management.AsyncClientCertificateProvider
:members:

Expand Down
19 changes: 4 additions & 15 deletions src/neo4j/_async/auth_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
expiring_auth_has_expired,
ExpiringAuth,
)
from .._meta import preview


if t.TYPE_CHECKING:
Expand Down Expand Up @@ -331,12 +330,6 @@ class AsyncRotatingClientCertificateProvider(AsyncClientCertificateProvider):
From that point on, the new certificate will be used for all new
connections until :meth:`update_certificate` is called again and so on.
**This is a preview** (see :ref:`filter-warnings-ref`).
It might be changed without following the deprecation policy.
See also
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
Example::
from neo4j import AsyncGraphDatabase
Expand Down Expand Up @@ -386,6 +379,8 @@ class AsyncRotatingClientCertificateProvider(AsyncClientCertificateProvider):
implementation internal. This entails removing the possibility to
directly instantiate this class. Please use the factory method
:meth:`.AsyncClientCertificateProviders.rotating` instead.
.. versionchanged:: 5.27 Stabilized from preview.
"""

@abc.abstractmethod
Expand Down Expand Up @@ -414,17 +409,12 @@ class AsyncClientCertificateProviders:
"""
A collection of :class:`.AsyncClientCertificateProvider` factories.
**This is a preview** (see :ref:`filter-warnings-ref`).
It might be changed without following the deprecation policy.
See also
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
.. versionadded:: 5.19
.. versionchanged:: 5.27 Stabilized from preview.
"""

@staticmethod
@preview("Mutual TLS is a preview feature.")
def static(cert: ClientCertificate) -> AsyncClientCertificateProvider:
"""
Create a static client certificate provider.
Expand All @@ -435,7 +425,6 @@ def static(cert: ClientCertificate) -> AsyncClientCertificateProvider:
return _AsyncStaticClientCertificateProvider(cert)

@staticmethod
@preview("Mutual TLS is a preview feature.")
def rotating(
initial_cert: ClientCertificate,
) -> AsyncRotatingClientCertificateProvider:
Expand Down
2 changes: 0 additions & 2 deletions src/neo4j/_async/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,6 @@ def driver(
config["client_certificate"] = (
_AsyncStaticClientCertificateProvider(client_certificate)
)
if client_certificate is not None:
preview_warn("Mutual TLS is a preview feature.", stack_level=2)

# TODO: 6.0 - remove "trust" config option
if "trust" in config and config["trust"] not in {
Expand Down
27 changes: 6 additions & 21 deletions src/neo4j/_auth_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import typing as t
from dataclasses import dataclass

from ._meta import preview


if t.TYPE_CHECKING:
from os import PathLike
Expand Down Expand Up @@ -215,7 +213,6 @@ async def handle_security_exception(
...


@preview("Mutual TLS is a preview feature.")
@dataclass
class ClientCertificate:
"""
Expand All @@ -224,13 +221,9 @@ class ClientCertificate:
The attributes are the same as the arguments to
:meth:`ssl.SSLContext.load_cert_chain()`.
**This is a preview** (see :ref:`filter-warnings-ref`).
It might be changed without following the deprecation policy.
See also
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
.. versionadded:: 5.19
.. versionchanged:: 5.27 Stabilized from preview.
"""

certfile: str | bytes | PathLike[str] | PathLike[bytes]
Expand Down Expand Up @@ -267,13 +260,9 @@ class ClientCertificateProvider(_Protocol, metaclass=abc.ABCMeta):
The provider **must not** interact with the driver in any way as this
can cause deadlocks and undefined behaviour.
**This is a preview** (see :ref:`filter-warnings-ref`).
It might be changed without following the deprecation policy.
See also
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
.. versionadded:: 5.19
.. versionchanged:: 5.27 Stabilized from preview.
"""

@abc.abstractmethod
Expand All @@ -300,17 +289,13 @@ class AsyncClientCertificateProvider(_Protocol, metaclass=abc.ABCMeta):
The package provides some default implementations of this class in
:class:`.AsyncClientCertificateProviders` for convenience.
**This is a preview** (see :ref:`filter-warnings-ref`).
It might be changed without following the deprecation policy.
See also
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
.. seealso::
:class:`.ClientCertificateProvider`,
:class:`.AsyncClientCertificateProviders`
.. versionadded:: 5.19
.. versionchanged:: 5.27 Stabilized from preview.
"""

@abc.abstractmethod
Expand Down
19 changes: 4 additions & 15 deletions src/neo4j/_sync/auth_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
expiring_auth_has_expired,
ExpiringAuth,
)
from .._meta import preview


if t.TYPE_CHECKING:
Expand Down Expand Up @@ -331,12 +330,6 @@ class RotatingClientCertificateProvider(ClientCertificateProvider):
From that point on, the new certificate will be used for all new
connections until :meth:`update_certificate` is called again and so on.
**This is a preview** (see :ref:`filter-warnings-ref`).
It might be changed without following the deprecation policy.
See also
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
Example::
from neo4j import GraphDatabase
Expand Down Expand Up @@ -386,6 +379,8 @@ class RotatingClientCertificateProvider(ClientCertificateProvider):
implementation internal. This entails removing the possibility to
directly instantiate this class. Please use the factory method
:meth:`.ClientCertificateProviders.rotating` instead.
.. versionchanged:: 5.27 Stabilized from preview.
"""

@abc.abstractmethod
Expand Down Expand Up @@ -414,17 +409,12 @@ class ClientCertificateProviders:
"""
A collection of :class:`.ClientCertificateProvider` factories.
**This is a preview** (see :ref:`filter-warnings-ref`).
It might be changed without following the deprecation policy.
See also
https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
.. versionadded:: 5.19
.. versionchanged:: 5.27 Stabilized from preview.
"""

@staticmethod
@preview("Mutual TLS is a preview feature.")
def static(cert: ClientCertificate) -> ClientCertificateProvider:
"""
Create a static client certificate provider.
Expand All @@ -435,7 +425,6 @@ def static(cert: ClientCertificate) -> ClientCertificateProvider:
return _StaticClientCertificateProvider(cert)

@staticmethod
@preview("Mutual TLS is a preview feature.")
def rotating(
initial_cert: ClientCertificate,
) -> RotatingClientCertificateProvider:
Expand Down
2 changes: 0 additions & 2 deletions src/neo4j/_sync/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,6 @@ def driver(
config["client_certificate"] = (
_StaticClientCertificateProvider(client_certificate)
)
if client_certificate is not None:
preview_warn("Mutual TLS is a preview feature.", stack_level=2)

# TODO: 6.0 - remove "trust" config option
if "trust" in config and config["trust"] not in {
Expand Down
6 changes: 0 additions & 6 deletions testkitbackend/_async/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,10 @@ async def new_driver(backend, data):
client_cert_provider_id
]
data.mark_item_as_read_if_equals("clientCertificate", None)
expected_warnings.append(
(neo4j.PreviewWarning, r"Mutual TLS is a preview feature\.")
)
else:
client_cert = fromtestkit.to_client_cert(data, "clientCertificate")
if client_cert is not None:
kwargs["client_certificate"] = client_cert
expected_warnings.append(
(neo4j.PreviewWarning, r"Mutual TLS is a preview feature\.")
)
if data["resolverRegistered"] or data["domainNameResolverRegistered"]:
kwargs["resolver"] = resolution_func(
backend,
Expand Down
6 changes: 0 additions & 6 deletions testkitbackend/_sync/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,10 @@ def new_driver(backend, data):
client_cert_provider_id
]
data.mark_item_as_read_if_equals("clientCertificate", None)
expected_warnings.append(
(neo4j.PreviewWarning, r"Mutual TLS is a preview feature\.")
)
else:
client_cert = fromtestkit.to_client_cert(data, "clientCertificate")
if client_cert is not None:
kwargs["client_certificate"] = client_cert
expected_warnings.append(
(neo4j.PreviewWarning, r"Mutual TLS is a preview feature\.")
)
if data["resolverRegistered"] or data["domainNameResolverRegistered"]:
kwargs["resolver"] = resolution_func(
backend,
Expand Down
10 changes: 3 additions & 7 deletions testkitbackend/fromtestkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
)

from ._preview_imports import NotificationDisabledClassification
from ._warning_check import warnings_check


def to_cypher_and_params(data):
Expand Down Expand Up @@ -222,12 +221,9 @@ def to_client_cert(data, key) -> ClientCertificate | None:
return None
data[key].mark_item_as_read_if_equals("name", "ClientCertificate")
cert_data = data[key]["data"]
with warnings_check(
((neo4j.PreviewWarning, r"Mutual TLS is a preview feature\."),)
):
return ClientCertificate(
cert_data["certfile"], cert_data["keyfile"], cert_data["password"]
)
return ClientCertificate(
cert_data["certfile"], cert_data["keyfile"], cert_data["password"]
)


def set_notifications_config(config, data):
Expand Down
15 changes: 3 additions & 12 deletions tests/unit/async_/test_auth_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from neo4j import (
Auth,
basic_auth,
PreviewWarning,
)
from neo4j._meta import copy_signature
from neo4j.auth_management import (
Expand Down Expand Up @@ -261,22 +260,14 @@ def client_cert_factory() -> t.Callable[[], ClientCertificate]:
i = 0

def factory() -> ClientCertificate:
with pytest.warns(PreviewWarning, match="Mutual TLS"):
return ClientCertificate(f"cert{i}")
return ClientCertificate(f"cert{i}")

return factory


@copy_signature(AsyncClientCertificateProviders.static)
def static_cert_provider(*args, **kwargs):
with pytest.warns(PreviewWarning, match="Mutual TLS"):
return AsyncClientCertificateProviders.static(*args, **kwargs)
static_cert_provider = AsyncClientCertificateProviders.static


@copy_signature(AsyncClientCertificateProviders.rotating)
def rotating_cert_provider(*args, **kwargs):
with pytest.warns(PreviewWarning, match="Mutual TLS"):
return AsyncClientCertificateProviders.rotating(*args, **kwargs)
rotating_cert_provider = AsyncClientCertificateProviders.rotating


@mark_async_test
Expand Down
10 changes: 3 additions & 7 deletions tests/unit/async_/test_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import pytest

from neo4j import (
PreviewWarning,
TrustAll,
TrustCustomCAs,
TrustSystemCAs,
Expand Down Expand Up @@ -426,10 +425,8 @@ async def test_custom_ssl_context(encrypted, trusted_certificates):
async def test_client_certificate(trusted_certificates, mocker) -> None:
ssl_context_mock = mocker.patch("ssl.SSLContext", autospec=True)

with pytest.warns(PreviewWarning, match="Mutual TLS"):
cert = ClientCertificate("certfile", "keyfile", "password")
with pytest.warns(PreviewWarning, match="Mutual TLS"):
provider = AsyncClientCertificateProviders.rotating(cert)
cert = ClientCertificate("certfile", "keyfile", "password")
provider = AsyncClientCertificateProviders.rotating(cert)
pool_config = AsyncPoolConfig.consume(
{
"client_certificate": provider,
Expand All @@ -455,8 +452,7 @@ async def test_client_certificate(trusted_certificates, mocker) -> None:
ssl_context_mock.assert_not_called()

# test cache invalidation
with pytest.warns(PreviewWarning, match="Mutual TLS"):
cert2 = ClientCertificate("certfile2", "keyfile2", "password2")
cert2 = ClientCertificate("certfile2", "keyfile2", "password2")
await provider.update_certificate(cert2)

ssl_context = await pool_config.get_ssl_context()
Expand Down
Loading

0 comments on commit 5238238

Please sign in to comment.