你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

适用于 .NET 的 Azure 机密账本客户端库 - 版本 1.2.0

Azure 机密账本提供用于记录到不可变、防篡改账本的服务。 作为 Azure 机密计算 组合的一部分,Azure 机密账本在 SGX enclave 中运行。 它基于 Microsoft Research 的 机密联盟框架

源代码 | 包 (NuGet)

入门

本部分应包括开发人员非常 快速地安装和创建其第一个客户端连接时需要执行的所有操作。

安装包

使用 NuGet 安装适用于 .NET 的 Azure 机密账本客户端库:

dotnet add package Azure.Security.ConfidentialLedger

先决条件

  • 一个 Azure 订阅
  • Azure 机密账本的运行实例。
  • Azure 机密账本中具有 Administrator 权限的已注册用户。

验证客户端

使用 Azure Active Directory

本文档演示如何使用 DefaultAzureCredential 通过 Azure Active Directory 向机密账本进行身份验证。 但是,将接受 Azure.Identity 提供的任何凭据。 有关其他凭据的详细信息,请参阅 Azure.Identity 文档。

使用客户端证书

作为 Azure Active Directory 的替代方法,客户端可以选择使用客户端证书通过相互 TLS 进行身份验证。

创建客户端

DefaultAzureCredential 将自动处理大多数 Azure SDK 客户端方案。 若要开始,请为向机密账本注册的 AAD 标识设置环境变量。

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

然后, DefaultAzureCredential 将能够对 进行身份验证 ConfidentialLedgerClient

构造客户端还需要机密账本的 URI,可以从 部分下字段中的机密账本 Ledger URI 的 Azure 门户页获取该 Properties URI。 检索 到 后, 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());

安全说明:默认情况下,创建机密账本客户端时,它将连接到 Azure 的机密账本标识服务,以获取账本的最新 TLS 服务证书,以便保护与账本节点的连接。 此示例中提供了此过程的详细信息。 可以通过在创建账本客户端时设置 options 参数来重写此行为。

关键概念

账本条目

每次写入 Azure 机密账本都会在服务中生成不可变的账本条目。 写入由事务 ID 唯一标识,事务 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 机密账本是分布式系统,因此罕见的暂时性故障可能会导致写入丢失。 对于必须保留的条目,建议验证写入是否持久。 注意:可能需要多次调用 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);

集合

虽然大多数用例将涉及一个账本,但我们提供了集合功能,以防不同的逻辑数据组需要存储在同一机密账本中。

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 机密账本服务将采用由服务确定的常量集合 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" }));

机密联盟和 enclave 验证

出于各种原因,可能需要验证有关机密账本的详细信息。 例如,你可能想要查看有关 Microsoft 如何管理机密账本的详细信息,作为 机密联盟框架治理的一部分,或验证机密账本是否确实在 SGX enclave 中运行。 为这些用例提供了许多客户端方法。

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 证明 Service 是 SGX enclave 报价的提供商之一。

线程安全

我们保证所有客户端实例方法都是线程安全的,并且彼此独立 (准则) 。 这可确保重用客户端实例的建议始终是安全的,即使在线程之间也是如此。

其他概念

客户端选项 | 访问响应 | 长时间运行的操作 | 处理失败 | 诊断 | 嘲笑 | 客户端生存期

示例

即将推出。。。

疑难解答

从 Azure 机密账本客户端方法返回的响应值是 Response 对象,这些对象包含有关 http 响应的信息(如 http Status 属性)和一个 Headers 包含有关失败的详细信息的 对象。

设置控制台日志记录

查看日志的最简单方法是启用控制台日志记录。 若要创建将消息输出到控制台的 Azure SDK 日志侦听器,请使用 AzureEventSourceListener.CreateConsoleLogger 方法。

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

若要了解有关其他日志记录机制的详细信息,请参阅 此处

后续步骤

有关 Azure 机密账本的更广泛文档,请参阅 API 参考文档。 你还可以阅读有关 Microsoft Research 的开源机密 联盟框架的详细信息。

贡献

本项目欢迎贡献和建议。 大多数贡献要求你同意贡献者许可协议 (CLA),并声明你有权(并且确实有权)授予我们使用你的贡献的权利。 有关详细信息,请访问 cla.microsoft.com

此项目采用了 Microsoft 开放源代码行为准则。 有关详细信息,请参阅行为准则常见问题解答,或如果有任何其他问题或意见,请与 联系。

曝光数