

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# Bolt プロトコルを使用して openCypher クエリを Neptune に作成する
<a name="access-graph-opencypher-bolt"></a>

[Bolt](https://boltprotocol.org/) は、当初は Neo4j に開発された、Creative Commons 3.0 [Attribution-ShareAlike](https://creativecommons.org/licenses/by-sa/3.0/) ライセンスの下で使用権利を得たステートメント指向のクライアント/サーバープロトコルです。これは、クライアント駆動型です。つまり、クライアントは常にメッセージ交換を開始します。

Neo4j の Bolt ドライバを使用して Neptune に接続するには、URL とポート番号を `bolt` URI スキームを使ってクラスターエンドポイントに置き換えるだけです。1 つの Neptune インスタンスが実行されている場合は、read\$1write エンドポイントを使用します。複数のインスタンスが実行されている場合は、ライター用とすべてのリードレプリカ用に 2 つのドライバを推奨します。デフォルトの 2 つのエンドポイントしかない場合は、read\$1write と read\$1only ドライバで十分ですが、カスタムエンドポイントがある場合は、それぞれにドライバインスタンスを作成することを検討してください。

**注記**  
Bolt の仕様では、Bolt は TCP または WebSockets を使用して接続できると記載されていますが、Neptune は Bolt について TCP 接続のみをサポートしています。

Neptune は、t3.medium と t4g.medium を除くすべてのインスタンスサイズで最大 1,000 件の同時 Bolt 接続を許可します。t3.medium および t4g.medium インスタンスでは、512 接続のみが許可されます。

Bolt ドライバを使用するさまざまな言語の openCypher クエリの例については、Neo4j [ドライバ & 言語ガイド](https://neo4j.com/developer/language-guides/)ドキュメントを参照してください。

**重要**  
Python、.NET、JavaScript、および Golang 用の Neo4j Bolt ドライバーは、当初 Signature v4 AWS 認証トークンの自動更新をサポートしていませんでした。つまり、署名の有効期限が切れると (多くの場合 5 分後)、ドライバーは認証に失敗し、それ以降のリクエストも失敗しました。以下の Python、.NET、JavaScript、および Go のサンプルはすべて、この問題の影響を受けました。  
詳細については、「[Neo4j Python ドライバーの問題 \$1834](https://github.com/neo4j/neo4j-python-driver/issues/834)」、「[Neo4j .NET の問題 \$1664](https://github.com/neo4j/neo4j-dotnet-driver/issues/664)」、「[Neo4j JavaScript ドライバーの問題 \$1993](https://github.com/neo4j/neo4j-javascript-driver/issues/993)」、および「[Neo4j goLang ドライバーの問題 \$1429](https://github.com/neo4j/neo4j-go-driver/issues/429)」を参照してください。  
ドライバーバージョン 5.8.0 以降、Go ドライバー用の新しいプレビュー再認証 API がリリースされました (「[v5.8.0 - 再認証に関するフィードバック募集](https://github.com/neo4j/neo4j-go-driver/discussions/482)」を参照)。

## Bolt と Java を使用して Neptune に接続するには
<a name="access-graph-opencypher-bolt-java"></a>

Maven から使用する任意のバージョンのドライバをダウンロードできます。[MVN リポジトリ](https://mvnrepository.com/artifact/org.neo4j.driver/neo4j-java-driver)または、この依存関係をプロジェクトに追加できます。

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

次に、これらの Bolt ドライバの 1 つを使用して Java で Neptune に接続するには、次のようなコードを使用して、クラスター内のプライマリ/ライターインスタンスのドライバインスタンスを作成します。

```
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());
```

1 つ以上のリーダーレプリカがある場合は、同様に、次のようなコードを使用して、それらのドライバインスタンスを作成できます。

```
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());
```

または、タイムアウトを設定して:

```
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());
```

カスタムエンドポイントがある場合は、各エンドポイントにドライバインスタンスを作成することをお勧めします。

## Bolt を使用した Python openCypher クエリの例
<a name="access-graph-opencypher-bolt-python"></a>

以下は、Bolt を使用して Python で Python openCypher クエリを作成する方法です。

```
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)
```

`auth` パラメータは無視される点に注意してください。

## Bolt を使用した .NET openCypher クエリの例
<a name="access-graph-opencypher-bolt-dotnet"></a>

Bolt を使用して .NET で openCypher クエリを実行するには、まず NuHet を使用して Neo4j ドライバーをインストールします。同期呼び出しを行うには、次のように `.Simple` バージョンを使用します。

```
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();
      }
    }
  }
}
```

## Bolt と IAM 認証を使用した Java openCypher クエリの例
<a name="access-graph-opencypher-bolt-java-iam-auth"></a>

以下の Java コードは、IAM 認証で Bolt を使用して Java で openCypher クエリを作成する方法を示しています。JavaDoc コメントには、その使用方法が説明されています。ドライバーインスタンスが使用可能になると、そのインスタンスを使用して複数の認証リクエストを行うことができます。

```
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);
  }
}
```

## Bolt と IAM 認証を使用した Python openCypher クエリの例
<a name="access-graph-opencypher-bolt-python-iam-auth"></a>

以下の Python クラスを使用すると、IAM 認証付きの Bolt を使用して Python で openCypher クエリを作成できます。

```
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)
```

このクラスを使用してドライバーを次のように作成します。

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

## IAM 認証と Bolt を使用した Node.js の例
<a name="access-graph-opencypher-bolt-nodejs-iam-auth"></a>

以下の Node.js コードは、 AWS SDK for JavaScript バージョン 3 および ES6 構文を使用して、リクエストを認証するドライバーを作成します。

```
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();
}
```

## Bolt と IAM 認証を使用した .NET openCypher クエリの例
<a name="access-graph-opencypher-bolt-dotnet-iam-auth"></a>

.NET で IAM 認証を有効にするには、接続を確立するときにリクエストに署名する必要があります。以下の例は、認証トークンを生成する `NeptuneAuthToken` ヘルパーを作成する方法を示しています。

```
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);
    }
  }
}
```

以下は、.NET で Bolt と IAM 認証を使用して openCypher クエリを作成する方法です。次の例では、`NeptuneAuthToken` ヘルパーを使用しています。

```
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);
      }
    }
  }
}
```

この例は、以下のコードを以下のパッケージで `.NET 6` または `.NET 7` に対して実行することで起動できます。
+ **`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();
    }
  }
}
```

## Bolt と IAM 認証を使用した Golang openCypher クエリの例
<a name="access-graph-opencypher-bolt-golang-iam-auth"></a>

次の例は、IAM 認証で Bolt プロトコルを使用して Go で openCypher クエリを実行する方法を示しています。AWS SDK for Go v2 for SigV4 署名と Neo4j Go ドライバー v5 を使用し、Neo4j Go ドライバーのトークンマネージャーインターフェイス (`github.com/neo4j/neo4j-go-driver/v5/neo4j/auth.TokenManager`) を実装する`AuthTokenManager`構造体を使用して、有効期限が切れる前に認証情報を自動的に更新します。

まず、SigV4-signed`AuthTokenManager`付きトークンを生成する を作成します。`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
}
```

次に、トークンマネージャーを使用してドライバーを作成し、ID でノードを見つけます。文字列補間の代わりにパラメータ化されたクエリ (`$nodeId`) を使用することに注意してください。

```
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)
}
```

**注記**  
この例で使用される`auth.TokenManager`インターフェイス (`github.com/neo4j/neo4j-go-driver/v5/neo4j/auth`) はNeo4j Go ドライバー **v5.14.0** で一般公開されました。このインターフェイスは認証情報の自動更新を有効にします。これは、SigV4 署名が短期間のみ有効であり、ドライバーが新しい接続を確立するときに再生成する必要があるため、Neptune IAM 認証に必要です。

この例は、次の Go モジュールを使用して検証されました。

```
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
  )
```

## Neptune での Bolt 接続動作
<a name="access-graph-opencypher-bolt-connections"></a>

Neptune Bolt 接続に関する注意事項をいくつか次に示します。
+ Bolt 接続は TCP レイヤーで作成されるため、HTTP エンドポイントでできるように、それらの前では[Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html)は使えません。
+ Neptune が Bolt 接続に使用するポートは、DB クラスターのポートです。
+ 渡された Bolt プリアンブルに基づいて、Neptune サーバーは最適な Bolt バージョン (1、2、3、または 4.0) を選択します。
+ クライアントが任意の時点でオープンできる Neptune サーバーへの最大接続数は 1,000 です。
+ クエリの後にクライアントが接続を閉じない場合、その結合を使用して次のクエリを実行できます。
+ ただし、接続が 20 分間アイドル状態になると、サーバーは自動的に閉じます。
+ IAM 認証が有効になっていない場合は、ダミーのユーザー名とパスワードを入力する代わりに `AuthTokens.none()` を使用できます。例えば、Java では次のようになります。

  ```
  GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)", AuthTokens.none(),
      Config.builder().withEncryption().withTrustStrategy(TrustStrategy.trustSystemCertificates()).build());
  ```
+ IAM 認証が有効になっている場合、Bolt 接続は確立されてから 10 日経過すると、他の理由で Bolt 接続がまだ閉じられていない場合、常に数分で切断されます。
+ クライアントが前のクエリの結果を消費せずに、接続を介して実行用のクエリを送信すると、新しいクエリは破棄されます。代わりに以前の結果を破棄するには、クライアントは接続を介してリセットメッセージを送信する必要があります。
+ 特定の接続では、一度に作成できるトランザクションは 1 つだけです。
+ トランザクション中に例外が発生すると、Neptune サーバーはトランザクションをロールバックし、接続を閉じます。この場合、ドライバーは次のクエリ用に新しい接続を作成します。
+ セッションはスレッドセーフではないことに注意してください。複数の並列操作では、複数の別々のセッションを使用する必要があります。