Biblioteca cliente de Azure Confidential Ledger para .NET: versión 1.2.0

Azure Confidential Ledger proporciona un servicio para el registro en un libro de contabilidad inmutable y a prueba de alteraciones. Como parte de la cartera de computación confidencial de Azure , Azure Confidential Ledger se ejecuta en enclaves sgx. Se basa en el marco del consorcio confidencial de Microsoft Research.

Código | fuentePaquete (NuGet)

Introducción

Esta sección debe incluir todo lo que un desarrollador debe hacer para instalar y crear su primera conexión de cliente muy rápidamente.

Instalar el paquete

Instale la biblioteca cliente de Azure Confidential Ledger para .NET con NuGet:

dotnet add package Azure.Security.ConfidentialLedger

Requisitos previos

  • Una suscripción de Azure.
  • Una instancia en ejecución de Azure Confidential Ledger.
  • Un usuario registrado en Azure Confidential Ledger con Administrator privilegios.

Autenticar el cliente

Uso de Azure Active Directory

En este documento se muestra cómo usar DefaultAzureCredential para autenticarse en el libro de contabilidad confidencial mediante Azure Active Directory. Sin embargo, se aceptará cualquiera de las credenciales que ofrece Azure.Identity . Consulte la documentación de Azure.Identity para obtener más información sobre otras credenciales.

Uso de un certificado de cliente

Como alternativa a Azure Active Directory, los clientes pueden optar por usar un certificado de cliente para autenticarse a través de TLS mutuo.

Creación de un cliente

DefaultAzureCredential controlará automáticamente la mayoría de los escenarios de cliente del SDK de Azure. Para empezar, establezca variables de entorno para la identidad de AAD registrada en el libro de contabilidad confidencial.

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

A continuación, DefaultAzureCredential podrá autenticar .ConfidentialLedgerClient

La construcción del cliente también requiere el URI del libro de contabilidad confidencial, que puede obtener en la página de Azure Portal del libro de contabilidad confidencial en el Ledger URI campo de la Properties sección . Cuando haya recuperado Ledger URI, úselo para reemplazarlo "https://my-ledger-url.confidential-ledger.azure.com" en el ejemplo siguiente.

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

Nota de seguridad: De forma predeterminada, cuando se crea un cliente de libro de contabilidad confidencial, se conectará al servicio de identidades del libro de contabilidad confidencial de Azure para obtener el certificado de servicio TLS más reciente para el libro de contabilidad con el fin de proteger las conexiones a los nodos del libro de contabilidad. Los detalles de este proceso están disponibles en este ejemplo. Este comportamiento se puede invalidar estableciendo el options argumento al crear el cliente de libro de contabilidad.

Conceptos clave

Entradas del libro de contabilidad

Cada escritura en Azure Confidential Ledger genera una entrada de libro de contabilidad inmutable en el servicio. Las escrituras se identifican de forma única por identificadores de transacción que se incrementan con cada escritura.

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

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

Dado que Azure Confidential Ledger es un sistema distribuido, los errores transitorios poco frecuentes pueden provocar la pérdida de escrituras. En el caso de las entradas que se deben conservar, es aconsejable comprobar que la escritura se hizo duradera. Nota: Puede que sea necesario llamar GetTransactionStatus varias veces hasta que devuelva un estado "Confirmado". Sin embargo, al llamar a PostLedgerEntry, un resultado correcto indica que el estado es "Confirmado".

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

Los cambios de estado en un libro de contabilidad confidencial se guardan en una estructura de datos denominada árbol Merkle. Para comprobar criptográficamente que las escrituras se guardaron correctamente, se puede recuperar una prueba de Merkle o recibo para cualquier identificador de transacción.

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

Console.WriteLine(receiptJson);

Colecciones

Aunque la mayoría de los casos de uso implicarán un libro de contabilidad, proporcionamos la característica de recopilaciones en caso de que se necesiten almacenar grupos lógicos diferentes de datos en el mismo libro de contabilidad confidencial.

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

Cuando no se especifica ningún identificador de recopilación en las llamadas de método, el servicio Azure Confidential Ledger asume un identificador de recopilación constante determinado por el servicio.

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

Las entradas del libro de contabilidad se recuperan de las colecciones. Cuando se especifica un identificador de transacción, el valor devuelto es el valor contenido en la colección especificada en el momento dado identificado por el identificador de transacción. Si no se especifica ningún identificador de transacción, se devuelve el valor disponible más reciente.

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"
Consultas con intervalos

Las entradas del libro de contabilidad de una colección se pueden recuperar en un intervalo de identificadores de transacción. Nota: Ambos intervalos son opcionales; se pueden proporcionar individualmente o no en absoluto.

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

Administración de usuarios

Los usuarios se administran directamente con el libro de contabilidad confidencial en lugar de a través de Azure. Los nuevos usuarios pueden estar basados en AAD o basados en certificados.

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

Comprobaciones de enclave y consorcio confidencial

Es posible que quiera validar los detalles sobre el libro de contabilidad confidencial por diversos motivos. Por ejemplo, puede que desee ver detalles sobre cómo Microsoft puede administrar el libro de contabilidad confidencial como parte de la gobernanza de Confidential Consortium Framework o comprobar que el libro de contabilidad confidencial se ejecuta realmente en enclaves sgx. Se proporcionan varios métodos de cliente para estos casos de uso.

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 es un proveedor de citas de enclave de SGX.

Seguridad para subprocesos

Garantizamos que todos los métodos de instancia de cliente son seguros para subprocesos e independientes entre sí (instrucciones). Esto garantiza que la recomendación de reutilizar instancias de cliente siempre es segura, incluso entre subprocesos.

Conceptos adicionales

Opciones | de clienteAcceso a la respuesta | Operaciones | de larga duraciónControl de errores | Diagnóstico | Burla | Duración del cliente

Ejemplos

Próximamente...

Solución de problemas

Los valores de respuesta devueltos por los métodos de cliente de Azure Confidential Ledger son Response objetos, que contienen información sobre la respuesta http, como la propiedad http Status y un Headers objeto que contiene más información sobre el error.

Configuración del registro de la consola

La manera más sencilla de ver los registros es habilitar el registro de la consola. Para crear un agente de escucha de registro del SDK de Azure que genere mensajes en la consola, use el método AzureEventSourceListener.CreateConsoleLogger.

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

Para más información sobre otros mecanismos de registro, consulte aquí.

Pasos siguientes

Para obtener documentación más amplia sobre Azure Confidential Ledger, consulte la documentación de referencia de la API. También puede leer más sobre el marco confidencial de código abierto de Microsoft Research.

Contribuciones

Este proyecto agradece las contribuciones y sugerencias. La mayoría de las contribuciones requieren que acepte un Contrato de licencia para el colaborador (CLA) que declara que tiene el derecho a concedernos y nos concede los derechos para usar su contribución. Para obtener más información, visite cla.microsoft.com.

Este proyecto ha adoptado el Código de conducta de Microsoft Open Source. Para más información, consulte las preguntas más frecuentes del código de conducta o póngase en contacto con opencode@microsoft.com si tiene cualquier otra pregunta o comentario.

Impresiones