Compartilhar via


Funções simples definidas pelo usuário no registro confidencial do Azure (prévia)

As UDFs (funções definidas pelo usuário) simples no livro-razão confidencial do Azure permitem que você crie funções JavaScript personalizadas que podem ser executadas dentro do perímetro de confiança do livro-razão. Esse recurso foi projetado para ser simples e fácil de usar, permitindo que você estenda a funcionalidade da API do razão sem a necessidade de desenvolvimento de aplicativos complexos.

Usando a API JavaScript interna, você pode executar código personalizado para realizar várias tarefas, como consultas e cálculos personalizados, verificações condicionais, tarefas pós-processamento e muito mais. Este recurso é adequado para cenários nos quais você precisa de uma integração direta com a API do livro-razão existente ou executar uma lógica leve e personalizada em um ambiente confidencial.

Para uma rápida visão geral e demonstração de UDFs, assista ao seguinte vídeo:

Importante

As funções definidas pelo usuário estão atualmente em VERSÃO PRÉVIA na versão 2024-12-09-previewda API. Você pode solicitar acesso para esta versão prévia por meio deste formulário de inscrição. Consulte os Termos de Uso Complementares para Versões Prévias do Microsoft Azure para termos legais que se aplicam aos recursos do Azure que estão em versão beta, versão prévia ou ainda não lançados em disponibilidade geral.

Dica

Para cenários mais avançados, como Controle de Acesso Baseado em Função (RBAC) personalizado ou integração com cargas de trabalho confidenciais externas, veja funções avançadas definidas pelo usuário no razão confidencial do Azure.

Casos de uso

Os UDFs do razão confidencial do Azure permitem que você estenda a funcionalidade do razão executando lógica personalizada. Alguns casos de uso comuns para UDFs incluem:

  • Cálculos e consultas personalizados: execute Funções Definidas pelo Usuário (UDFs) independentes para ler ou gravar dados em qualquer tabela de aplicativo contábil de acordo com sua lógica de negócios.

  • Validação de dados e verificações de entrada: use UDFs como pré-ganchos para executar ações de pré-processamento antes que uma entrada do razão seja gravada no razão, por exemplo, para higienizar dados de entrada ou verificar pré-condições.

  • Enriquecimento de dados e contratos inteligentes: use UDFs como pós-ganchos para executar ações de pós-processamento depois que uma entrada do razão for gravada, por exemplo, para adicionar metadados personalizados ao razão ou acionar fluxos de trabalho de pós-gravação.

Escrevendo UDFs

Um UDF de razão confidencial do Azure é uma entidade armazenada no razão com uma ID exclusiva e contém o código JavaScript que é executado quando o UDF é chamado. Esta seção descreve como escrever código UDF e usar a API JavaScript para realizar tarefas diferentes.

Estrutura de funções

O código de uma UDF requer uma função exportada que é o ponto de entrada do script no momento da execução. Um modelo de código UDF básico tem esta aparência:

export function main() {
    // Your JavaScript code here
}

Observação

O nome da função de ponto de entrada exportada que é chamada durante a execução pode ser modificado com o exportedFunctionName argumento ao executar a UDF. Se não for especificado, o nome padrão será main.

Observação

Há suporte para funções Lambda, mas exigem que o nome da função exportada seja definido explicitamente e corresponda ao nome da função de ponto de entrada. Por exemplo:

export const main = () => { 
    // Your JavaScript code here 
};

Argumentos de função

Você pode especificar quaisquer argumentos opcionais de runtime aceitos pela UDF. Os valores dos argumentos podem ser passados em runtime ao executar a UDF usando o arguments parâmetro.

Os argumentos são sempre passados como uma matriz de cadeias de caracteres. É responsabilidade do usuário garantir que os argumentos especificados no código UDF correspondam aos argumentos passados ao executar a UDF. O usuário também deve garantir que os argumentos sejam analisados corretamente para o tipo de dados esperado em runtime.

export function main(arg1, arg2) {
    // Your JavaScript code here
}

API de JavaScript

O código JavaScript de uma UDF é executado dentro de um ambiente de área restrita que fornece um conjunto limitado de APIs.

Todas as funções, objetos e valores globais padrão do JavaScript podem ser usados . Um objeto global chamado ccf pode ser usado para acessar funcionalidades e utilitários específicos fornecidos pelo CCF (Confidential Consortium Framework) (por exemplo, funções auxiliares de criptografia, acessadores de tabelas do razão etc.). A API completa do ccf objeto global está documentada aqui.

Você também pode acessar informações contextuais da solicitação atual usando o context objeto global. Esse objeto fornece acesso aos metadados de solicitação que originaram a execução da função (context.request) e a ID do usuário do chamador de função (context.userId). Para ganchos de transação, o ID da coleção e o conteúdo da transação associado à operação de gravação também são adicionados ao objeto context (context.collectionId e context.contents respectivamente).

O snippet a seguir mostra alguns exemplos básicos do uso da API JavaScript:

export function main(args) {
    
    // Basic instructions
    const a = 1 + 1;

    // Basic statements
    if (a > 0) {
        console.log("a is positive");
    } else {
        console.log("a is negative or zero");
    }

    // Parse the string argument as a JSON object
    JSON.parse(args);

    // Logging utilities
    console.log("Hello world");
    
    // Math utilities
    Math.random();
    
    // CCF cryptography utilities
    ccf.crypto.digest("SHA-256", ccf.strToBuf("Hello world"));
    
    // Write to a custom ledger table
    ccf.kv["public:mytable"].set(ccf.strToBuf("myKey"), ccf.strToBuf("myValue"));

    // Read from a custom ledger table
    ccf.bufToStr(ccf.kv["public:mytable"].get(ccf.strToBuf("myKey")));

    // Read from the ledger entry table
    ccf.kv["public:confidentialledger.logs"].get(ccf.strToBuf("subledger:0"));

    // Get the request metadata that originated the function execution
    const requestMetadata = context.request;
    
    // Get the collection ID and transaction content (for transaction hooks only)
    const collectionId = context.collectionId;
    const contents = context.contents;

    // Throw exceptions
    throw new Error("MyCustomError");
}

Dica

Para obter mais informações sobre como mapas de livro-razão podem ser usados para armazenar e recuperar dados, consulte a documentação do CCF na API Key-Value Store.

Observação

Não há suporte para a importação de módulos em UDFs. O código JavaScript deve ser autocontido e não pode contar com bibliotecas ou módulos externos. As APIs Web também não têm suporte no momento.

Gerenciando UDFs

Os aplicativos do ledger confidencial do Azure fornecem uma API CRUD dedicada para criar, ler, atualizar e excluir entidades UDF. Os UDFs são armazenados com segurança no razão e são acessíveis somente ao aplicativo de razão.

Observação

Funções definidas pelo usuário simples e funções avançadas definidas pelo usuário são recursos mutuamente exclusivos. Você não pode criar ou executar UDFs simples se UDFs avançadas estiverem definidas, e vice-versa. Para alternar entre os dois, siga as instruções na página de visão geral da UDF.

Criar ou atualizar um UDF

PUT /app/userDefinedFunctions/myFunction
{
    "code": "export function main() { return "Hello World"; }",
}

Importante

A função de administrador é necessária para criar ou atualizar um UDF.

Obtenha um UDF

GET /app/userDefinedFunctions/myFunction

Listar UDFs

GET /app/userDefinedFunctions

Excluir um UDF

DELETE /app/userDefinedFunctions/myFunction

Importante

A função de administrador é necessária para excluir uma UDF.

Observação

Excluir uma UDF apenas remove a entidade do estado atual do razão. Qualquer UDF excluído é sempre retido no histórico do razão imutável (como qualquer transação confirmada).

Executando UDFs

Depois de criado, os usuários do Azure Confidential Ledger podem executar uma UDF como função autônoma ou como um gancho de transação associado a uma operação de gravação. Cada execução de UDF é executada em um ambiente de execução e sandbox separados, o que significa que a execução de UDF é isolada de outras UDFs ou outras operações de razão.

A execução da UDF pode ser controlada usando propriedades opcionais que podem ser especificadas no corpo da solicitação. As propriedades com suporte no momento são:

  • arguments: uma matriz de cadeias de caracteres que representam os argumentos a serem passados para a UDF. Os argumentos são passados na mesma ordem que são definidos no código UDF. O valor padrão é uma matriz vazia.

  • exportedFunctionName: o nome da função exportada a ser chamada durante a execução. Se esse campo não for especificado, o valor padrão será main.

  • runtimeOptions: um objeto que especifica as opções de runtime para a execução da UDF. As seguintes opções estão disponíveis:

    • max_heap_bytes: o tamanho máximo do heap em bytes. O valor padrão é 10.485.760 (10 MB).

    • max_stack_bytes: o tamanho máximo da pilha em bytes. O valor padrão é 1.048.576 (1 MB).

    • max_execution_time_ms: o tempo máximo de execução em milissegundos. O valor padrão é 1000 (1 segundo).

    • log_exception_details: um valor booliano que especifica se os detalhes da exceção devem ser registrados em log. O valor padrão é true.

    • return_exception_details: um valor booliano que especifica se os detalhes da exceção devem ser retornados na resposta. O valor padrão é true.

Funções autônomas

Uma UDF pode ser executada diretamente usando a POST /app/userDefinedFunctions/{functionId}:execute API.

POST /app/userDefinedFunctions/myFunction:execute
{}

O corpo da solicitação pode ser usado para especificar parâmetros de execução opcionais, como argumentos de função e propriedades de runtime do JavaScript.

POST /app/userDefinedFunctions/myFunction:execute
{
    "arguments": ["arg1", "arg2"],
    "exportedFunctionName": "myMainFunction",
    "runtimeOptions": {
        "max_heap_bytes": 5,
        "max_stack_bytes": 1024,
        "max_execution_time_ms": 5000,
        "log_exception_details": true,
        "return_exception_details": true
    }
}

A resposta indica o resultado da execução da UDF (bem-sucedida ou com falha). Se a UDF tiver sido bem-sucedida, a resposta incluirá o valor retornado da função no formato de cadeia de caracteres (se houver).

{
    "result": 
        {
            "returnValue": "MyReturnValue"
        }, 
    "status": "Succeeded"
}

Se a UDF falhar, a resposta incluirá a mensagem de erro com o rastreamento de pilha detalhado.

{
    "error": {
        "message": "Error while executing function myFunction: Error: MyCustomError\n    at myMainFunction (myFunction)\n"
    }, 
    "status": "Failed"
}

Importante

A função de colaborador é necessária para executar uma UDF.

Ganchos de transação

Um UDF pode ser executado alternativamente como um gancho antes (pré-gancho) ou depois (pós-gancho) de uma entrada ser gravada no razão como parte da API de gravação do razão (POST /app/transactions). Os ganchos são executados no mesmo contexto da operação de gravação; isso significa que quaisquer dados gravados no razão pelos ganchos são automaticamente incluídos na mesma transação de gravação.

O corpo da solicitação de gravação pode ser usado para especificar quaisquer IDs UDF a serem executadas como pré-ganchos e pós-ganchos, respectivamente.

POST /app/transactions?collectionId=myCollection
{
  "contents": "myValue", 
  "preHooks": [ 
    { 
        "functionId": "myPreHook"
    } 
  ], 
  "postHooks": [ 
    { 
        "functionId": "myPostHook" 
    }
  ] 
} 

Importante

Os ganchos devem ser definidos explicitamente no corpo da solicitação da operação de escrita. Em geral, as UDFs não podem ser executadas automaticamente para cada operação de gravação após serem criadas.

Para cada gancho, é possível especificar quaisquer propriedades de execução opcionais. Por exemplo:

POST /app/transactions?collectionId=myCollection
{
  "contents": "myValue", 
  "preHooks": [ 
    { 
        "functionId": "myPreHook", 
        "properties": { 
            "arguments": [ 
                "arg1",
                "arg2"
            ], 
            "exportedFunctionName": "myMainFunction", 
            "runtimeOptions": { 
                "max_heap_bytes": 5,
                "max_stack_bytes": 1024,
                "max_execution_time_ms": 5000,
                "log_exception_details": true,
                "return_exception_details": true
            } 
        } 
    } 
  ], 
  "postHooks": [ 
    { 
        "functionId": "myPostHook", 
        "properties": { 
            "arguments": [ 
                "arg1"
            ], 
            "exportedFunctionName": "myMainFunction", 
            "runtimeOptions": { 
                "max_heap_bytes": 5,
                "max_stack_bytes": 1024,
                "max_execution_time_ms": 5000,
                "log_exception_details": true,
                "return_exception_details": true
            } 
        } 
    }
  ] 
} 

Você pode especificar até 5 pré-ganchos e pós-ganchos no corpo da solicitação, com qualquer combinação. Os ganchos são sempre executados na ordem em que são fornecidos no corpo da solicitação.

Se um pré-gancho ou pós-gancho falhar, toda a transação será abortada. Nesse caso, a resposta contém a mensagem de erro com o motivo da falha:

{
    "error": {
        "code": "InternalError",
        "message": "Error while executing function myPreHook: Error: MyCustomError\n    at myMainFunction (myPreHook)\n",
    }
}

Observação

Mesmo que vários ganchos sejam bem-sucedidos, a transação ainda poderá falhar se algum dos pré-ganchos ou pós-ganchos definidos não for executado com sucesso até a conclusão.

Dica

Uma UDF pode ser reutilizada como um pré-gancho e um pós-gancho na mesma solicitação e chamada várias vezes.

Exemplos

Essa seção aborda alguns exemplos práticos de como usar UDFs no razão confidencial do Azure. Para os cenários de exemplo a seguir, pressupõe-se o uso do Azure Confidential Ledger a fim de armazenar transações bancárias de diferentes usuários bancários.

Contexto

Para armazenar uma transação bancária para um usuário, a API de gravação de razão existente pode ser usada: o valor da transação é o conteúdo da entrada do razão e o ID do usuário pode ser a coleção, ou chave, onde o conteúdo é gravado.

POST /app/transactions?collectionId=John
{
    "contents": "10"
}

HTTP/1.1 200 OK

Como não há validação no conteúdo de entrada, é possível gravar um valor não numérico como um conteúdo. Por exemplo, essa solicitação é bem-sucedida mesmo que o valor do conteúdo não seja um número:

POST /app/transactions?collectionId=Mark
{
    "contents": "This is not a number"
}

HTTP/1.1 200 OK

Pré-ganchos para validação de dados

Para garantir que o conteúdo da transação seja sempre um número, uma UDF pode ser criada para verificar o conteúdo de entrada. O pré-gancho a seguir verifica se o valor do conteúdo é um número e gera um erro caso contrário.

PUT /app/userDefinedFunctions/validateTransaction
{
    "code": "export function main() { if (isNaN(context.contents)) { throw new Error('Contents is not a number'); } }"
}

HTTP/1.1 201 CREATED

Usando o pre-hook na solicitação de gravação, é possível impor que os dados de entrada correspondam ao formato esperado. A solicitação anterior agora falha conforme o esperado:

POST /app/transactions?collectionId=Mark
{
    "contents": "This is not a number",
    "preHooks": [
        {
            "functionId": "validateTransaction"
        }
    ]
}

HTTP/1.1 500 INTERNAL_SERVER_ERROR
{
  "error": {
    "code": "InternalError",
    "message": "Error while executing function validateTransaction: Error: Contents is not a number\n    at main (validateTransaction)\n"
  }
}

As solicitações válidas que contêm valores numéricos teriam êxito conforme o esperado:

POST /app/transactions?collectionId=Mark
{
    "contents": "30",
    "preHooks": [
        {
            "functionId": "validateTransaction"
        }
    ]
}

HTTP/1.1 200 OK

Pós-ganchos para enriquecimento de dados

À medida que os usuários executam novas transações bancárias, queremos registrar quando uma transação é maior que um determinado limite por motivos de auditoria. Um pós-gancho pode ser usado para gravar metadados personalizados em um razão após uma operação de gravação para indicar se a transação foi maior que um determinado limite.

Por exemplo, uma UDF pode ser criada para verificar o valor da transação e escrever uma mensagem fictícia ("Alerta" para valores altos, "Normal" caso contrário) sob o usuário de entrada em uma tabela de razão personalizada (payment_metadata) se o valor for maior que 50.

PUT /app/userDefinedFunctions/detectHighTransaction
{
    "code": "export function main() { let value = 'Normal'; if (context.contents > 50) { value = 'Alert' } ccf.kv['public:payment_metadata'].set(ccf.strToBuf(context.collectionId), ccf.strToBuf(value)); }"
}

HTTP/1.1 201 CREATED

Depois que o UDF for criado com sucesso, o pós-gancho poderá ser usado em novas solicitações de gravação:

POST /app/transactions?collectionId=Mark
{
    "contents": "100",
    "preHooks": [
        {
            "functionId": "validateTransaction"
        }
    ],
    "postHooks": [
        {
            "functionId": "detectHighTransaction"
        }
    ]
}

HTTP/1.1 200 OK
POST /app/transactions?collectionId=John
{
    "contents": "20",
    "preHooks": [
        {
            "functionId": "validateTransaction"
        }
    ],
    "postHooks": [
        {
            "functionId": "detectHighTransaction"
        }
    ]
}

HTTP/1.1 200 OK

UDFs autônomos para consultas personalizadas

Para inspecionar os últimos valores gravados na tabela personalizada payment_metadata usando o pós-gancho, uma UDF pode ser criada para ler os valores da tabela, dado um ID de usuário de entrada:

PUT /app/userDefinedFunctions/checkPaymentMetadataTable
{
    "code": "export function main(user) { const value = ccf.kv['public:payment_metadata'].get(ccf.strToBuf(user)); if (value === undefined) { throw new Error('UnknownUser'); } return ccf.bufToStr(value); }"
}

HTTP/1.1 201 CREATED

Executando a UDF diretamente, é possível verificar o valor mais recente registrado na tabela de metadados personalizados para um determinado usuário.

Para usuários com uma transação alta recente, o UDF retorna o valor "Alerta", conforme esperado.

POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
    "arguments": [
        "Mark"
    ]
}

HTTP/1.1 200 OK
{
  "result": {
    "returnValue": "Alert"
  },
  "status": "Succeeded"
}

Para usuários com uma transação recente de baixo valor, a UDF retorna o valor "Normal".

POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
    "arguments": [
        "John"
    ]
}

HTTP/1.1 200 OK
{
  "result": {
    "returnValue": "Normal"
  },
  "status": "Succeeded"
}

Para usuários que não têm nenhuma entrada na tabela personalizada, a UDF gera um erro conforme definido no código UDF.

POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
    "arguments": [
        "Jane"
    ]
}

HTTP/1.1 200 OK
{
  "error": {
    "message": "Error while executing function checkPaymentMetadataTable: Error: UnknownUser\n    at main (checkPaymentMetadataTable)\n"
  },
  "status": "Failed"
}

Considerações

  • Atualmente, os ganchos de transação são suportados apenas para a API POST /app/transactions, ao adicionar uma nova entrada ao razão.

  • UDFs e ganchos são sempre executados na réplica primária do razão, para garantir a ordem das transações e uma consistência forte.

  • A execução de código UDF é sempre encapsulada em uma única transação atômica. Se a lógica JavaScript em uma UDF for concluída sem nenhuma exceção, todas as operações dentro da UDF serão confirmadas no razão. Se alguma exceção for lançada, todas as transações serão revertidas. Da mesma forma, pré-ganchos e pós-ganchos são executados no mesmo contexto da operação de gravação na qual estão registrados. Se um pré-gancho ou um pós-gancho falhar, toda a transação será abortada e nenhuma entrada será adicionada ao razão.

  • Os UDFs só podem acessar tabelas de aplicativos CCF e não podem acessar as tabelas internas e de governança do razão ou outras tabelas internas por motivos de segurança. As tabelas do razão onde as entradas são gravadas (public:confidentialledger.logs para livros-razão públicos e private:confidentialledger.logs para livros-razão privados) são somente leitura.

  • O número máximo de pré-ganchos e pós-ganchos que podem ser registrados para uma única transação de gravação é 5.

  • A execução de UDF e guincho é limitada a 5 segundos. Se uma função levar mais de 5 segundos para ser executada, a operação será anulada e um erro será retornado.