From 9230d64a21cf182fd575ae03016c049f6ded46c6 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Thu, 21 Nov 2024 05:16:37 +0100 Subject: [PATCH] Mutual TLS (#445) --- .../modules/ROOT/pages/connect-advanced.adoc | 75 ++++++++++++++ .../modules/ROOT/pages/connect-advanced.adoc | 62 ++++++++++++ .../modules/ROOT/pages/connect-advanced.adoc | 62 ++++++++++++ .../modules/ROOT/pages/connect-advanced.adoc | 98 +++++++++++++++++++ 4 files changed, 297 insertions(+) diff --git a/go-manual/modules/ROOT/pages/connect-advanced.adoc b/go-manual/modules/ROOT/pages/connect-advanced.adoc index 12fcf00c..d63e4d0c 100644 --- a/go-manual/modules/ROOT/pages/connect-advanced.adoc +++ b/go-manual/modules/ROOT/pages/connect-advanced.adoc @@ -101,6 +101,81 @@ You can switch users at both xref:query-simple.adoc#impersonation[query level] a `TokenManager` implementations and providers must not interact with the driver in any way, as this can cause deadlocks and undefined behavior. +[#mtls] +[role=label--new-5.27 label--not-on-aura] +== Mutual TLS (client-side certificates as 2FA) + +Mutual TLS (mTLS) allows you to use a client certificate as second factor for authenticating with the server. +The certificate can only be used together with an authentication token and is not a replacement of regular authentication, unless authentication is disabled on the server. + +The client's certificate and public key must be placed in the server's `/certificates/bolt/trusted` directory. For more information on server setup, see link:https://neo4j.com/docs/operations-manual/current/security/ssl-framework/#ssl-bolt-config[Configure SSL over Bolt]. + +[NOTE] +For mTLS to work, the driver's connection with the server must be encrypted, i.e. the xref:_connection_protocols_and_security[connection URI scheme] must be either `+s` or `+ssc` (ex. `neo4j+s://example.com:7687`). + +[.tabbed-example] +===== +[.include-with-static-certificate] +====== +Use link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j/auth#NewStaticClientCertificateProvider[`auth.NewStaticClientCertificateProvider()`] for static certificates. + +The method takes a link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j/auth#ClientCertificate[`ClientCertificate`] instance. + +[source, go, test-skip] +---- +certProvider, err := auth.NewStaticClientCertificateProvider(auth.ClientCertificate { + CertFile: "path/to/cert.pem", + KeyFile: "path/to/key.pem", + Password: "theCertPassword", +}) +if err != nil { + log.Fatalf("Failed to load certificate: %v", err) +} +_, _ = neo4j.NewDriverWithContext(dbUri, neo4j.BasicAuth(dbUser, dbPassword, ""), func(config *config.Config) { + config.ClientCertificateProvider = certProvider +}) +---- + +====== +[.include-with-rotating-certificate] +====== + +Use link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j/auth#NewRotatingClientCertificateProvider[`auth.NewRotatingClientCertificateProvider()`] for rotating certificates. + +The method takes a link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j/auth#ClientCertificate[`ClientCertificate`] instance. + +[source, go, test-skip] +---- +password := "theCertPassword" +certProvider, err := auth.NewRotatingClientCertificateProvider(auth.ClientCertificate { + CertFile: "path/to/cert.pem", + KeyFile: "path/to/key.pem", + Password: &password, +}) +if err != nil { + log.Fatalf("Failed to load certificate: %v", err) +} +_, _ = neo4j.NewDriverWithContext(dbUri, neo4j.BasicAuth(dbUser, dbPassword, ""), func(config *config.Config) { + config.ClientCertificateProvider = certProvider +}) +// use the driver a bit... +// when it's time to rotate the certificate... +err = provider.UpdateCertificate(auth.ClientCertificate { + CertFile: "path/to/new_cert.pem", + KeyFile: "path/to/new_key.pem", + Password: &password, +}) +if err != nil { + log.Fatalf("Failed to update certificate: %v", err) +} +// use the driver again... +---- + +====== +===== + + +For more information, see link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j/auth#ClientCertificateProvider[API docs -> `ClientCertificateProvider`]. + + == Custom address resolver When creating a `DriverWithContext` object, you can specify a _resolver_ function to resolve the connection address the driver is initialized with. diff --git a/java-manual/modules/ROOT/pages/connect-advanced.adoc b/java-manual/modules/ROOT/pages/connect-advanced.adoc index 75d79c4c..f5a7a0f0 100644 --- a/java-manual/modules/ROOT/pages/connect-advanced.adoc +++ b/java-manual/modules/ROOT/pages/connect-advanced.adoc @@ -118,6 +118,68 @@ You can switch users at both xref:query-simple.adoc#impersonation[query level] a `AuthTokenManager` objects must not interact with the driver in any way, as this can cause deadlocks and undefined behavior. +[#mtls] +[role=label--new-5.27 label--not-on-aura] +== Mutual TLS (client-side certificates as 2FA) + +Mutual TLS (mTLS) allows you to use a client certificate as second factor for authenticating with the server. +The certificate can only be used together with an authentication token and is not a replacement of regular authentication, unless authentication is disabled on the server. + +The client's certificate and public key must be placed in the server's `/certificates/bolt/trusted` directory. For more information on server setup, see link:https://neo4j.com/docs/operations-manual/current/security/ssl-framework/#ssl-bolt-config[Configure SSL over Bolt]. + +[NOTE] +For mTLS to work, the driver's connection with the server must be encrypted, i.e. the xref:_connection_protocols_and_security[connection URI scheme] must be either `+s` or `+ssc` (ex. `neo4j+s://example.com:7687`). + +Use link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/ClientCertificateManagers.html[`ClientCertificateManagers.rotating()`] for both static and rotating certificates. + +The method takes a link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/ClientCertificates.html[`ClientCertificate`] instance. + +For rotating certificates, use the `.rotate()` method; static certificates don't need to be updated. + +[.tabbed-example] +===== +[.include-with-static-certificate] +====== + +[source, java, test-skip] +---- +var certificateFile = new File("/path/to/cert.pem"); +var privateKeyFile = new File("/path/to/key.pem"); +var keyPassword = "password"; // optional +var certificate = ClientCertificates.of(certificateFile, privateKeyFile, keyPassword); +var certificateManager = ClientCertificateManagers.rotating(certificate); +try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword), certificateManager)) { + // use the driver +} +---- + +====== +[.include-with-rotating-certificate] +====== + +[source, java, test-skip] +---- +var certificateFile = new File("/path/to/cert.pem"); +var privateKeyFile = new File("/path/to/key.pem"); +var keyPassword = "password"; // optional +var certificate = ClientCertificates.of(certificateFile, privateKeyFile, keyPassword); +// instantiate the rotating certificate with an initial one +var certificateManager = ClientCertificateManagers.rotating(certificate); +try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword), certificateManager)) { + // use the driver... + // ... until it's time to rotate the certificate + var updatedCertificate = ClientCertificates.of( + new File("/path/to/new/cert.pem") + new File("/path/to/new/key.pem"), + "newPassword" // optional + ); + certificateManager.rotate(updatedCertificate); + // use the driver some more - new connections are opened with the new certificate +} +---- + +====== +===== + + == Logging By default, the driver logs `INFO` messages through the Java logging framework `java.util.logging`. diff --git a/javascript-manual/modules/ROOT/pages/connect-advanced.adoc b/javascript-manual/modules/ROOT/pages/connect-advanced.adoc index 244de1ef..011bbc01 100644 --- a/javascript-manual/modules/ROOT/pages/connect-advanced.adoc +++ b/javascript-manual/modules/ROOT/pages/connect-advanced.adoc @@ -120,6 +120,68 @@ You can switch users at both xref:query-simple.adoc#impersonation[query level] a `AuthManagers` (including provider functions passed to `expirationBasedAuthTokenManager()`) must not interact with the driver in any way, as this can cause deadlocks and undefined behavior. +[#mtls] +[role=label--new-5.27 label--not-on-aura] +== Mutual TLS (client-side certificates as 2FA) + +Mutual TLS (mTLS) allows you to use a client certificate as second factor for authenticating with the server. +The certificate can only be used together with an authentication token and is not a replacement of regular authentication, unless authentication is disabled on the server. + +The client's certificate and public key must be placed in the server's `/certificates/bolt/trusted` directory. For more information on server setup, see link:https://neo4j.com/docs/operations-manual/current/security/ssl-framework/#ssl-bolt-config[Configure SSL over Bolt]. + +[NOTE] +For mTLS to work, the driver's connection with the server must be encrypted, i.e. the xref:_connection_protocols_and_security[connection URI scheme] must be either `+s` or `+ssc` (ex. `neo4j+s://example.com:7687`). + +[.tabbed-example] +===== +[.include-with-static-certificate] +====== +Use the driver configuration option link:https://neo4j.com/docs/api/javascript-driver/current/class/lib6/types.js~Config.html#instance-member-clientCertificate[`clientCertificate`] to provide the certificate information as an object. + +[source, javascript, test-skip] +---- +const driver = neo4j.driver(URI, neo4j.auth.basic(USER, PASSWORD), { + clientCertificate: { + certfile: '/path/to/cert.cert', + keyfile: '/path/to/cert.pem', + password: 'the_key_password' // optional + } +}) +---- + +====== +[.include-with-rotating-certificate] +====== + +Instantiate the certificate object via link:https://neo4j.com/docs/api/javascript-driver/current/class/lib6/client-certificate.js~ClientCertificateProviders.html[`clientCertificateProviders.rotating`] and provide it when instantiating the driver via the configuration option link:https://neo4j.com/docs/api/javascript-driver/current/class/lib6/types.js~Config.html#instance-member-clientCertificate[`clientCertificate`]. + +[source, javascript, test-skip] +---- +const initialClientCertificate: { + certfile: '/path/to/cert.cert', + keyfile: '/path/to/cert.pem', + password: 'the_key_password' // optional +} +const clientCertificateProvider = neo4j.clientCertificateProviders.rotating({ + initialCertificate: initialClientCertificate +}) +const driver = neo4j.driver(URI, MY_CREDENTIALS, { + clientCertificate: clientCertificateProvider +}) +// use the driver... +// ... until it's time to update the certificate +clientCertificateProvider.updateCertificate({ + certfile: '/path/to/new_cert.cert', + keyfile: '/path/to/new_cert.pem', + password: 'the_new_key_password' // optional +}) +// use the driver some more +---- + +====== +===== + + == Custom address resolver When creating a `Driver` object, you can specify a _resolver_ function to resolve the connection address the driver is initialized with. diff --git a/python-manual/modules/ROOT/pages/connect-advanced.adoc b/python-manual/modules/ROOT/pages/connect-advanced.adoc index ea052ecb..c1d391a5 100644 --- a/python-manual/modules/ROOT/pages/connect-advanced.adoc +++ b/python-manual/modules/ROOT/pages/connect-advanced.adoc @@ -107,6 +107,104 @@ You can switch users at both xref:query-simple.adoc#impersonation[query level] a `AuthManagers` (including provider functions passed to `AuthManagers.expiration_based()`) must not interact with the driver in any way, as this can cause deadlocks and undefined behavior. +[#mtls] +[role=label--new-5.27 label--not-on-aura] +== Mutual TLS (client-side certificates as 2FA) + +Mutual TLS (mTLS) allows you to use a client certificate as second factor for authenticating with the server. +The certificate can only be used together with an authentication token and is not a replacement of regular authentication, unless authentication is disabled on the server. + +The client's certificate and public key must be placed in the server's `/certificates/bolt/trusted` directory. For more information on server setup, see link:https://neo4j.com/docs/operations-manual/current/security/ssl-framework/#ssl-bolt-config[Configure SSL over Bolt]. + +[NOTE] +For mTLS to work, the driver's connection with the server must be encrypted, i.e. the xref:_connection_protocols_and_security[connection URI scheme] must be either `+s` or `+ssc` (ex. `neo4j+s://example.com:7687`). + +[.tabbed-example] +===== +[.include-with-static-certificate] +====== +Use link:https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.auth_management.ClientCertificateProviders.static[`ClientCertificateProviders.static()`] for static certificates. + +The method takes a link:https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.auth_management.ClientCertificate[`ClientCertificate`] instance, which takes the same parameters as Python's link:https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain[`ssl.SSLContext.load_cert_chain()`]. + +[source, python, test-skip] +---- +import neo4j +from neo4j.auth_management import ( + ClientCertificate, + ClientCertificateProviders, +) +URI = "" +AUTH = ("", "") +cert_provider = ClientCertificateProviders.static( + ClientCertificate( + # path to certificate + "path/to/cert.pem", + # path to private key (optional; needed if certificate does not contain private key too) + "path/to/key.pem", + # password to decrypt private key (can be function or string) (optional) + lambda: "password", + ) +) +with neo4j.GraphDatabase.driver( + URI, + auth=AUTH, + client_certificate=cert_provider, +) as driver: + ... +---- + +====== +[.include-with-rotating-certificate] +====== + +Use link:https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.auth_management.ClientCertificateProviders.rotating[`ClientCertificateProviders.rotating()`] for rotating certificates. + +The method takes a link:https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.auth_management.ClientCertificate[`ClientCertificate`] instance. + +[source, python, test-skip] +---- +import neo4j +from neo4j.auth_management import ( + ClientCertificate, + ClientCertificateProviders, +) +URI = "" +AUTH = ("", "") +cert_provider = ClientCertificateProviders.rotating( + ClientCertificate( + # path to public certificate to load + "path/to/cert.pem", + # path to private key to load + "path/to/key.pem", + # password to decrypt private key (can be a function or string) + # see also Python's ssl.SSLContext.load_cert_chain() + lambda: "password", + ) +) +driver = neo4j.GraphDatabase.driver( + URI + auth=(USERNAME, PASSWORD), + client_certificate=cert_provider +) +# use the driver... +# ... until the certificate needs to be rotated +cert_provider.update_certificate( + ClientCertificate( + certfile="path/to/new/cert.pem", + keyfile="path/to/new/key.pem", + password=lambda: "new_super_secret_password" + ) +) +# use the driver again, until the certificate needs to be +# rotated again +# ... +---- + +====== +===== + +For more information, see link:https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.auth_management.ClientCertificateProvider[API docs -> `ClientCertificateProvider`]. + + == Custom address resolver When creating a `Driver` object, you can specify a _resolver_ function to resolve any addresses the driver receives ahead of DNS resolution.