.NET 用 Azure Confidential Ledger クライアント ライブラリ - バージョン 1.2.0

Azure Confidential Ledger は、変更できない改ざん防止台帳にログを記録するためのサービスを提供します。 Azure Confidential Computing ポートフォリオの一部として、Azure Confidential Ledger は SGX エンクレーブで実行されます。 これは、Microsoft Research の Confidential Consortium Framework に基づいて構築されています。

ソースコード | パッケージ (NuGet)

作業の開始

このセクションには、最初のクライアント接続を 非常に迅速にインストールして作成するために開発者が行う必要があるすべてを含める必要があります。

パッケージをインストールする

NuGet を使用して .NET 用 Azure Confidential Ledger クライアント ライブラリをインストールします。

dotnet add package Azure.Security.ConfidentialLedger

前提条件

  • Azure サブスクリプション
  • Azure Confidential Ledger の実行中のインスタンス。
  • 特権を持つ Administrator Azure Confidential Ledger に登録されているユーザー。

クライアントを認証する

Azure Active Directory の使用

このドキュメントでは、 DefaultAzureCredential を使用して Azure Active Directory を介して機密台帳に対する認証を行う方法について説明します。 ただし、 Azure.Identity によって提供される資格情報はすべて受け入れられます。 その他の資格情報の詳細については、 Azure.Identity のドキュメントを参照してください。

クライアント証明書の使用

Azure Active Directory の代わりに、クライアントはクライアント証明書を使用して相互 TLS 経由で認証することを選択できます。

クライアントの作成

DefaultAzureCredential は、ほとんどの Azure SDK クライアント シナリオを自動的に処理します。 まず、機密台帳に登録されている AAD ID の環境変数を設定します。

export AZURE_CLIENT_ID="generated app id"
export AZURE_CLIENT_SECRET="random password"
export AZURE_TENANT_ID="tenant id"

その後、 DefaultAzureCredential を認証 ConfidentialLedgerClientできるようになります。

クライアントを構築するには、機密台帳の URI も必要です。この URI は、セクションの下PropertiesにあるフィールドのLedger URI機密台帳の Azure Portal ページから取得できます。 を取得したら、 Ledger URIそれを使用して次の例で を置き換えてください "https://my-ledger-url.confidential-ledger.azure.com"

var ledgerClient = new ConfidentialLedgerClient(new Uri("https://my-ledger-url.confidential-ledger.azure.com"), new DefaultAzureCredential());

セキュリティに関する注意: 既定では、Confidential ledger クライアントが作成されると、台帳ノードへの接続をセキュリティで保護するために、Azure の confidential ledger Identity Service に接続して、Ledger の最新の TLS サービス証明書を取得します。 このプロセスの詳細については、 このサンプルを参照してください。 この動作は、台帳クライアントの作成時に 引数を options 設定することでオーバーライドできます。

主要な概念

台帳エントリ

Azure Confidential Ledger に対するすべての書き込みでは、サービスに不変の台帳エントリが生成されます。 書き込みは、書き込みごとにインクリメントされるトランザクション ID によって一意に識別されます。

Operation postOperation = ledgerClient.PostLedgerEntry(
    waitUntil: WaitUntil.Completed,
    RequestContent.Create(
        new { contents = "Hello world!" }));

string transactionId = postOperation.Id;
Console.WriteLine($"Appended transaction with Id: {transactionId}");

Azure Confidential Ledger は分散システムであるため、まれに一時的な障害が発生すると、書き込みが失われる可能性があります。 保持する必要があるエントリの場合は、書き込みが永続的になったことを確認することをお勧めします。 注: "コミット済み" 状態を返すまで、複数回呼び出す GetTransactionStatus 必要がある場合があります。 ただし、 を呼び出 PostLedgerEntryすと、成功した結果は状態が "Committed" であることを示します。

Response statusResponse = ledgerClient.GetTransactionStatus(transactionId);

string status = JsonDocument.Parse(statusResponse.Content)
    .RootElement
    .GetProperty("state")
    .GetString();

Console.WriteLine($"Transaction status: {status}");

// Wait for the entry to be committed
while (status == "Pending")
{
    statusResponse = ledgerClient.GetTransactionStatus(transactionId);
    status = JsonDocument.Parse(statusResponse.Content)
        .RootElement
        .GetProperty("state")
        .GetString();
}

Console.WriteLine($"Transaction status: {status}");

Receipts

機密台帳に対する状態の変更は、Merkle ツリーと呼ばれるデータ構造に保存されます。 書き込みが正しく保存されたことを暗号で確認するには、任意のトランザクション ID に対して Merkle 証明 (領収書) を取得できます。

Response receiptResponse = ledgerClient.GetReceipt(transactionId);
string receiptJson = new StreamReader(receiptResponse.ContentStream).ReadToEnd();

Console.WriteLine(receiptJson);

コレクション

ほとんどのユース ケースには 1 つの台帳が含まれますが、異なる論理データ グループを同じ機密台帳に格納する必要がある場合に備えて、コレクション機能を提供します。

ledgerClient.PostLedgerEntry(
    waitUntil: WaitUntil.Completed,
    RequestContent.Create(
        new { contents = "Hello from Chris!", collectionId = "Chris' messages" }));

ledgerClient.PostLedgerEntry(
    waitUntil: WaitUntil.Completed,
    RequestContent.Create(
        new { contents = "Hello from Allison!", collectionId = "Allison's messages" }));

メソッド呼び出しでコレクション ID が指定されていない場合、Azure Confidential Ledger サービスでは、サービスによって決定される定数のコレクション ID が想定されます。

postOperation = ledgerClient.PostLedgerEntry(
    waitUntil: WaitUntil.Completed,
    RequestContent.Create(
        new { contents = "Hello world!" }));

string content = postOperation.GetRawResponse().Content.ToString();
transactionId = postOperation.Id;
string collectionId = "subledger:0";

// Try fetching the ledger entry until it is "loaded".
Response getByCollectionResponse = default;
JsonElement rootElement = default;
bool loaded = false;

while (!loaded)
{
    // Provide both the transactionId and collectionId.
    getByCollectionResponse = ledgerClient.GetLedgerEntry(transactionId, collectionId);
    rootElement = JsonDocument.Parse(getByCollectionResponse.Content).RootElement;
    loaded = rootElement.GetProperty("state").GetString() != "Loading";
}

string contents = rootElement
    .GetProperty("entry")
    .GetProperty("contents")
    .GetString();

Console.WriteLine(contents); // "Hello world!"

// Now just provide the transactionId.
getByCollectionResponse = ledgerClient.GetLedgerEntry(transactionId);

string collectionId2 = JsonDocument.Parse(getByCollectionResponse.Content)
    .RootElement
    .GetProperty("entry")
    .GetProperty("collectionId")
    .GetString();

Console.WriteLine($"{collectionId} == {collectionId2}");

台帳エントリはコレクションから取得されます。 トランザクション ID が指定されている場合、返される値は、トランザクション ID で識別された時点で指定されたコレクションに含まれる値です。トランザクション ID が指定されていない場合は、使用可能な最新の値が返されます。

Operation firstPostOperation = ledgerClient.PostLedgerEntry(
    waitUntil: WaitUntil.Completed,
    RequestContent.Create(new { contents = "Hello world 0" }));
ledgerClient.PostLedgerEntry(
    waitUntil: WaitUntil.Completed,
    RequestContent.Create(new { contents = "Hello world 1" }));
Operation collectionPostOperation = ledgerClient.PostLedgerEntry(
    waitUntil: WaitUntil.Completed,
    RequestContent.Create(new { contents = "Hello world collection 0" }),
    "my collection");
ledgerClient.PostLedgerEntry(
    waitUntil: WaitUntil.Completed,
    RequestContent.Create(new { contents = "Hello world collection 1" }),
    "my collection");

transactionId = firstPostOperation.Id;

// Wait for the entry to be committed
status = "Pending";
while (status == "Pending")
{
    statusResponse = ledgerClient.GetTransactionStatus(transactionId);
    status = JsonDocument.Parse(statusResponse.Content)
        .RootElement
        .GetProperty("state")
        .GetString();
}

// The ledger entry written at the transactionId in firstResponse is retrieved from the default collection.
Response getResponse = ledgerClient.GetLedgerEntry(transactionId);

// Try until the entry is available.
loaded = false;
JsonElement element = default;
contents = null;
while (!loaded)
{
    loaded = JsonDocument.Parse(getResponse.Content)
        .RootElement
        .TryGetProperty("entry", out element);
    if (loaded)
    {
        contents = element.GetProperty("contents").GetString();
    }
    else
    {
        getResponse = ledgerClient.GetLedgerEntry(transactionId, collectionId);
    }
}

string firstEntryContents = JsonDocument.Parse(getResponse.Content)
    .RootElement
    .GetProperty("entry")
    .GetProperty("contents")
    .GetString();

Console.WriteLine(firstEntryContents); // "Hello world 0"

// This will return the latest entry available in the default collection.
getResponse = ledgerClient.GetCurrentLedgerEntry();

// Try until the entry is available.
loaded = false;
element = default;
string latestDefaultCollection = null;
while (!loaded)
{
    loaded = JsonDocument.Parse(getResponse.Content)
        .RootElement
        .TryGetProperty("contents", out element);
    if (loaded)
    {
        latestDefaultCollection = element.GetString();
    }
    else
    {
        getResponse = ledgerClient.GetCurrentLedgerEntry();
    }
}

Console.WriteLine($"The latest ledger entry from the default collection is {latestDefaultCollection}"); //"Hello world 1"

// The ledger entry written at collectionTransactionId is retrieved from the collection 'collection'.
string collectionTransactionId = collectionPostOperation.Id;

getResponse = ledgerClient.GetLedgerEntry(collectionTransactionId, "my collection");
// Try until the entry is available.
loaded = false;
element = default;
string collectionEntry = null;
while (!loaded)
{
    loaded = JsonDocument.Parse(getResponse.Content)
        .RootElement
        .TryGetProperty("entry", out element);
    if (loaded)
    {
        collectionEntry = element.GetProperty("contents").GetString();
    }
    else
    {
        getResponse = ledgerClient.GetLedgerEntry(collectionTransactionId, "my collection");
    }
}

Console.WriteLine(collectionEntry); // "Hello world collection 0"

// This will return the latest entry available in the collection.
getResponse = ledgerClient.GetCurrentLedgerEntry("my collection");
string latestCollection = JsonDocument.Parse(getResponse.Content)
    .RootElement
    .GetProperty("contents")
    .GetString();

Console.WriteLine($"The latest ledger entry from the collection is {latestCollection}"); // "Hello world collection 1"
範囲指定クエリ

コレクション内の台帳エントリは、トランザクション ID の範囲で取得できます。 注: 両方の範囲は省略可能です。これらは個別に提供することも、まったく提供することもできません。

ledgerClient.GetLedgerEntries(fromTransactionId: "2.1", toTransactionId: collectionTransactionId);

[ユーザー管理]

ユーザーは、Azure ではなく機密台帳で直接管理されます。 新しいユーザーは、AAD ベースまたは証明書ベースの場合があります。

string newUserAadObjectId = "<some AAD user or service princpal object Id>";
ledgerClient.CreateOrUpdateUser(
    newUserAadObjectId,
    RequestContent.Create(new { assignedRole = "Reader" }));

機密コンソーシアムとエンクレーブの検証

さまざまな理由で機密台帳に関する詳細を検証したい場合があります。 たとえば、Microsoft が Confidential Consortium Framework ガバナンスの一部として機密台帳を管理する方法の詳細を表示したり、機密台帳が実際に SGX エンクレーブで実行されていることを確認したりできます。 これらのユース ケースには、さまざまなクライアント メソッドが用意されています。

Pageable<BinaryData> consortiumResponse = ledgerClient.GetConsortiumMembers();
foreach (var page in consortiumResponse)
{
    string membersJson = page.ToString();
    // Consortium members can manage and alter the confidential ledger, such as by replacing unhealthy nodes.
    Console.WriteLine(membersJson);
}

// The constitution is a collection of JavaScript code that defines actions available to members,
// and vets proposals by members to execute those actions.
Response constitutionResponse = ledgerClient.GetConstitution();
string constitutionJson = new StreamReader(constitutionResponse.ContentStream).ReadToEnd();

Console.WriteLine(constitutionJson);

// Enclave quotes contain material that can be used to cryptographically verify the validity and contents of an enclave.
Response enclavesResponse = ledgerClient.GetEnclaveQuotes();
string enclavesJson = new StreamReader(enclavesResponse.ContentStream).ReadToEnd();

Console.WriteLine(enclavesJson);

Microsoft Azure Attestation Service は、SGX エンクレーブ引用符のプロバイダーの 1 つです。

スレッド セーフ

すべてのクライアント インスタンス メソッドがスレッド セーフであり、相互に独立していることを保証します (ガイドライン)。 これにより、クライアント インスタンスの再利用に関する推奨事項は、スレッド間でも常に安全になります。

その他の概念

クライアント オプション | 応答 | へのアクセス実行時間の長い操作 | エラーの | 処理診断 | あざける | クライアントの有効期間

もうすぐです。。。

トラブルシューティング

Azure Confidential Ledger クライアント メソッドから返される応答値は オブジェクトであり Response 、http プロパティなどの http Status 応答に関する情報と Headers 、エラーに関する詳細情報を含むオブジェクトが含まれます。

コンソール ログの設定

ログを表示する最も簡単な方法は、コンソール ログを有効にすることです。 コンソールにメッセージを出力する Azure SDK ログ リスナーを作成するには、AzureEventSourceListener.CreateConsoleLogger メソッドを使用します。

// Setup a listener to monitor logged events.
using AzureEventSourceListener listener = AzureEventSourceListener.CreateConsoleLogger();

その他のログメカニズムの詳細については、 こちらを参照してください

次のステップ

Azure Confidential Ledger に関するより広範なドキュメントについては、API リファレンス ドキュメントを参照してください。 また、Microsoft Research のオープンソースの Confidential Consortium Framework の詳細を参照することもできます。

共同作成

このプロジェクトでは、共同作成と提案を歓迎しています。 ほとんどの共同作成では、共同作成者使用許諾契約書 (CLA) にご同意いただき、ご自身の共同作成内容を使用する権利を Microsoft に供与する権利をお持ちであり、かつ実際に供与することを宣言していただく必要があります。 詳細については、「 cla.microsoft.com」を参照してください。

このプロジェクトでは、Microsoft オープン ソースの倫理規定を採用しています。 詳しくは、「Code of Conduct FAQ (倫理規定についてよくある質問)」を参照するか、opencode@microsoft.com 宛てに質問またはコメントをお送りください。

インプレッション数