Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Les fonctions définies par l’utilisateur (UDF) simples dans le registre confidentiel Azure vous permettent de créer des fonctions JavaScript personnalisées qui peuvent être exécutées à l’intérieur de la limite d’approbation du registre. Cette fonctionnalité est conçue pour être simple et facile à utiliser, ce qui vous permet d’étendre les fonctionnalités de l’API de registre sans avoir besoin de développement d’applications complexes.
À l’aide de l’API JavaScript intégrée, vous pouvez exécuter du code personnalisé pour effectuer différentes tâches, telles que des requêtes et des calculs personnalisés, des vérifications conditionnelles, des tâches de post-traitement, etc. Cette fonctionnalité convient aux scénarios où vous avez besoin d’une intégration directe avec l’API de registre existante ou d’exécuter une logique personnalisée légère dans un environnement confidentiel.
Pour obtenir une vue d’ensemble rapide et une version de démonstration des UDF, regardez la vidéo suivante :
Important
Les fonctions définies par l’utilisateur sont actuellement en aperçu sous la version 2024-12-09-preview de l’API.
Vous pouvez demander l’accès à cette préversion via ce formulaire d’inscription.
Consultez les Conditions d’utilisation supplémentaires pour les préversions Microsoft Azure pour les conditions légales qui s’appliquent aux fonctionnalités Azure en version bêta, en préversion ou qui ne sont pas encore publiées en disponibilité générale.
Conseil / Astuce
Pour des scénarios plus avancés, tels que Role-Based le contrôle d’accès personnalisé (RBAC) ou l’intégration à des charges de travail confidentielles externes, consultez les fonctions avancées définies par l’utilisateur dans le registre confidentiel Azure.
Cas d’utilisation
Les UDF du registre confidentiel Azure vous permettent d’étendre les fonctionnalités du registre en exécutant une logique personnalisée. Voici quelques cas d’usage courants pour les UDF :
Calculs et requêtes personnalisés : exécutez des UDF autonome pour lire ou écrire des données dans n’importe quelle table d’application de registre en fonction de votre logique de productivité.
Validation des données et vérifications d’entrée : utilisez des UDF comme pré-crochets pour exécuter des actions de prétraitement avant qu’une entrée de registre soit écrite dans le registre. Par exemple pour nettoyer les données d’entrée ou vérifier les conditions préalables.
Enrichissement des données et contrats intelligents : utilisez les UDFs comme post-hooks pour exécuter des actions de post-traitement après l'enregistrement d'une entrée dans le journal, par exemple pour ajouter des métadonnées personnalisées dans le journal ou déclencher des flux de travail après l'enregistrement.
Écriture des UDF
Une fonction UDF du registre confidentiel Azure est une entité stockée dans le registre avec un ID unique et contient le code JavaScript qui est exécuté lorsque l’UDF est appelée. Cette section explique comment écrire du code UDF et utiliser l’API JavaScript pour accomplir différentes tâches.
Structure de fonction
Le code d’une fonction UDF nécessite une fonction exportée qui est le point d’entrée du script au moment de l’exécution. Un modèle de code UDF de base ressemble à ceci :
export function main() {
// Your JavaScript code here
}
Remarque
Le nom de la fonction de point d'entrée exportée, appelée lors de l'exécution, peut être modifié avec l'argument exportedFunctionName lors de l'exécution de la fonction UDF. Si ce n’est pas spécifié, le nom par défaut est main.
Remarque
Les fonctions lambda sont prises en charge, mais elles nécessitent que le nom de la fonction exportée soit explicitement défini et qu’ils correspondent au nom de la fonction de point d’entrée. Par exemple:
export const main = () => {
// Your JavaScript code here
};
Arguments de fonction
Vous pouvez spécifier tous les arguments d’exécution facultatifs acceptés par la fonction UDF. Les valeurs des arguments peuvent être passées au moment de l’exécution lors de l’exécution de la fonction UDF à l’aide du arguments paramètre.
Les arguments sont toujours passés sous forme de tableau de chaînes. Il incombe à l’utilisateur de s’assurer que les arguments spécifiés dans le code UDF correspondent aux arguments passés lors de l’exécution de la fonction UDF. L’utilisateur doit également s’assurer que les arguments sont correctement analysés sur le type de données attendu lors de l’exécution.
export function main(arg1, arg2) {
// Your JavaScript code here
}
API JavaScript
Le code JavaScript d’un UDF est exécuté à l’intérieur d’un environnement de bac à sable qui fournit un ensemble limité d’API.
Toutes les fonctions globales, objets et valeurs standard JavaScript peuvent être utilisées. Un objet global appelé ccf peut être utilisé pour accéder à des fonctionnalités et utilitaires spécifiques fournis par le CCF (Confidential Consortium Framework) ( par exemple, fonctions d’assistance de chiffrement, accesseurs de tables de registre, etc.). L’API complète de l’objet ccf global est documentée ici.
Vous pouvez également accéder aux informations contextuelles de la requête actuelle à l’aide de l’objet context global. Cet objet fournit l’accès aux métadonnées de requête qui proviennent de l’exécution de la fonction (context.request) et de l’ID utilisateur de l’appelant de fonction (context.userId). Pour les hooks de transaction, l’ID de collection et le contenu de la transaction associés à l’opération d’écriture sont également ajoutés à l’objet context (context.collectionId et context.contents respectivement).
L’extrait de code suivant illustre quelques exemples de base de l’utilisation de l’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");
}
Conseil / Astuce
Pour plus d’informations sur la façon dont les mappages de registre peuvent être utilisés pour stocker et récupérer des données, consultez la documentation CCF sur l’API Key-Value Store.
Remarque
L’importation de modules n’est pas prise en charge dans les UDF. Le code JavaScript doit être autonome et ne peut pas s’appuyer sur des bibliothèques ou des modules externes. Les API web ne sont pas prises en charge actuellement.
Gestion des fonctions définies par l’utilisateur (User-Defined Functions)
Les applications de registre confidentiel Azure fournissent une API CRUD dédiée pour créer, lire, mettre à jour et supprimer des entités UDF. Les UDF sont stockés en toute sécurité dans le registre et sont accessibles uniquement à l’application de registre.
Remarque
Les fonctions simples définies par l’utilisateur et les fonctions avancées définies par l’utilisateur sont des fonctionnalités mutuellement exclusives. Vous ne pouvez pas créer ou exécuter des fonctions définies par l’utilisateur simples si des fonctions définies par l’utilisateur avancées sont définies, et inversement. Pour basculer entre les deux, suivez les instructions de la page de vue d’ensemble de la fonction UDF.
Créer ou mettre à jour un UDF
PUT /app/userDefinedFunctions/myFunction
{
"code": "export function main() { return "Hello World"; }",
}
Important
Le rôle d’administrateur est requis pour créer ou mettre à jour une fonction UDF.
Obtenir un UDF
GET /app/userDefinedFunctions/myFunction
Afficher la liste des UDF
GET /app/userDefinedFunctions
Supprimer un UDF
DELETE /app/userDefinedFunctions/myFunction
Important
Le rôle d’administrateur est requis pour supprimer une fonction UDF.
Remarque
La suppression d’un UDF supprime uniquement l’entité de l’état actuel du registre. Tout UDF supprimé est toujours conservée dans l’historique immuable du registre (comme toute transaction validée).
Exécution des UDF
Une fois créés, les utilisateurs du registre confidentiel Azure peuvent exécuter une fonction UDF en tant que fonction autonome ou en tant que hook de transaction associé à une opération d’écriture. Chaque exécution d’un UDF se fait sur un environnement d’exécution et un bac à sable distincts, ce qui signifie que l’exécution d’un UDF est isolée des autres fonctions définies par l’utilisateur ou d’autres opérations de registre.
L’exécution UDF peut être contrôlée à l’aide de propriétés facultatives qui peuvent être spécifiées dans le corps de la requête. Les propriétés actuellement prises en charge sont les suivantes :
arguments: tableau de chaînes qui représentent les arguments à passer à un UDF. Les arguments sont passés dans le même ordre qu’ils sont définis dans le code UDF. La valeur par défaut est un tableau vide.exportedFunctionName: nom de la fonction exportée à appeler pendant l’exécution. Si elle n’est pas spécifiée, la valeur par défaut estmain.runtimeOptions: objet qui spécifie les options d’exécution pour l’exécution d’un UDF. Les options suivantes sont disponibles :max_heap_bytes: taille maximale du tas en octets. La valeur par défaut est 10 485 760 (10 Mo).max_stack_bytes: taille maximale de la pile en octets. La valeur par défaut est 1 048 576 (1 Mo).max_execution_time_ms: durée d’exécution maximale en millisecondes. La valeur par défaut est 1 000 (1 seconde).log_exception_details: valeur booléenne qui spécifie s’il faut enregistrer les détails de l’exception. La valeur par défaut esttrue.return_exception_details: valeur booléenne qui spécifie s’il faut retourner les détails de l’exception dans la réponse. La valeur par défaut esttrue.
Fonctions autonomes
Une fonction UDF peut être exécutée directement à l’aide de l’API POST /app/userDefinedFunctions/{functionId}:execute .
POST /app/userDefinedFunctions/myFunction:execute
{}
Le corps de la requête peut être utilisé pour spécifier des paramètres d’exécution facultatifs, tels que des arguments de fonction et des propriétés d’exécution 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
}
}
La réponse indique le résultat de l’exécution d’un UDF (réussie ou pas). Si l’UDF a réussi, la réponse inclut la valeur retournée par la fonction au format de chaîne (le cas échéant).
{
"result":
{
"returnValue": "MyReturnValue"
},
"status": "Succeeded"
}
Si un UDF a échoué, la réponse inclut le message d’erreur avec le rapport détaillé des appels de procédure.
{
"error": {
"message": "Error while executing function myFunction: Error: MyCustomError\n at myMainFunction (myFunction)\n"
},
"status": "Failed"
}
Important
Le rôle Collaborateur est requis pour exécuter un UDF.
Hooks de transaction
Un UDF peut également être exécuté comme un hook avant (pre-hook) ou après (post-hook) l’criture d’une entrée dans le registre en tant qu’élément de l'API d’écriture du registre (POST /app/transactions). Les hooks s’exécutent dans le même contexte de l’opération d’écriture ; cela signifie que toutes les données écrites dans le registre par les hooks sont automatiquement incluses dans la même transaction d’écriture.
Le corps de la demande d’écriture peut être utilisé pour spécifier les ID UDF à exécuter en tant que pre-hooks et post-hooks respectivement.
POST /app/transactions?collectionId=myCollection
{
"contents": "myValue",
"preHooks": [
{
"functionId": "myPreHook"
}
],
"postHooks": [
{
"functionId": "myPostHook"
}
]
}
Important
Les hooks doivent être définis explicitement dans le corps de la requête de l’opération d’écriture. En règle générale, les UDF ne peuvent pas s’exécuter automatiquement pour chaque opération d’écriture après la création.
Pour chaque hook, il est possible de spécifier toutes les propriétés d’exécution facultatives. Par exemple:
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
}
}
}
]
}
Vous pouvez spécifier jusqu’à 5 pré-hooks et post-hooks dans le corps de la demande, avec n’importe quelle combinaison. Les hooks sont toujours exécutés dans l’ordre dans lequel ils sont fournis dans le corps de la demande.
En cas d’échec d’un pré-hook ou d’un post-hook, la transaction entière est abandonnée. Dans ce cas, la réponse contient le message d’erreur avec la raison de l’échec :
{
"error": {
"code": "InternalError",
"message": "Error while executing function myPreHook: Error: MyCustomError\n at myMainFunction (myPreHook)\n",
}
}
Remarque
Même si plusieurs hooks réussissent, la transaction peut toujours échouer si l'un des pre-hooks ou post-hooks définis ne s'exécute pas jusqu'au bout.
Conseil / Astuce
Un UDF peut être réutilisé en tant que pré-hook et post-hook dans la même requête et appelé plusieurs fois.
Exemples
Cette section décrit quelques exemples pratiques d’utilisation des UDF dans le registre confidentiel Azure. Pour les exemples de scénarios suivants, nous supposons l’utilisation du registre confidentiel Azure pour stocker des transactions bancaires de différents utilisateurs de banque.
Contexte
Pour stocker une transaction bancaire pour un utilisateur, l’API d’écriture de registre existante peut être utilisée : la valeur de la transaction est le contenu de l’entrée de registre et l’ID utilisateur peut servir de collection, ou de clé, où le contenu est stocké.
POST /app/transactions?collectionId=John
{
"contents": "10"
}
HTTP/1.1 200 OK
Étant donné qu’il n’existe aucune validation sur le contenu d’entrée, il est possible d’écrire une valeur non numérique en tant que contenu. Par exemple, cette requête réussit même si la valeur du contenu n’est pas un nombre :
POST /app/transactions?collectionId=Mark
{
"contents": "This is not a number"
}
HTTP/1.1 200 OK
Pré-hooks pour validation des données
Pour vous assurer que le contenu de la transaction est toujours un nombre, une fonction UDF peut être créée pour vérifier le contenu d’entrée. Le pre-hook suivant vérifie si la valeur du contenu est un nombre et signale une erreur si ce n’est pas le cas.
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
En utilisant le pre-hook dans la demande d'écriture, il est possible de s'assurer que les données d'entrée correspondent au format attendu. La demande précédente échoue maintenant comme prévu :
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"
}
}
Les requêtes valides contenant des valeurs numériques réussissent plutôt comme prévu :
POST /app/transactions?collectionId=Mark
{
"contents": "30",
"preHooks": [
{
"functionId": "validateTransaction"
}
]
}
HTTP/1.1 200 OK
Post-hooks pour l’enrichissement des données
À mesure que les utilisateurs effectuent de nouvelles transactions bancaires, nous voulons enregistrer lorsqu’une transaction est supérieure à un certain seuil pour des raisons d’audit. Un post-hook peut être utilisé pour écrire des métadonnées personnalisées dans un registre après une opération d’écriture pour indiquer si la transaction était supérieure à un certain seuil.
Par exemple, une fonction UDF peut être créée pour vérifier la valeur de la transaction et rédiger un message factice (« Alerte » pour les valeurs élevées, « Normal » sinon) sous l'entrée utilisateur dans une table de grand livre personnalisée (payment_metadata) si la valeur est supérieure à 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
Une fois un UDF créé, le post-hook peut être utilisé dans les nouvelles demandes d’écriture :
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
Fonctions définies par l’utilisateur autonome pour les requêtes personnalisées
Pour inspecter les dernières valeurs écrites dans la table payment_metadata personnalisée à l’aide du post-hook, une fonction UDF peut être créée pour lire les valeurs de la table en fonction d’un ID d’utilisateur d’entrée :
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
En exécutant directement la fonction UDF, il est possible de vérifier la dernière valeur enregistrée dans la table de métadonnées personnalisée pour un utilisateur donné.
Pour les utilisateurs ayant récemment effectué une transaction élevée, l’UDF retourne la valeur « Alerte » comme prévu.
POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
"arguments": [
"Mark"
]
}
HTTP/1.1 200 OK
{
"result": {
"returnValue": "Alert"
},
"status": "Succeeded"
}
Pour les utilisateurs ayant une transaction faible récente, l’UDF retourne la valeur « Normal » à la place.
POST /app/userDefinedFunctions/checkPaymentMetadataTable:execute
{
"arguments": [
"John"
]
}
HTTP/1.1 200 OK
{
"result": {
"returnValue": "Normal"
},
"status": "Succeeded"
}
Pour les utilisateurs qui n’ont pas d’entrée dans la table personnalisée, la fonction UDF lève une erreur telle que définie dans le code 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"
}
Considérations
Les hooks de transaction ne sont actuellement pris en charge que pour l’API
POST /app/transactions, lors de l’ajout d’une nouvelle entrée au registre.Les UDF et les hooks sont toujours exécutés sur le réplica principal du registre, pour garantir l’ordre des transactions et une cohérence forte.
L’exécution du code UDF est toujours encapsulée dans une transaction atomique unique. Si la logique JavaScript dans une fonction UDF se termine sans exception, toutes les opérations au sein d’un UDF sont validées dans le registre. Si une exception est levée, toutes les transactions sont annulées. De même, les pré-hooks et les post-hooks sont exécutés dans le même contexte de l’opération d’écriture à laquelle ils sont inscrits. Si un pré-hook ou un post-hook échoue, la transaction entière est abandonnée et aucune entrée n’est ajoutée au registre.
Les fonctions définies par l’utilisateur peuvent uniquement accéder aux tables d’application CCF et ne peuvent pas accéder aux tables internes et de gouvernance du registre ou à d’autres tables intégrées pour des raisons de sécurité. Les tables de registre dans lesquelles les entrées sont écrites (
public:confidentialledger.logspour les registres publics etprivate:confidentialledger.logspour les registres privés) sont en lecture seule.Le nombre maximal de pré-hooks et de post-hooks qui peuvent être inscrits pour une transaction d’écriture unique est de 5.
L’exécution des UDF et des hooks est limitée à 5 secondes. Si une fonction prend plus de 5 secondes pour s’exécuter, l’opération est abandonnée et une erreur est retournée.