View a markdown version of this page

Verwenden des Bolt-Protokolls für openCypher-Abfragen an Neptune - Amazon Neptune

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Verwenden des Bolt-Protokolls für openCypher-Abfragen an Neptune

Bolt ist ein aussagekräftiges client/server Protokoll, das ursprünglich von Neo4j entwickelt und unter der Creative Commons 3.0 Attribution-Lizenz lizenziert wurde. ShareAlike Es ist Client-gesteuert, was bedeutet, dass der Client stets den Nachrichtenaustausch initiiert.

Um über die Bolt-Treiber von Neo4j eine Verbindung zu Neptune herzustellen, ersetzen Sie einfach mittels des bolt-URI-Schemas URL und Portnummer durch Ihre Cluster-Endpunkte. Wenn Sie eine einzelne Neptune-Instance ausführen, verwenden Sie den Endpunkt read_write. Wenn mehrere Instances ausgeführt werden, werden zwei Treiber empfohlen, ein Treiber für den Writer und ein Treiber für alle Read Replicas. Wenn es nur die beiden Standardendpunkte gibt, reichen ein read_write- und ein read_only-Treiber aus. Wenn es jedoch auch benutzerdefinierte Endpunkte gibt, sollten Sie für jeden Endpunkt eine Treiber-Instance erstellen.

Anmerkung

Obwohl die Bolt-Spezifikation besagt, dass Bolt eine Verbindung entweder über TCP oder herstellen kann WebSockets, unterstützt Neptune nur TCP-Verbindungen für Bolt.

Neptune ermöglicht bis zu 1000 gleichzeitige Bolt-Verbindungen auf allen Instance-Größen außer t3.medium und t4g.medium. Auf den Instances t3.medium und t4g.medium sind nur 512 Verbindungen zulässig.

Beispiele für openCypher-Abfragen in verschiedenen Sprachen, die Bolt-Treiber verwenden, finden Sie in der Neo4j-Dokumentation Drivers & Language Guides.

Wichtig

Die Neo4j Bolt-Treiber für Python JavaScript, .NET und Golang unterstützten zunächst nicht die automatische Verlängerung von AWS Signature v4-Authentifizierungstoken. Das bedeutet, dass sich der Treiber nach Ablauf der Signatur (häufig innerhalb von 5 Minuten) nicht authentifizieren konnte und nachfolgende Anforderungen fehlschlugen. Die folgenden Python- JavaScript, .NET- und Go-Beispiele waren alle von diesem Problem betroffen.

Weitere Informationen finden Sie unter Neo4j Python-Treiberproblem #834, Neo4j .NET-Problem #664, JavaScriptNeo4j-Treiberproblem #993 und Neo4j GoLang-Treiberproblem #429.

Ab Treiberversion 5.8.0 wurde eine neue Vorschau-API zur erneuten Authentifizierung für den Go-Treiber veröffentlicht (siehe v5.8.0 – Feedback zur erneuten Authentifizierung gewünscht).

Herstellen einer Verbindung mit Neptune über Bolt mit Java

Sie können einen Treiber für jede gewünschte Version aus dem Maven-MVN-Repository herunterladen oder diese Abhängigkeit zu Ihrem Projekt hinzufügen:

<dependency> <groupId>org.neo4j.driver</groupId> <artifactId>neo4j-java-driver</artifactId> <version>4.3.3</version> </dependency>

Um dann mit einem dieser Bolt-Treiber eine Verbindung zu Neptune in Java herzustellen, erstellen Sie eine Treiberinstanz für die primary/writer Instanz in Ihrem Cluster mit Code wie dem folgenden:

import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; final Driver driver = GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)", AuthTokens.none(), Config.builder().withEncryption() .withTrustStrategy(TrustStrategy.trustSystemCertificates()) .build());

Wenn Sie über eine oder mehrere Leserreplikate verfügen, können Sie auf ähnliche Weise eine Treiber-Instance für sie erstellen, indem Sie den folgenden Code verwenden:

final Driver read_only_driver = // (without connection timeout) GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)", Config.builder().withEncryption() .withTrustStrategy(TrustStrategy.trustSystemCertificates()) .build());

Oder mit einem Timeout:

final Driver read_only_timeout_driver = // (with connection timeout) GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)", Config.builder().withConnectionTimeout(30, TimeUnit.SECONDS) .withEncryption() .withTrustStrategy(TrustStrategy.trustSystemCertificates()) .build());

Wenn es benutzerdefinierte Endpunkte gibt, kann es sich auch lohnen, für jeden dieser Endpunkte eine Treiber-Instance zu erstellen.

Beispiel für eine Python-openCypher-Abfrage über Bolt

So erstellen Sie eine openCypher-Abfrage in Python über Bolt:

python -m pip install neo4j
from neo4j import GraphDatabase uri = "bolt://(your cluster endpoint URL):(your cluster port)" driver = GraphDatabase.driver(uri, auth=("username", "password"), encrypted=True)

Beachten Sie, dass die auth-Parameter ignoriert werden.

Beispiel für eine .NET-openCypher-Abfrage über Bolt

Um eine OpenCypher-Abfrage in.NET mit Bolt zu erstellen, müssen Sie zunächst den Neo4j-Treiber mit installieren. NuHet Um synchrone Aufrufe auszuführen, verwenden Sie die .Simple-Version wie folgt:

Install-Package Neo4j.Driver.Simple-4.3.0
using Neo4j.Driver; namespace hello { // This example creates a node and reads a node in a Neptune // Cluster where IAM Authentication is not enabled. public class HelloWorldExample : IDisposable { private bool _disposed = false; private readonly IDriver _driver; private static string url = "bolt://(your cluster endpoint URL):(your cluster port)"; private static string createNodeQuery = "CREATE (a:Greeting) SET a.message = 'HelloWorldExample'"; private static string readNodeQuery = "MATCH(n:Greeting) RETURN n.message"; ~HelloWorldExample() => Dispose(false); public HelloWorldExample(string uri) { _driver = GraphDatabase.Driver(uri, AuthTokens.None, o => o.WithEncryptionLevel(EncryptionLevel.Encrypted)); } public void createNode() { // Open a session using (var session = _driver.Session()) { // Run the query in a write transaction var greeting = session.WriteTransaction(tx => { var result = tx.Run(createNodeQuery); // Consume the result return result.Consume(); }); // The output will look like this: // ResultSummary{Query=`CREATE (a:Greeting) SET a.message = 'HelloWorldExample"..... Console.WriteLine(greeting); } } public void retrieveNode() { // Open a session using (var session = _driver.Session()) { // Run the query in a read transaction var greeting = session.ReadTransaction(tx => { var result = tx.Run(readNodeQuery); // Consume the result. Read the single node // created in a previous step. return result.Single()[0].As<string>(); }); // The output will look like this: // HelloWorldExample Console.WriteLine(greeting); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _driver?.Dispose(); } _disposed = true; } public static void Main() { using (var apiCaller = new HelloWorldExample(url)) { apiCaller.createNode(); apiCaller.retrieveNode(); } } } }

Ein Beispiel für eine Java-openCypher-Abfrage über Bolt mit IAM-Authentifizierung

Der folgende Java-Code zeigt, wie openCypher-Abfragen in Java über Bolt mit IAM-Authentifizierung ausgeführt werden. Der JavaDoc Kommentar beschreibt seine Verwendung. Sobald eine Treiber-Instance verfügbar ist, können Sie diese verwenden, um mehrere authentifizierte Anfragen zu senden.

package software.amazon.neptune.bolt; import com.amazonaws.DefaultRequest; import com.amazonaws.Request; import com.amazonaws.auth.AWS4Signer; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.http.HttpMethodName; import com.google.gson.Gson; import lombok.Builder; import lombok.Getter; import lombok.NonNull; import org.neo4j.driver.Value; import org.neo4j.driver.Values; import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.value.StringValue; import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.Map; import static com.amazonaws.auth.internal.SignerConstants.AUTHORIZATION; import static com.amazonaws.auth.internal.SignerConstants.HOST; import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_DATE; import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_SECURITY_TOKEN; /** * Use this class instead of `AuthTokens.basic` when working with an IAM * auth-enabled server. It works the same as `AuthTokens.basic` when using * static credentials, and avoids making requests with an expired signature * when using temporary credentials. Internally, it generates a new signature * on every invocation (this may change in a future implementation). * * Note that authentication happens only the first time for a pooled connection. * * Typical usage: * * NeptuneAuthToken authToken = NeptuneAuthToken.builder() * .credentialsProvider(credentialsProvider) * .region("aws region") * .url("cluster endpoint url") * .build(); * * Driver driver = GraphDatabase.driver( * authToken.getUrl(), * authToken, * config * ); */ public class NeptuneAuthToken extends InternalAuthToken { private static final String SCHEME = "basic"; private static final String REALM = "realm"; private static final String SERVICE_NAME = "neptune-db"; private static final String HTTP_METHOD_HDR = "HttpMethod"; private static final String DUMMY_USERNAME = "username"; @NonNull private final String region; @NonNull @Getter private final String url; @NonNull private final AWSCredentialsProvider credentialsProvider; private final Gson gson = new Gson(); @Builder private NeptuneAuthToken( @NonNull final String region, @NonNull final String url, @NonNull final AWSCredentialsProvider credentialsProvider ) { // The superclass caches the result of toMap(), which we don't want super(Collections.emptyMap()); this.region = region; this.url = url; this.credentialsProvider = credentialsProvider; } @Override public Map<String, Value> toMap() { final Map<String, Value> map = new HashMap<>(); map.put(SCHEME_KEY, Values.value(SCHEME)); map.put(PRINCIPAL_KEY, Values.value(DUMMY_USERNAME)); map.put(CREDENTIALS_KEY, new StringValue(getSignedHeader())); map.put(REALM_KEY, Values.value(REALM)); return map; } private String getSignedHeader() { final Request<Void> request = new DefaultRequest<>(SERVICE_NAME); request.setHttpMethod(HttpMethodName.GET); request.setEndpoint(URI.create(url)); // Comment out the following line if you're using an engine version older than 1.2.0.0 request.setResourcePath("/opencypher"); final AWS4Signer signer = new AWS4Signer(); signer.setRegionName(region); signer.setServiceName(request.getServiceName()); signer.sign(request, credentialsProvider.getCredentials()); return getAuthInfoJson(request); } private String getAuthInfoJson(final Request<Void> request) { final Map<String, Object> obj = new HashMap<>(); obj.put(AUTHORIZATION, request.getHeaders().get(AUTHORIZATION)); obj.put(HTTP_METHOD_HDR, request.getHttpMethod()); obj.put(X_AMZ_DATE, request.getHeaders().get(X_AMZ_DATE)); obj.put(HOST, request.getHeaders().get(HOST)); obj.put(X_AMZ_SECURITY_TOKEN, request.getHeaders().get(X_AMZ_SECURITY_TOKEN)); return gson.toJson(obj); } }

Ein Beispiel für eine Python-openCypher-Abfrage über Bolt mit IAM-Authentifizierung

Mittels der folgenden Python-Klasse können Sie openCypher-Abfragen in Python über Bolt mit IAM-Authentifizierung ausführen:

import json from neo4j import Auth from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials from botocore.auth import ( SigV4Auth, _host_from_url, ) SCHEME = "basic" REALM = "realm" SERVICE_NAME = "neptune-db" DUMMY_USERNAME = "username" HTTP_METHOD_HDR = "HttpMethod" HTTP_METHOD = "GET" AUTHORIZATION = "Authorization" X_AMZ_DATE = "X-Amz-Date" X_AMZ_SECURITY_TOKEN = "X-Amz-Security-Token" HOST = "Host" class NeptuneAuthToken(Auth): def __init__( self, credentials: Credentials, region: str, url: str, **parameters ): # Do NOT add "/opencypher" in the line below if you're using an engine version older than 1.2.0.0 request = AWSRequest(method=HTTP_METHOD, url=url + "/opencypher") request.headers.add_header("Host", _host_from_url(request.url)) sigv4 = SigV4Auth(credentials, SERVICE_NAME, region) sigv4.add_auth(request) auth_obj = { hdr: request.headers[hdr] for hdr in [AUTHORIZATION, X_AMZ_DATE, X_AMZ_SECURITY_TOKEN, HOST] } auth_obj[HTTP_METHOD_HDR] = request.method creds: str = json.dumps(auth_obj) super().__init__(SCHEME, DUMMY_USERNAME, creds, REALM, **parameters)

Sie erstellen mittels dieser Klasse einen Treiber wie folgt:

authToken = NeptuneAuthToken(creds, REGION, URL) driver = GraphDatabase.driver(URL, auth=authToken, encrypted=True)

Ein Beispiel für Node.js mit IAM-Authentifizierung und Bolt

Der folgende Code für Node.js verwendet das AWS SDK für JavaScript Version 3 und die ES6 Syntax, um einen Treiber zu erstellen, der Anfragen authentifiziert:

import neo4j from "neo4j-driver"; import { HttpRequest } from "@smithy/protocol-http"; import { defaultProvider } from "@aws-sdk/credential-provider-node"; import { SignatureV4 } from "@smithy/signature-v4"; import crypto from "@aws-crypto/sha256-js"; const { Sha256 } = crypto; import assert from "node:assert"; const region = "us-west-2"; const serviceName = "neptune-db"; const host = "(your cluster endpoint URL)"; const port = 8182; const protocol = "bolt"; const hostPort = host + ":" + port; const url = protocol + "://" + hostPort; const createQuery = "CREATE (n:Greeting {message: 'Hello'}) RETURN ID(n)"; const readQuery = "MATCH(n:Greeting) WHERE ID(n) = $id RETURN n.message"; async function signedHeader() { const req = new HttpRequest({ method: "GET", protocol: protocol, hostname: host, port: port, // Comment out the following line if you're using an engine version older than 1.2.0.0 path: "/opencypher", headers: { host: hostPort } }); const signer = new SignatureV4({ credentials: defaultProvider(), region: region, service: serviceName, sha256: Sha256 }); return signer.sign(req, { unsignableHeaders: new Set(["x-amz-content-sha256"]) }) .then((signedRequest) => { const authInfo = { "Authorization": signedRequest.headers["authorization"], "HttpMethod": signedRequest.method, "X-Amz-Date": signedRequest.headers["x-amz-date"], "Host": signedRequest.headers["host"], "X-Amz-Security-Token": signedRequest.headers["x-amz-security-token"] }; return JSON.stringify(authInfo); }); } async function createDriver() { let authToken = { scheme: "basic", realm: "realm", principal: "username", credentials: await signedHeader() }; return neo4j.driver(url, authToken, { encrypted: "ENCRYPTION_ON", trust: "TRUST_SYSTEM_CA_SIGNED_CERTIFICATES", maxConnectionPoolSize: 1, // logging: neo4j.logging.console("debug") } ); } async function unmanagedTxn(driver) { const session = driver.session(); const tx = session.beginTransaction(); try { const created = await tx.run(createQuery); const matched = await tx.run(readQuery, { id: created.records[0].get(0) }); const msg = matched.records[0].get("n.message"); assert.equal(msg, "Hello"); await tx.commit(); } catch (err) { // The transaction will be rolled back, now handle the error. console.log(err); } finally { await session.close(); } } const driver = await createDriver(); try { await unmanagedTxn(driver); } catch (err) { console.log(err); } finally { await driver.close(); }

Ein Beispiel für eine .NET-openCypher-Abfrage über Bolt mit IAM-Authentifizierung

Um die IAM-Authentifizierung in .NET zu aktivieren, müssen Sie eine Anforderung bei Herstellung der Verbindung signieren. Das folgende Beispiel zeigt, wie Sie einen NeptuneAuthToken-Helper erstellen, um ein Authentifizierungstoken zu generieren:

using Amazon.Runtime; using Amazon.Util; using Neo4j.Driver; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Web; namespace Hello { /* * Use this class instead of `AuthTokens.None` when working with an IAM-auth-enabled server. * * Note that authentication happens only the first time for a pooled connection. * * Typical usage: * * var authToken = new NeptuneAuthToken(AccessKey, SecretKey, Region).GetAuthToken(Host); * _driver = GraphDatabase.Driver(Url, authToken, o => o.WithEncryptionLevel(EncryptionLevel.Encrypted)); */ public class NeptuneAuthToken { private const string ServiceName = "neptune-db"; private const string Scheme = "basic"; private const string Realm = "realm"; private const string DummyUserName = "username"; private const string Algorithm = "AWS4-HMAC-SHA256"; private const string AWSRequest = "aws4_request"; private readonly string _accessKey; private readonly string _secretKey; private readonly string _region; private readonly string _emptyPayloadHash; private readonly SHA256 _sha256; public NeptuneAuthToken(string awsKey = null, string secretKey = null, string region = null) { var awsCredentials = awsKey == null || secretKey == null ? FallbackCredentialsFactory.GetCredentials().GetCredentials() : null; _accessKey = awsKey ?? awsCredentials.AccessKey; _secretKey = secretKey ?? awsCredentials.SecretKey; _region = region ?? FallbackRegionFactory.GetRegionEndpoint().SystemName; //ex: us-east-1 _sha256 = SHA256.Create(); _emptyPayloadHash = Hash(Array.Empty<byte>()); } public IAuthToken GetAuthToken(string url) { return AuthTokens.Custom(DummyUserName, GetCredentials(url), Realm, Scheme); } /******************** AWS SIGNING FUNCTIONS *********************/ private string Hash(byte[] bytesToHash) { return ToHexString(_sha256.ComputeHash(bytesToHash)); } private static byte[] HmacSHA256(byte[] key, string data) { return new HMACSHA256(key).ComputeHash(Encoding.UTF8.GetBytes(data)); } private byte[] GetSignatureKey(string dateStamp) { var kSecret = Encoding.UTF8.GetBytes($"AWS4{_secretKey}"); var kDate = HmacSHA256(kSecret, dateStamp); var kRegion = HmacSHA256(kDate, _region); var kService = HmacSHA256(kRegion, ServiceName); return HmacSHA256(kService, AWSRequest); } private static string ToHexString(byte[] array) { return Convert.ToHexString(array).ToLowerInvariant(); } private string GetCredentials(string url) { var request = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri($"https://{url}/opencypher") }; var signedrequest = Sign(request); var headers = new Dictionary<string, object> { [HeaderKeys.AuthorizationHeader] = signedrequest.Headers.GetValues(HeaderKeys.AuthorizationHeader).FirstOrDefault(), ["HttpMethod"] = HttpMethod.Get.ToString(), [HeaderKeys.XAmzDateHeader] = signedrequest.Headers.GetValues(HeaderKeys.XAmzDateHeader).FirstOrDefault(), // Host should be capitalized, not like in Amazon.Util.HeaderKeys.HostHeader ["Host"] = signedrequest.Headers.GetValues(HeaderKeys.HostHeader).FirstOrDefault(), }; return JsonSerializer.Serialize(headers); } private HttpRequestMessage Sign(HttpRequestMessage request) { var now = DateTimeOffset.UtcNow; var amzdate = now.ToString("yyyyMMddTHHmmssZ"); var datestamp = now.ToString("yyyyMMdd"); if (request.Headers.Host == null) { request.Headers.Host = $"{request.RequestUri.Host}:{request.RequestUri.Port}"; } request.Headers.Add(HeaderKeys.XAmzDateHeader, amzdate); var canonicalQueryParams = GetCanonicalQueryParams(request); var canonicalRequest = new StringBuilder(); canonicalRequest.Append(request.Method + "\n"); canonicalRequest.Append(request.RequestUri.AbsolutePath + "\n"); canonicalRequest.Append(canonicalQueryParams + "\n"); var signedHeadersList = new List<string>(); foreach (var header in request.Headers.OrderBy(a => a.Key.ToLowerInvariant())) { canonicalRequest.Append(header.Key.ToLowerInvariant()); canonicalRequest.Append(':'); canonicalRequest.Append(string.Join(",", header.Value.Select(s => s.Trim()))); canonicalRequest.Append('\n'); signedHeadersList.Add(header.Key.ToLowerInvariant()); } canonicalRequest.Append('\n'); var signedHeaders = string.Join(";", signedHeadersList); canonicalRequest.Append(signedHeaders + "\n"); canonicalRequest.Append(_emptyPayloadHash); var credentialScope = $"{datestamp}/{_region}/{ServiceName}/{AWSRequest}"; var stringToSign = $"{Algorithm}\n{amzdate}\n{credentialScope}\n" + Hash(Encoding.UTF8.GetBytes(canonicalRequest.ToString())); var signing_key = GetSignatureKey(datestamp); var signature = ToHexString(HmacSHA256(signing_key, stringToSign)); request.Headers.TryAddWithoutValidation(HeaderKeys.AuthorizationHeader, $"{Algorithm} Credential={_accessKey}/{credentialScope}, SignedHeaders={signedHeaders}, Signature={signature}"); return request; } private static string GetCanonicalQueryParams(HttpRequestMessage request) { var querystring = HttpUtility.ParseQueryString(request.RequestUri.Query); // Query params must be escaped in upper case (i.e. "%2C", not "%2c"). var queryParams = querystring.AllKeys.OrderBy(a => a) .Select(key => $"{key}={Uri.EscapeDataString(querystring[key])}"); return string.Join("&", queryParams); } } }

Hier wird beschrieben, wie Sie eine openCypher-Abfrage in .NET über Bolt mit IAM-Authentifizierung erstellen. Das folgende Beispiel verwendet den NeptuneAuthToken-Helper:

using Neo4j.Driver; namespace Hello { public class HelloWorldExample { private const string Host = "(your hostname):8182"; private const string Url = $"bolt://{Host}"; private const string CreateNodeQuery = "CREATE (a:Greeting) SET a.message = 'HelloWorldExample'"; private const string ReadNodeQuery = "MATCH(n:Greeting) RETURN n.message"; private const string AccessKey = "(your access key)"; private const string SecretKey = "(your secret key)"; private const string Region = "(your AWS region)"; // e.g. "us-west-2" private readonly IDriver _driver; public HelloWorldExample() { var authToken = new NeptuneAuthToken(AccessKey, SecretKey, Region).GetAuthToken(Host); // Note that when the connection is reinitialized after max connection lifetime // has been reached, the signature token could have already been expired (usually 5 min) // You can face exceptions like: // `Unexpected server exception 'Signature expired: XXXX is now earlier than YYYY (ZZZZ - 5 min.)` _driver = GraphDatabase.Driver(Url, authToken, o => o.WithMaxConnectionLifetime(TimeSpan.FromMinutes(60)).WithEncryptionLevel(EncryptionLevel.Encrypted)); } public async Task CreateNode() { // Open a session using (var session = _driver.AsyncSession()) { // Run the query in a write transaction var greeting = await session.WriteTransactionAsync(async tx => { var result = await tx.RunAsync(CreateNodeQuery); // Consume the result return await result.ConsumeAsync(); }); // The output will look like this: // ResultSummary{Query=`CREATE (a:Greeting) SET a.message = 'HelloWorldExample"..... Console.WriteLine(greeting.Query); } } public async Task RetrieveNode() { // Open a session using (var session = _driver.AsyncSession()) { // Run the query in a read transaction var greeting = await session.ReadTransactionAsync(async tx => { var result = await tx.RunAsync(ReadNodeQuery); var records = await result.ToListAsync(); // Consume the result. Read the single node // created in a previous step. return records[0].Values.First().Value; }); // The output will look like this: // HelloWorldExample Console.WriteLine(greeting); } } } }

Sie können dieses Beispiel starten, indem der folgende Code auf .NET 6 oder .NET 7 mit den folgenden Paketen ausgeführt wird:

  • Neo4j.Driver=4.3.0

  • AWSSDK.Core=3.7.102.1

namespace Hello { class Program { static async Task Main() { var apiCaller = new HelloWorldExample(); await apiCaller.CreateNode(); await apiCaller.RetrieveNode(); } } }

Ein Beispiel für eine Golang-openCypher-Abfrage über Bolt mit IAM-Authentifizierung

Das folgende Beispiel zeigt, wie OpenCypher-Abfragen in Go mithilfe des Bolt-Protokolls mit IAM-Authentifizierung durchgeführt werden. Es verwendet das AWS SDK for Go v2 für die Sigv4-Signierung und den Neo4j Go-Treiber v5 mit einer AuthTokenManager Struktur, die die Token-Manager-Schnittstelle (github.com/neo4j/neo4j-go-driver/v5/neo4j/auth.TokenManager) des Neo4j Go-Treibers implementiert, um Anmeldeinformationen automatisch zu aktualisieren, bevor sie ablaufen.

Erstellen Sie zunächst eine, die SIGV4-signierte Token generiert. AuthTokenManager Speichern Sie das als: auth_token_manager.go

// AuthTokenManager for Amazon Neptune IAM authentication via the Bolt protocol. // Provides SigV4-signed credentials to the Neo4j driver's auth interface. package main import ( "context" "encoding/json" "fmt" "net/http" "reflect" "sync" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials/stscreds" "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/neo4j/neo4j-go-driver/v5/neo4j" "github.com/neo4j/neo4j-go-driver/v5/neo4j/db" ) const ( serviceName = "neptune-db" emptyPayloadHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ) // AuthTokenManager manages SigV4-signed authentication tokens for Neptune with automatic refresh. type AuthTokenManager struct { region string endpoint string refreshInterval time.Duration credentials aws.CredentialsProvider mutex sync.Mutex cachedToken neo4j.AuthToken tokenTime time.Time } // NewAuthTokenManager creates a new AuthTokenManager. // // Parameters: // - region: AWS region (e.g., "us-east-1") // - endpoint: Neptune endpoint with port (e.g., "cluster.region.neptune.amazonaws.com:8182") // - profile: AWS profile name (optional, pass "" to use default) // - roleArn: AWS role ARN to assume (optional, pass "" to skip) // - refreshInterval: Token refresh interval func NewAuthTokenManager(region, endpoint, profile, roleArn string, refreshInterval time.Duration) (*AuthTokenManager, error) { credentials, err := createCredentialsProvider(region, profile, roleArn) if err != nil { return nil, err } return &AuthTokenManager{ region: region, endpoint: endpoint, refreshInterval: refreshInterval, credentials: credentials, }, nil } // createCredentialsProvider builds an AWS credentials provider, optionally using // a named profile and/or assuming a role. func createCredentialsProvider(region, profile, roleArn string) (aws.CredentialsProvider, error) { ctx := context.Background() var opts []func(*config.LoadOptions) error opts = append(opts, config.WithRegion(region)) if profile != "" { opts = append(opts, config.WithSharedConfigProfile(profile)) } cfg, err := config.LoadDefaultConfig(ctx, opts...) if err != nil { return nil, fmt.Errorf("failed to load AWS config: %w", err) } var credentials aws.CredentialsProvider = cfg.Credentials if roleArn != "" { stsClient := sts.NewFromConfig(cfg) credentials = stscreds.NewAssumeRoleProvider(stsClient, roleArn, func(o *stscreds.AssumeRoleOptions) { o.RoleSessionName = "NeptuneAuthSession" o.Duration = 900 * time.Second }) } return credentials, nil } // GetAuthToken returns a valid authentication token, using cached token if still valid. func (m *AuthTokenManager) GetAuthToken(ctx context.Context) (neo4j.AuthToken, error) { m.mutex.Lock() defer m.mutex.Unlock() if time.Since(m.tokenTime) < m.refreshInterval && m.cachedToken.Tokens != nil { return m.cachedToken, nil } token, err := m.generateToken(ctx) if err != nil { return neo4j.AuthToken{}, err } m.cachedToken = token m.tokenTime = time.Now() return token, nil } // HandleSecurityException handles security exceptions by invalidating the cached // token (if it matches the token that caused the error) and returning true so // the driver retries with a fresh token. The comparison prevents a concurrent // retry from unnecessarily invalidating a freshly generated token. func (m *AuthTokenManager) HandleSecurityException(ctx context.Context, token neo4j.AuthToken, err *db.Neo4jError) (bool, error) { m.mutex.Lock() defer m.mutex.Unlock() if reflect.DeepEqual(m.cachedToken.Tokens, token.Tokens) { m.tokenTime = time.Time{} m.cachedToken = neo4j.AuthToken{} } return true, nil } // generateToken generates a new SigV4-signed authentication token for Neptune. func (m *AuthTokenManager) generateToken(ctx context.Context) (neo4j.AuthToken, error) { req, err := http.NewRequest(http.MethodGet, "https://"+m.endpoint+"/opencypher", nil) if err != nil { return neo4j.AuthToken{}, err } req.Host = m.endpoint creds, err := m.credentials.Retrieve(ctx) if err != nil { return neo4j.AuthToken{}, err } signer := v4.NewSigner() err = signer.SignHTTP(ctx, creds, req, emptyPayloadHash, serviceName, m.region, time.Now()) if err != nil { return neo4j.AuthToken{}, err } authData := map[string]string{ "Authorization": req.Header.Get("Authorization"), "X-Amz-Date": req.Header.Get("X-Amz-Date"), "Host": m.endpoint, "HttpMethod": req.Method, } if st := req.Header.Get("X-Amz-Security-Token"); st != "" { authData["X-Amz-Security-Token"] = st } authJSON, err := json.Marshal(authData) if err != nil { return neo4j.AuthToken{}, err } return neo4j.BasicAuth("username", string(authJSON), ""), nil }

Verwenden Sie dann den Token-Manager, um einen Treiber zu erstellen und einen Knoten anhand der ID zu finden. Beachten Sie die Verwendung einer parametrisierten Abfrage ($nodeId) anstelle einer Zeichenketteninterpolation:

package main import ( "context" "fmt" "log" "time" "github.com/neo4j/neo4j-go-driver/v5/neo4j" ) func findNode(ctx context.Context, driver neo4j.DriverWithContext, nodeId string) (string, error) { session := driver.NewSession(ctx, neo4j.SessionConfig{ AccessMode: neo4j.AccessModeRead, }) defer session.Close(ctx) // Use parameterized queries to prevent injection and enable query plan caching. result, err := session.Run(ctx, "MATCH (n) WHERE ID(n) = $nodeId RETURN n", map[string]any{"nodeId": nodeId}, ) if err != nil { return "", fmt.Errorf("error running query: %v", err) } if !result.Next(ctx) { if err = result.Err(); err != nil { return "", fmt.Errorf("error fetching result: %v", err) } return "", fmt.Errorf("node not found") } n, found := result.Record().Get("n") if !found { return "", fmt.Errorf("node not found") } return fmt.Sprintf("%+v", n), nil } func main() { region := "(your AWS region)" // e.g. "us-east-1" endpoint := "(your Neptune endpoint):8182" // e.g. "cluster.xxx.us-east-1.neptune.amazonaws.com:8182" ctx := context.Background() // Pass a profile name for local development, or a role ARN for cross-account access authManager, err := NewAuthTokenManager(region, endpoint, "", "", 4*time.Minute) // Refresh before the 5-minute SigV4 signature expiry if err != nil { log.Fatalf("auth manager error: %v", err) } // bolt+s:// enables TLS with full certificate verification. // If you're developing on macOS, use bolt+ssc:// instead. Go on macOS uses the // system TLS verifier, which requires Certificate Transparency compliance that // Neptune endpoints don't support. In production (typically Linux), bolt+s:// // works with system CA certificates. driver, err := neo4j.NewDriverWithContext("bolt+s://"+endpoint, authManager) if err != nil { log.Fatalf("driver error: %v", err) } defer driver.Close(ctx) if err = driver.VerifyConnectivity(ctx); err != nil { log.Fatalf("connectivity error: %v", err) } // Neptune assigns UUID-format node IDs if a user does not supply their own node IDs res, err := findNode(ctx, driver, "72c2e8c1-7d5f-5f30-10ca-9d2bb8c4afbc") if err != nil { log.Fatal(err) } fmt.Println(res) }
Anmerkung

Das in diesem Beispiel verwendete auth.TokenManager Interface (github.com/neo4j/neo4j-go-driver/v5/neo4j/auth) wurde im Neo4j Go-Treiber v5.14.0 allgemein verfügbar. Diese Schnittstelle ermöglicht die automatische Aktualisierung der Anmeldeinformationen, was für die Neptune IAM-Authentifizierung erforderlich ist, da Sigv4-Signaturen nur für einen kurzen Zeitraum gültig sind und neu generiert werden müssen, wenn der Treiber neue Verbindungen herstellt.

Dieses Beispiel wurde mit den folgenden Go-Modulen validiert:

require ( github.com/aws/aws-sdk-go-v2 v1.30.3 github.com/aws/aws-sdk-go-v2/config v1.27.27 github.com/aws/aws-sdk-go-v2/credentials v1.17.27 github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 github.com/neo4j/neo4j-go-driver/v5 v5.22.0 )

Bolt-Verbindungsverhalten in Neptune

Dies sind einige Punkte, die Sie bei Neptune-Bolt-Verbindungen beachten sollten:

  • Da Bolt-Verbindungen auf der TCP-Ebene erstellt werden, können Sie diesen (anders als bei HTTP-Endpunkten) keinen Application Load Balancer voranstellen.

  • Der Port, den Neptune für Bolt-Verbindungen verwendet, ist der Port Ihres DB-Clusters.

  • Basierend auf der übergebenen Bolt-Präambel wählt der Neptune-Server die am besten geeignete Bolt-Version aus (1, 2, 3 oder 4.0).

  • Die maximale Anzahl der Verbindungen zum Neptune-Server, die gleichzeitig auf einem Client geöffnet sein können, beträgt 1 000.

  • Wenn der Client eine Verbindung nach Abschluss einer Abfrage nicht schließt, kann sie für die nächste Abfrage verwendet werden.

  • Wenn eine Verbindung jedoch 20 Minuten inaktiv ist, schließt der Server sie automatisch.

  • Wenn die IAM-Authentifizierung nicht aktiviert ist, können Sie AuthTokens.none() statt eines Platzhalter-Benutzernamens und Passwort verwenden. Beispiel für Java:

    GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)", AuthTokens.none(), Config.builder().withEncryption().withTrustStrategy(TrustStrategy.trustSystemCertificates()).build());
  • Wenn die IAM-Authentifizierung aktiviert ist, wird eine Bolt-Verbindung nach Ablauf von 10 Tagen nach der Herstellung stets für einige Minuten unterbrochen, wenn sie nicht bereits geschlossen wurde.

  • Wenn ein Client eine Abfrage zur Ausführung über eine Verbindung sendet, ohne die Ergebnisse einer vorherigen Abfrage verwendet zu haben, wird die neue Abfrage verworfen. Um stattdessen die vorherigen Ergebnisse zu verwerfen, muss der Client eine Rücksetzungsmeldung über die Verbindung senden.

  • Es kann jeweils nur eine Transaktion gleichzeitig für eine bestimmte Verbindung erstellt werden.

  • Wenn während einer Transaktion eine Ausnahme auftritt, setzt der Neptune-Server die Transaktion zurück und schließt die Verbindung. In diesem Fall erstellt der Treiber eine neue Verbindung für die nächste Abfrage.

  • Beachten Sie, dass Sitzungen nicht Thread-sicher sind. Mehrere parallele Operationen müssen mehrere getrennte Sitzungen verwenden.