Créer des connecteurs de données pull sans code à l’aide d’un sondage imbriqué d’API

Certaines API REST nécessitent des appels séquentiels, où la réponse d’un point de terminaison fournit une entrée qui doit être passée à un autre point de terminaison. CCF (Codeless Connector Framework) Microsoft Sentinel prend en charge ce modèle via l’interrogation de l’API imbriquée pour les connecteurs RestApiPoller.

Important

Les sondages API imbriqués sont actuellement en aperçu public. Les Conditions complémentaires de l’aperçu Azure incluent davantage de termes juridiques qui s’appliquent aux fonctionnalités Azure en version bêta, en aperçu ou autrement non encore publiées en disponibilité générale.

Utilisez l’interrogation d’API imbriquée lorsqu’un appel d’API parent retourne des identificateurs, des curseurs ou d’autres valeurs requises par un ou plusieurs appels d’API enfants. CCF extrait les valeurs requises de la réponse parente, les remplace dans la requête enfant et envoie les réponses enfants à la table de destination configurée.

Configurez l’interrogation d’API imbriquée dans un connecteur d’extraction CCF en définissant la logique d’imbrication dans les règles de connexion RestApiPoller.

Pour connaître le processus de bout en bout permettant de créer et de packager un connecteur CCF, consultez Créer un connecteur sans code pour Microsoft Sentinel. Vous pouvez également utiliser l’extension Microsoft Sentinel pour Visual Studio Code afin d’implémenter et tester des flux de travail de polling API imbriqués. Pour des informations sur la configuration et l’utilisation, voir Construire des connecteurs personnalisés avec de l’IA dans Microsoft Sentinel. Pour connaître les propriétés de requête, de réponse, d’authentification, de pagination et de DCR standard RestApiPoller , consultez les règles de connexion du connecteur de données RestApiPoller.

Note

Si vous êtes un éditeur de logiciels indépendant (ISV) qui crée une intégration Microsoft Sentinel à l'aide de l'infrastructure du connecteur sans code, l'équipe Microsoft App Assure peut être en mesure d'aider. Pour impliquer l’équipe d’assurance des applications, envoyez un e-mail à azuresentinelpartner@microsoft.com.

Prerequisites

Avant de configurer l’interrogation d’API imbriquée, vérifiez que vous comprenez :

  • Points de terminaison d’API que le connecteur doit appeler.
  • Quelles valeurs de réponse de l’appel d’API parent sont requises par l’appel d’API enfant.
  • Schéma de sortie de la table de destination.
  • Comment créer un connecteur CCF RestApiPoller standard.

Un connecteur CCF complet comprend les composants suivants :

  • Table : la table Log Analytics personnalisée où les données ingérées sont stockées.
  • DCR : règle de collection de données qui définit la transformation d’ingestion.
  • Interface utilisateur du connecteur : définition du connecteur de données qui apparaît dans le hub de contenu Microsoft Sentinel.
  • Règles de connexion de données : configuration du connecteur qui extrait les données de l’API source.

L’interrogation de l’API imbriquée est configurée dans les règles de connexion aux données pour un connecteur RestApiPoller.

Qu’est-ce que la scrutation d’API imbriquée ?

L’interrogation d’API imbriquée est un modèle d’interrogation CCF qui chaîne les appels d’API REST. Le premier appel d’API, appelé étape parente, retourne les valeurs requises par les appels d’API ultérieurs, appelées étapes enfants.

Par exemple, une API peut utiliser ce modèle :

  1. GET /incidents retourne une liste d’ID d’incident.
  2. GET /incidents/{incidentId}/details retourne l’enregistrement d’incident complet pour chaque ID.

Un seul appel d’API ne retourne pas les données complètes. Le connecteur doit appeler le point de terminaison de liste, extraire chaque incidentId, puis appeler le point de terminaison de détail une fois pour chaque identifiant.

Utilisez l’interrogation d’API imbriquée dans les cas suivants :

  • Un point de terminaison de liste retourne des ID de ressource et un point de terminaison de détails nécessite chaque ID dans le chemin d’URL ou la chaîne de requête.
  • Une réponse parente renvoie un curseur, un jeton de session, un ID de requête ou un ID de référence nécessaire à une requête enfant.
  • Une réponse contient un tableau de valeurs qui doivent être transmises individuellement à un autre point de terminaison.
  • La réponse parente contient des champs que vous souhaitez conserver, et la réponse enfant ajoute des données d’enrichissement qui doivent être jointes à la même ligne de sortie.

Si un seul appel d’API retourne toutes les données dont vous avez besoin, l’interrogation d’API imbriquée n’est pas nécessaire. Utilisez la propriété standard eventsJsonPaths pour extraire des enregistrements de la réponse.

Fonctionnement de l’interrogation imbriquée des API

L’interrogation imbriquée de l’API est configurée dans les sections suivantes :

Rubrique Emplacement Purpose
request Étape parent Définit la requête API parente. Les propriétés de la fenêtre de temps sont configurées ici.
response Étape parent Définit comment les enregistrements sont extraits de la réponse parent.
stepInfo Étape parent Active l’interrogation imbriquée et définit les étapes enfants à exécuter ensuite.
stepCollectorConfigs Étape parent Définit chaque étape enfant, y compris la requête enfant et le traitement de la réponse.
shouldJoinNestedData Étape enfant Définit si la réponse enfant remplace la sortie du parent ou est ajoutée à l’enregistrement parent.

Le flux d’interrogation imbriqué fonctionne comme suit :

  1. La requête parente s’exécute.
  2. La réponse parente est divisée en enregistrements à l’aide de response.eventsJsonPaths.
  3. stepPlaceholdersParsingKql extrait les valeurs d’espace réservé de chaque enregistrement parent.
  4. CCF remplace les espaces réservés dans la configuration de l’étape enfant.
  5. CCF exécute les requêtes enfants.
  6. La réponse de l’enfant est soit envoyée comme ligne de sortie, soit associée à l’enregistrement parent, selon la valeur de shouldJoinNestedData.

Squelette de configuration d’interrogation imbriquée

L’exemple suivant montre la structure d’un connecteur imbriqué RestApiPoller . Les propriétés CCF standard sont abrégées avec ....

{
  "kind": "RestApiPoller",
  "properties": {
    "connectorDefinitionName": "...",
    "dcrConfig": { },
    "dataType": "...",
    "auth": { },
    "request": {
      "apiEndpoint": "https://api.example.com/incidents",
      "httpMethod": "GET",
      "queryWindowInMin": 60,
      "queryTimeFormat": "yyyy-MM-ddTHH:mm:ssZ",
      "startTimeAttributeName": "startTime",
      "endTimeAttributeName": "endTime"
    },
    "response": {
      "eventsJsonPaths": [ "$.incidents" ],
      "format": "json"
    },
    "stepInfo": {
      "stepType": "Nested",
      "nextSteps": [
        {
          "stepId": "fetchIncidentDetails",
          "stepPlaceholdersParsingKql": "source | project res = parse_json(data) | project incidentId = res.incidentId"
        }
      ]
    },
    "stepCollectorConfigs": {
      "fetchIncidentDetails": {
        "shouldJoinNestedData": false,
        "request": {
          "httpMethod": "GET",
          "apiEndpoint": "https://api.example.com/incidents/$incidentId$/details"
        },
        "response": {
          "eventsJsonPaths": [ "$" ],
          "format": "json"
        }
      }
    }
  }
}

Propriétés d’interrogation imbriquée

Propriété Emplacement Description
stepInfo.stepType Étape parent Doit être défini sur Nested pour activer l’interrogation imbriquée de l’API.
stepInfo.nextSteps[].stepId Étape parent Nom de l’étape enfant. Cette valeur doit correspondre à une clé dans stepCollectorConfigs.
stepInfo.nextSteps[].stepPlaceholdersParsingKql Étape parent KQL qui extrait les valeurs de la réponse parent. La requête s’exécute sur source, où la data colonne contient chaque enregistrement parent sous forme de chaîne JSON brute. Chaque colonne projetée devient un espace réservé.
stepCollectorConfigs Étape parent Table de correspondance des définitions d’étapes enfants, indexée par les valeurs stepId déclarées dans stepInfo.nextSteps.
shouldJoinNestedData Étape enfant Contrôle la façon dont la réponse enfant est remise au flux. Défini sur false lorsque la réponse enfant contient l’enregistrement complet de sortie. Définissez sur true lorsque vous avez besoin de champs à partir des réponses parent et enfant dans la même ligne de sortie.
joinedDataStepName Étape enfant Nom de la colonne dynamic qui stocke la réponse enfant associée quand shouldJoinNestedData est true. Non utilisé quand shouldJoinNestedData est false.

Substitution d’espace réservé

Les marqueurs de substitution sont extraits à l'aide de stepPlaceholdersParsingKql et référencés avec la syntaxe $placeholderName$.

Par exemple, ce KQL crée un espace réservé nommé incidentId:

source
| project res = parse_json(data)
| project incidentId = res.incidentId

L’étape enfant peut ensuite faire référence à l’espace réservé sous la forme $incidentId$ :

"apiEndpoint": "https://api.example.com/incidents/$incidentId$/details"

La substitution des espaces réservés est prise en charge dans l’ensemble de la configuration de l’étape enfant, y compris dans la requête enfant apiEndpoint, headers, queryParameters et queryParametersTemplate.

Configurer les propriétés d’une requête enfant

Le bloc request dans une étape enfant prend en charge les propriétés de requête courantes utilisées pour l’interrogation d’API.

Les propriétés courantes d’une requête enfant incluent :

Propriété Description
apiEndpoint Point de terminaison de l’API enfant. Vous pouvez inclure des marqueurs de substitution tels que $incidentId$.
httpMethod Méthode HTTP pour la requête enfant, telle que GET ou POST.
headers En-têtes de requête pour l’appel d’API enfant. La substitution d’espace réservé est prise en charge.
queryParameters Paramètres de chaîne de requête pour l’appel à l’API enfant. La substitution d’espace réservé est prise en charge.
queryParametersTemplate Modèle utilisé pour les scénarios de charge utile de requête ou de corps de requête. La substitution d’espace réservé est prise en charge.
isPostPayloadJson Défini sur true le moment où la charge utile POST doit être envoyée en tant que JSON.
rateLimitQPS Nombre maximal de requêtes par seconde.
rateLimitConfig Configuration de limite de débit qui peut utiliser des en-têtes de limite de débit retournés par l’API.
retryCount Nombre de nouvelles tentatives. Valeur par défaut : 3. Plage prise en charge : 1 à 6.
timeoutInSeconds Délai d’expiration des requêtes, en secondes. Valeur par défaut : 20. Plage prise en charge : 1 à 180.

La requête parente détermine le temps d’interrogation. Configurez les propriétés de temps telles que queryWindowInMin, queryTimeFormat, startTimeAttributeName et endTimeAttributeName uniquement sur la requête parente. Les étapes enfants sont généralement pilotées par des valeurs d’espace réservé extraites de la réponse parente.

Configurer le parallélisme des requêtes enfants

maxParallelism contrôle le nombre d’appels enfants qui peuvent s’exécuter simultanément. La valeur par défaut est 15.

maxParallelism ne fait pas partie de la configuration du connecteur parent standard et ne peut pas être défini à l’étape parente. Étant donné que les étapes enfant sont transmises telles quelles, sans traduction des noms de champ, vous pouvez définir maxParallelism dans le bloc request d’une étape enfant si un ajustement est nécessaire.

"stepCollectorConfigs": {
  "fetchIncidentDetails": {
    "shouldJoinNestedData": false,
    "request": {
      "httpMethod": "GET",
      "apiEndpoint": "https://api.contoso.com/incidents/$incidentId$/details",
      "maxParallelism": 15
    },
    "response": {
      "eventsJsonPaths": [ "$" ],
      "format": "json"
    }
  }
}

Choisir s’il faut joindre des données parent et enfant

Utilisez shouldJoinNestedData pour contrôler la façon dont les réponses enfants sont remises au flux.

Utilisez shouldJoinNestedData: false.

Définissez shouldJoinNestedData sur false lorsque la réponse parent fournit uniquement les valeurs nécessaires à la requête enfant et que la réponse enfant contient l’enregistrement complet que vous souhaitez ingérer.

Par exemple, utilisez false quand :

  • L’appel parent retourne uniquement les ID d’incident.
  • L’appel enfant retourne les enregistrements d’incident complets.
  • Vous n’avez besoin de conserver aucun champ parent dans la ligne de destination.
"stepCollectorConfigs": {
  "fetchIncidentDetails": {
    "shouldJoinNestedData": false,
    "request": {
      "httpMethod": "GET",
      "apiEndpoint": "https://api.contoso.com/incidents/$incidentId$/details"
    },
    "response": {
      "eventsJsonPaths": [ "$" ],
      "format": "json"
    }
  }
}

Utilisez shouldJoinNestedData: true.

Définissez shouldJoinNestedData sur true lorsque vous avez besoin de champs provenant à la fois de la réponse parente et de la réponse enfant dans la même ligne de destination.

Par exemple, utilisez true quand :

  • L’appel parent retourne des champs d’alerte tels que l’ID d’alerte, la gravité et l’heure de détection.
  • L’appel enfant retourne des champs d’enrichissement tels que l’utilisateur affecté, l’adresse IP source ou la géolocalisation.
  • La transformation DCR doit mapper les champs parent et enfant dans la table de destination.

Quand shouldJoinNestedData est true, définissez joinedDataStepName le nom de la dynamic colonne qui stocke la réponse enfant.

"stepCollectorConfigs": {
  "fetchAlertEnrichment": {
    "shouldJoinNestedData": true,
    "joinedDataStepName": "enrichment",
    "request": {
      "httpMethod": "GET",
      "apiEndpoint": "https://api.contoso.com/alerts/$alertId$/enrichment"
    },
    "response": {
      "eventsJsonPaths": [ "$" ],
      "format": "json"
    }
  }
}

Exemple : requête GET enfant

Cet exemple utilise une API d’incident Contoso en deux étapes :

  1. La requête parente appelle GET /incidents et reçoit une liste d’identifiants d’incident.
  2. stepPlaceholdersParsingKql extrait incidentId de chaque enregistrement parent.
  3. La requête enfant effectue un appel à GET /incidents/$incidentId$/details pour chaque ID d’incident.
  4. Les réponses enfants sont envoyées dans le flux sous forme d’enregistrements plats.

Réponse parente

{
  "incidents": [
    { "incidentId": "INC-001" },
    { "incidentId": "INC-002" },
    { "incidentId": "INC-003" }
  ]
}

Réponse enfant

{
  "incidentId": "INC-001",
  "title": "Suspicious login attempt",
  "severity": "High",
  "status": "Active",
  "createdAt": "2026-05-30T14:22:00Z",
  "affectedUser": "alice@contoso.com",
  "sourceIp": "198.51.100.42"
}

Configuration de l’interrogation

{
  "kind": "RestApiPoller",
  "properties": {
    "connectorDefinitionName": "ContosoIncidentsConnector",
    "dcrConfig": {
      "dataCollectionEndpoint": "{{dataCollectionEndpoint}}",
      "dataCollectionRuleImmutableId": "{{dataCollectionRuleImmutableId}}",
      "streamName": "Custom-ContosoIncidents_CL"
    },
    "dataType": "ContosoIncidents_CL",
    "auth": {
      "type": "APIKey",
      "ApiKey": "{{apiKey}}",
      "ApiKeyName": "x-functions-key"
    },
    "request": {
      "apiEndpoint": "https://api.contoso.com/incidents",
      "httpMethod": "GET",
      "queryWindowInMin": 60,
      "queryTimeFormat": "yyyy-MM-ddTHH:mm:ssZ",
      "startTimeAttributeName": "startTime",
      "endTimeAttributeName": "endTime",
      "headers": {
        "Accept": "application/json"
      }
    },
    "response": {
      "eventsJsonPaths": [ "$.incidents" ],
      "format": "json"
    },
    "stepInfo": {
      "stepType": "Nested",
      "nextSteps": [
        {
          "stepId": "fetchIncidentDetails",
          "stepPlaceholdersParsingKql": "source | project res = parse_json(data) | project incidentId = res.incidentId"
        }
      ]
    },
    "stepCollectorConfigs": {
      "fetchIncidentDetails": {
        "shouldJoinNestedData": false,
        "request": {
          "httpMethod": "GET",
          "apiEndpoint": "https://api.contoso.com/incidents/$incidentId$/details",
          "headers": {
            "Accept": "application/json"
          },
          "retryCount": 3,
          "timeoutInSeconds": 60
        },
        "response": {
          "eventsJsonPaths": [ "$" ],
          "format": "json"
        }
      }
    }
  }
}

Exemple : requête POST enfant avec un corps au format JSON

Certaines API nécessitent l’envoi d’identificateurs de la réponse parente dans un corps POST au lieu du chemin d’URL ou de la chaîne de requête. Utilisez queryParametersTemplate avec isPostPayloadJson pour ce modèle.

Dans cet exemple, la réponse parente renvoie un incidentId, et la requête enfant envoie cette valeur dans le corps d’une requête POST au format JSON.

"stepCollectorConfigs": {
  "fetchIncidentDetails": {
    "shouldJoinNestedData": false,
    "request": {
      "httpMethod": "POST",
      "apiEndpoint": "https://api.contoso.com/incidents/details:batchGet",
      "headers": {
        "Accept": "application/json",
        "Content-Type": "application/json"
      },
      "queryParametersTemplate": "{'ids': ['$incidentId$']}",
      "isPostPayloadJson": true,
      "retryCount": 3,
      "timeoutInSeconds": 60
    },
    "response": {
      "eventsJsonPaths": [ "$.items" ],
      "format": "json"
    }
  }
}

Exemple : associer les données d’enrichissement de l’enregistrement enfant à l’enregistrement parent

Cet exemple utilise une API d’alerte Contoso où la réponse parente contient des champs qui doivent être conservés, et la réponse enfant contient des données d’enrichissement.

Réponse parente

{
  "alerts": [
    {
      "alertId": "ALT-001",
      "severity": "High",
      "detectedAt": "2026-05-30T14:22:00Z",
      "riskScore": 92
    }
  ]
}

Réponse enfant

{
  "alertId": "ALT-001",
  "affectedUser": "bob@contoso.com",
  "sourceIp": "198.51.100.77",
  "geolocation": "US/Virginia",
  "relatedIncidentId": "INC-042"
}

Configuration de l’interrogation

{
  "kind": "RestApiPoller",
  "properties": {
    "connectorDefinitionName": "ContosoAlertsConnector",
    "dcrConfig": {
      "dataCollectionEndpoint": "{{dataCollectionEndpoint}}",
      "dataCollectionRuleImmutableId": "{{dataCollectionRuleImmutableId}}",
      "streamName": "Custom-ContosoAlerts_CL"
    },
    "dataType": "ContosoAlerts_CL",
    "auth": {
      "type": "APIKey",
      "ApiKey": "{{apiKey}}",
      "ApiKeyName": "x-functions-key"
    },
    "request": {
      "apiEndpoint": "https://api.contoso.com/alerts",
      "httpMethod": "GET",
      "queryWindowInMin": 60,
      "queryTimeFormat": "yyyy-MM-ddTHH:mm:ssZ",
      "startTimeAttributeName": "startTime",
      "endTimeAttributeName": "endTime"
    },
    "response": {
      "eventsJsonPaths": [ "$.alerts" ],
      "format": "json"
    },
    "stepInfo": {
      "stepType": "Nested",
      "nextSteps": [
        {
          "stepId": "fetchAlertEnrichment",
          "stepPlaceholdersParsingKql": "source | project res = parse_json(data) | project alertId = res.alertId"
        }
      ]
    },
    "stepCollectorConfigs": {
      "fetchAlertEnrichment": {
        "shouldJoinNestedData": true,
        "joinedDataStepName": "enrichment",
        "request": {
          "httpMethod": "GET",
          "apiEndpoint": "https://api.contoso.com/alerts/$alertId$/enrichment"
        },
        "response": {
          "eventsJsonPaths": [ "$" ],
          "format": "json"
        }
      }
    }
  }
}

La transformation DCR peut ensuite projeter des champs à partir de l’enregistrement parent et de la réponse enfant jointe.

Par exemple:

source
| extend enrichment = todynamic(enrichment)
| project
    TimeGenerated = todatetime(detectedAt),
    AlertId = tostring(alertId),
    Severity = tostring(severity),
    RiskScore = toint(riskScore),
    AffectedUser = tostring(enrichment.affectedUser),
    SourceIp = tostring(enrichment.sourceIp),
    Geolocation = tostring(enrichment.geolocation),
    RelatedIncidentId = tostring(enrichment.relatedIncidentId)

Authentification par étape enfant et noms de champs de pagination

La traduction du nom de champ s’applique uniquement à l’étape parent. Chaque entrée de stepCollectorConfigs est transmise telle quelle et n’est pas remappée. Par conséquent, tout bloc auth ou paging à l’intérieur d’une étape enfant doit utiliser les noms de champ de l’étape enfant indiqués dans les tableaux suivants.

Champs d’authentification

Nom du champ d’étape parent Nom du champ d’étape enfant
type AuthType
apiKey APIKey
apiKeyName APIKeyName
redirectUri pour OAuth2 RedirectionEndpoint
isCredentialsInHeaders pour OAuth2 ou JWT IsClientSecretInHeader
grantType pour OAuth2 FlowName
queryParameters pour JWT TokenEndpointQueryParameters
isJsonRequest pour JWT IsTokenEndpointPostPayloadJson
userName / password paires clé-valeur pour jWT ou authentification de session UsernameAttributeNameet et UsernameAttributeValue / PasswordAttributeNamePasswordAttributeValue

Pour OAuth2, la FlowName valeur est également transformée. Par exemple, utilisez ClientCredentials plutôt que client_credentials, et AuthCode au lieu de authorization_code.

Champs de pagination

Nom du champ d’étape parent Nom du champ d’étape enfant
pageSizeParameterName pageSizeParaName

Tous les autres noms de champs de pagination et de réponse sont identiques pour les étapes parent et enfant.

Limits

L’interrogation imbriquée de l’API est soumise aux limites suivantes :

Limit Description
Étapes enfants dans stepCollectorConfigs Une configuration imbriquée prend en charge jusqu’à quatre entrées dans stepCollectorConfigs.
Entrées dans stepInfo.nextSteps stepInfo.nextSteps prend en charge jusqu’à trois entrées.
Références circulaires Les références d’étape circulaire ne sont pas prises en charge et sont rejetées pendant la validation.

Compléter le connecteur

Après avoir configuré les règles de connexion imbriquées RestApiPoller , complétez les composants restants du connecteur CCF :

  • Créez ou mettez à jour la table de destination.
  • Créez la DCR et la transformation.
  • Créez la définition de l’interface utilisateur du connecteur.
  • Empaqueter le connecteur dans un modèle de déploiement ARM.
  • Déployez et testez le connecteur.

Pour le processus de bout en bout, consultez Créer un connecteur sans code pour Microsoft Sentinel.