Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.
Menggunakan protokol Bolt untuk membuat kueri OpenCypher ke Neptunus
Bolt
Untuk terhubung ke Neptunus menggunakan driver Bolt Neo4j, cukup ganti URL dan nomor Port dengan titik akhir cluster Anda menggunakan skema URI. bolt Jika Anda memiliki satu instance Neptunus yang berjalan, gunakan endpoint read_write. Jika beberapa instance berjalan, maka dua driver direkomendasikan, satu untuk penulis dan satu lagi untuk semua replika baca. Jika Anda hanya memiliki dua titik akhir default, driver read_write dan read_only sudah cukup, tetapi jika Anda memiliki titik akhir khusus juga, pertimbangkan untuk membuat instance driver untuk masing-masing titik akhir.
catatan
Meskipun spesifikasi Bolt menyatakan bahwa Bolt dapat terhubung menggunakan TCP atau, WebSockets Neptunus hanya mendukung koneksi TCP untuk Bolt.
Neptunus memungkinkan hingga 1000 koneksi Bolt bersamaan pada semua ukuran instans kecuali untuk t3.medium dan t4g.medium. Pada instans t3.medium dan t4g.medium hanya 512 koneksi yang diizinkan.
penting
Driver Neo4j Bolt untuk Python, .NET JavaScript, dan Golang awalnya tidak mendukung pembaruan otomatis token otentikasi Signature v4. AWS Ini berarti bahwa setelah tanda tangan kedaluwarsa (seringkali dalam 5 menit), pengemudi gagal mengautentikasi, dan permintaan berikutnya gagal. Contoh Python, .NET JavaScript, dan Go di bawah ini semuanya terpengaruh oleh masalah ini.
Lihat masalah driver Python Neo4j #834, masalah Neo4j .NET #664, masalah driver
Pada driver versi 5.8.0, API otentikasi ulang pratinjau baru dirilis untuk driver Go (lihat v5.8.0
Menggunakan Bolt dengan Java untuk terhubung ke Neptunus
Anda dapat mengunduh driver untuk versi apa pun yang ingin Anda gunakan dari repositori Maven MVN
<dependency> <groupId>org.neo4j.driver</groupId> <artifactId>neo4j-java-driver</artifactId> <version>4.3.3</version> </dependency>
Kemudian, untuk terhubung ke Neptunus di Jawa menggunakan salah satu driver Bolt ini, buat instance driver untuk instance primary/writer di cluster Anda menggunakan kode seperti berikut:
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());
Jika Anda memiliki satu atau lebih replika pembaca, Anda juga dapat membuat instance driver untuk mereka menggunakan kode seperti ini:
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());
Atau, dengan batas waktu:
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());
Jika Anda memiliki titik akhir khusus, mungkin juga bermanfaat untuk membuat instance driver untuk masing-masing titik.
Contoh kueri Python OpenCypher menggunakan Bolt
Berikut ini cara membuat kueri OpenCypher dengan Python menggunakan 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)
Perhatikan bahwa auth parameter diabaikan.
Contoh kueri.NET OpenCypher menggunakan Bolt
Untuk membuat kueri OpenCypher di.NET menggunakan Bolt, langkah pertama adalah menginstal driver Neo4j menggunakan. NuHet Untuk melakukan panggilan sinkron, gunakan .Simple versi, seperti ini:
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(); } } } }
Contoh kueri Java OpenCypher menggunakan Bolt dengan otentikasi IAM
Kode Java di bawah ini menunjukkan cara membuat query OpenCypher di Java menggunakan Bolt dengan otentikasi IAM. JavaDoc Komentar tersebut menjelaskan penggunaannya. Setelah instance driver tersedia, Anda dapat menggunakannya untuk membuat beberapa permintaan yang diautentikasi.
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); } }
Contoh kueri OpenCypher Python menggunakan Bolt dengan otentikasi IAM
Kelas Python di bawah ini memungkinkan Anda membuat kueri OpenCypher dengan Python menggunakan Bolt dengan otentikasi IAM:
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)
Anda menggunakan kelas ini untuk membuat driver sebagai berikut:
authToken = NeptuneAuthToken(creds, REGION, URL) driver = GraphDatabase.driver(URL, auth=authToken, encrypted=True)
Contoh Node.js menggunakan otentikasi IAM dan Bolt
Kode Node.js di bawah ini menggunakan AWS SDK untuk JavaScript versi 3 dan ES6 sintaks untuk membuat driver yang mengautentikasi permintaan:
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(); }
Contoh kueri.NET OpenCypher menggunakan Bolt dengan otentikasi IAM
Untuk mengaktifkan otentikasi IAM di .NET, Anda perlu menandatangani permintaan saat membuat koneksi. Contoh di bawah ini menunjukkan cara membuat NeptuneAuthToken helper untuk menghasilkan token otentikasi:
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); } } }
Berikut adalah cara membuat query OpenCypher di.NET menggunakan Bolt dengan otentikasi IAM. Contoh di bawah ini menggunakan 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); } } } }
Contoh ini dapat diluncurkan dengan menjalankan kode di bawah ini pada .NET 6 atau .NET 7 dengan paket-paket berikut:
Neo4j.Driver=4.3.0AWSSDK.Core=3.7.102.1
namespace Hello { class Program { static async Task Main() { var apiCaller = new HelloWorldExample(); await apiCaller.CreateNode(); await apiCaller.RetrieveNode(); } } }
Contoh kueri Golang OpenCypher menggunakan Bolt dengan otentikasi IAM
Contoh berikut menunjukkan bagaimana membuat query OpenCypher di Go menggunakan protokol Bolt dengan otentikasi IAM. Ini menggunakan AWS SDK for Go v2 untuk penandatanganan SigV4 dan driver Neo4j Go v5 dengan struct yang mengimplementasikan AuthTokenManager antarmuka manajer token driver Neo4j Go () untuk menyegarkan kredensyal secara otomatis sebelum kedaluwarsa. github.com/neo4j/neo4j-go-driver/v5/neo4j/auth.TokenManager
Pertama, buat yang menghasilkan token AuthTokenManager yang ditandatangani SIGV4. Simpan ini sebagaiauth_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 }
Kemudian gunakan token manager untuk membuat driver dan menemukan node dengan ID. Perhatikan penggunaan query parameterized ($nodeId) alih-alih interpolasi string:
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) }
catatan
auth.TokenManagerAntarmuka (github.com/neo4j/neo4j-go-driver/v5/neo4j/auth) yang digunakan dalam contoh ini menjadi tersedia secara umum di driver Neo4j Go v5.14.0. Antarmuka ini memungkinkan penyegaran kredenal otomatis, yang diperlukan untuk otentikasi IAM Neptunus karena tanda tangan SigV4 hanya berlaku untuk waktu yang singkat dan harus dibuat ulang ketika driver membuat koneksi baru.
Contoh ini divalidasi menggunakan modul Go berikut:
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 )
Perilaku koneksi baut di Neptunus
Berikut adalah beberapa hal yang perlu diingat tentang koneksi Neptunus Bolt:
Karena koneksi Bolt dibuat pada layer TCP, Anda tidak dapat menggunakan Application Load Balancer di depannya, seperti yang Anda bisa dengan endpoint HTTP.
Port yang digunakan Neptunus untuk koneksi Bolt adalah port cluster DB Anda.
Berdasarkan pembukaan Bolt yang diteruskan ke sana, server Neptunus memilih versi Bolt tertinggi yang sesuai (1, 2, 3, atau 4.0).
Jumlah maksimum koneksi ke server Neptunus yang dapat dibuka klien kapan saja adalah 1.000.
Jika klien tidak menutup koneksi setelah kueri, koneksi itu dapat digunakan untuk menjalankan kueri berikutnya.
Namun, jika koneksi menganggur selama 20 menit, server menutupnya secara otomatis.
-
Jika autentikasi IAM tidak diaktifkan, Anda dapat menggunakan
AuthTokens.none()daripada memasok nama pengguna dan kata sandi dummy. Misalnya, di Jawa:GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)", AuthTokens.none(), Config.builder().withEncryption().withTrustStrategy(TrustStrategy.trustSystemCertificates()).build()); Ketika autentikasi IAM diaktifkan, koneksi Bolt selalu terputus beberapa menit lebih dari 10 hari setelah dibuat jika belum ditutup karena alasan lain.
Jika klien mengirim kueri untuk dieksekusi melalui koneksi tanpa menghabiskan hasil kueri sebelumnya, kueri baru akan dibuang. Untuk membuang hasil sebelumnya, klien harus mengirim pesan reset melalui koneksi.
Hanya satu transaksi pada satu waktu yang dapat dibuat pada koneksi tertentu.
Jika pengecualian terjadi selama transaksi, server Neptunus memutar kembali transaksi dan menutup koneksi. Dalam hal ini, driver membuat koneksi baru untuk kueri berikutnya.
Ketahuilah bahwa sesi tidak aman untuk utas. Beberapa operasi parallel harus menggunakan beberapa sesi terpisah.