Skip to content

Mature Feature: mTLS #1118

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
Nov 12, 2024
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
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