Creare connettori dati senza codice pull usando il polling dell'API annidata

Alcune API REST richiedono chiamate sequenziali, in cui la risposta da un endpoint fornisce l'input che deve essere passato a un altro endpoint. Il framework CCF (Codeless Connector Framework) Microsoft Sentinel supporta questo modello tramite il polling dell'API annidata per RestApiPoller i connettori.

Importante

Il polling dell'API annidata è attualmente in anteprima pubblica. I Termini Supplementari di Azure Preview includono termini legali più validi che si applicano alle funzionalità di Azure in beta, anteprima o in altro modo non ancora rilasciate in disponibilità generale.

Usare il polling dell'API annidata quando una chiamata API padre restituisce identificatori, cursori o altri valori richiesti da una o più chiamate API figlio. CCF estrae i valori necessari dalla risposta padre, li inserisce nella richiesta figlia e invia le risposte figlie alla tabella di destinazione configurata.

Configurare il polling dell'API annidata in un connettore di pull CCF definendo la logica di annidamento nelle regole di connessione RestApiPoller.

Per la procedura end-to-end per creare e pacchettizzare un connettore CCF, vedi Creare un connettore senza codice per Microsoft Sentinel. Puoi anche utilizzare l'estensione Microsoft Sentinel per Visual Studio Code per implementare e testare flussi di lavoro di polling API annidati. Per informazioni sulla configurazione e sull'utilizzo, vedere Build custom connectors with AI in Microsoft Sentinel. Per le proprietà standard RestApiPoller di richiesta, risposta, autenticazione, paging e DCR, vedere Informazioni di riferimento sulle regole di connessione del connettore dati RestApiPoller.

Annotazioni

Se si è un fornitore di software indipendente (ISV) che crea un'integrazione Microsoft Sentinel usando il framework del connettore codeless, il team di Microsoft App Assure può essere in grado di assistere. Per coinvolgere il team di App Assure, inviare un messaggio di posta elettronica a azuresentinelpartner@microsoft.com.

Prerequisiti

Prima di configurare il polling dell'API annidata, assicurarsi di comprendere:

  • Gli endpoint API che il connettore deve chiamare.
  • Quali valori di risposta della chiamata API padre sono richiesti dalla chiamata API figlio.
  • Schema di output per la tabella di destinazione.
  • Come creare un connettore CCF RestApiPoller standard.

Un connettore CCF completo include i componenti seguenti:

  • Tabella: Log Analytics tabella personalizzata in cui vengono archiviati i dati inseriti.
  • DCR: regola di raccolta dati che definisce la trasformazione di inserimento.
  • Interfaccia utente del connettore: la definizione del connettore dati visualizzata nell'hub contenuti di Microsoft Sentinel.
  • Regole di connessione dati: configurazione del connettore che recupera i dati dall'API di origine.

Il polling dell'API annidata è configurato nelle regole di connessione dati per un connettore RestApiPoller.

Che cos'è il polling dell'API annidata?

Il polling dell'API annidata è un modello di polling CCF che concatena le chiamate API REST. La prima chiamata API, denominata passaggio padre, restituisce i valori richiesti dalle chiamate API successive, denominati passaggi figlio.

Ad esempio, un'API potrebbe usare questo modello:

  1. GET /incidents restituisce un elenco di ID incidente.
  2. GET /incidents/{incidentId}/details restituisce il record completo dell'incidente per ogni ID.

Una singola chiamata API non restituisce i dati completi. Il connettore deve chiamare l'endpoint dell'elenco, estrarre ogni incidentId e quindi chiamare l'endpoint dei dettagli una volta per ogni ID.

Utilizza il polling API annidato quando:

  • Un endpoint di elenco restituisce gli ID delle risorse e un endpoint di dettaglio richiede ciascun ID nel percorso dell'URL o nella stringa di query.
  • Una risposta padre restituisce un cursore, un token di sessione, un ID di query o un ID di riferimento di cui una richiesta figlio ha bisogno.
  • Una risposta contiene una matrice di valori che devono essere passati singolarmente a un altro endpoint.
  • La risposta padre contiene i campi da mantenere e la risposta figlio aggiunge i dati di arricchimento che devono essere uniti nella stessa riga di output.

Se una singola chiamata API restituisce tutti i dati necessari, il polling dell'API annidato non è obbligatorio. Utilizzare la proprietà standard eventsJsonPaths per estrarre i record dalla risposta.

Funzionamento del polling dell'API annidata

Il polling dell'API annidata è configurato con queste sezioni:

Sezione Posizione Purpose
request Passaggio padre Definisce la richiesta API principale. Le proprietà della finestra temporale si configurano qui.
response Passaggio padre Definisce come i record vengono estratti dalla risposta principale.
stepInfo Passaggio padre Abilita il polling annidato e definisce i passaggi figlio da eseguire in seguito.
stepCollectorConfigs Passaggio padre Definisce ogni passaggio figlio, inclusa la gestione delle richieste e delle risposte figlio.
shouldJoinNestedData Passaggio figlio Definisce se la risposta del figlio sostituisce l'output del padre o viene aggiunta al record del padre.

Il flusso di polling annidato funziona come segue:

  1. La richiesta principale viene eseguita.
  2. La risposta principale viene suddivisa in record utilizzando response.eventsJsonPaths.
  3. stepPlaceholdersParsingKql estrae i valori dei segnaposto da ogni record principale.
  4. CCF sostituisce i segnaposto nella configurazione del passaggio figlio.
  5. CCF esegue le richieste figlio.
  6. La risposta figlio viene inviata come riga di output o unita al record padre, a seconda del valore di shouldJoinNestedData.

Modello di configurazione del polling annidato

Nell'esempio seguente viene illustrata la struttura di un connettore annidato RestApiPoller . Le proprietà CCF standard sono abbreviate con ....

{
  "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"
        }
      }
    }
  }
}

Proprietà di polling annidate

Proprietà Posizione Descrizione
stepInfo.stepType Passaggio padre Deve essere impostato su Nested per abilitare il polling API annidato.
stepInfo.nextSteps[].stepId Passaggio padre Nome del passaggio figlio. Questo valore deve corrispondere a una chiave in stepCollectorConfigs.
stepInfo.nextSteps[].stepPlaceholdersParsingKql Passaggio padre KQL che estrae i valori dalla risposta principale. La query viene eseguita su source, dove la colonna data contiene ogni record padre come stringa JSON non elaborata. Ogni colonna proiettata diventa un segnaposto.
stepCollectorConfigs Passaggio padre Mappa delle definizioni dei passaggi figlio, con chiave in base ai stepId valori dichiarati in stepInfo.nextSteps.
shouldJoinNestedData Passaggio figlio Controllare la modalità di recapito della risposta figlio al flusso. Impostare su false quando la risposta figlio contiene il record di output completo. Impostare su true quando sono necessari campi delle risposte padre e figlio nella stessa riga di output.
joinedDataStepName Passaggio figlio Nome della colonna dynamic che memorizza la risposta figlia combinata quando shouldJoinNestedData è true. Non usato quando shouldJoinNestedData è false.

Sostituzione dei segnaposto

I segnaposto vengono estratti tramite stepPlaceholdersParsingKql e a essi si fa riferimento con la sintassi $placeholderName$.

Ad esempio, questo KQL crea un segnaposto denominato incidentId:

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

Il passaggio figlio può quindi fare riferimento al segnaposto come segue: $incidentId$

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

La sostituzione dei segnaposto è supportata in tutta la configurazione del passaggio figlio, inclusa la richiesta figlio apiEndpoint, headers, queryParameters e queryParametersTemplate.

Configurare le proprietà della richiesta figlio

Il blocco request all'interno di un passaggio figlio supporta le proprietà comuni della richiesta usate per il polling delle API.

Le proprietà comuni delle richieste figlio includono:

Proprietà Descrizione
apiEndpoint L'endpoint figlio dell'API. È possibile includere segnaposto come $incidentId$.
httpMethod Il metodo HTTP per la richiesta figlia, ad esempio GET o POST.
headers Intestazioni di richiesta per la chiamata API figlio. La sostituzione segnaposto è supportata.
queryParameters Parametri della stringa di query per la chiamata all’API figlia. La sostituzione segnaposto è supportata.
queryParametersTemplate Modello usato negli scenari di payload del corpo della richiesta o della query. La sostituzione segnaposto è supportata.
isPostPayloadJson Impostare su true quando il payload POST deve essere inviato come JSON.
rateLimitQPS Numero massimo di richieste al secondo.
rateLimitConfig Configurazione del limite di frequenza che può usare le intestazioni del limite di frequenza restituite dall'API.
retryCount Numero di tentativi di ripetizione. Impostazione predefinita: 3. Intervallo supportato: 1 da a 6.
timeoutInSeconds Periodo di timeout, espresso in secondi, della richiesta. Impostazione predefinita: 20. Intervallo supportato: 1 da a 180.

La richiesta principale determina la finestra temporale di polling. Configurare le proprietà della finestra temporale, quali queryWindowInMin, queryTimeFormat, startTimeAttributeName e endTimeAttributeName, solo sulla richiesta padre. I passaggi secondari sono in genere basati sui valori di segnaposto estratti dalla risposta principale.

Configurare il parallelismo delle richieste figlie

maxParallelism determina quante chiamate figlie possono essere eseguite in parallelo. Il valore predefinito è 15.

maxParallelism non fa parte della configurazione del connettore padre standard e non può essere impostata nel passaggio padre. Poiché i passaggi secondari vengono trasferiti senza traduzione dei nomi dei campi, è possibile impostare maxParallelism all'interno del blocco request di un passaggio secondario, se è necessaria una modifica.

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

Scegliere se aggiungere dati padre e figlio

Usare shouldJoinNestedData per controllare il modo in cui le risposte figlio vengono recapitate al flusso.

Utilizzare shouldJoinNestedData: false.

Impostare shouldJoinNestedData su false quando la risposta padre fornisce solo i valori necessari per la richiesta figlio e la risposta figlio include il record completo che si desidera acquisire.

Ad esempio, usare false quando:

  • La chiamata principale restituisce solo gli ID degli incidenti.
  • La chiamata figlio restituisce i record completi degli eventi imprevisti.
  • Non è necessario conservare alcun campo padre nella riga di destinazione.
"stepCollectorConfigs": {
  "fetchIncidentDetails": {
    "shouldJoinNestedData": false,
    "request": {
      "httpMethod": "GET",
      "apiEndpoint": "https://api.contoso.com/incidents/$incidentId$/details"
    },
    "response": {
      "eventsJsonPaths": [ "$" ],
      "format": "json"
    }
  }
}

Utilizzare shouldJoinNestedData: true.

Impostare shouldJoinNestedData su true quando servono campi sia della risposta padre sia della risposta figlia nella stessa riga di destinazione.

Ad esempio, usare true quando:

  • La chiamata principale restituisce campi dell'avviso come l'ID dell'avviso, la gravità e l'ora di rilevamento.
  • La chiamata figlio restituisce campi di arricchimento come l'utente interessato, l'indirizzo IP di origine o la geolocalizzazione.
  • La trasformazione DCR deve mappare sia i campi padre che quelli figlio nella tabella di destinazione.

Quando shouldJoinNestedData è true, impostare joinedDataStepName sul nome della colonna dynamic che contiene la risposta figlio.

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

Esempio: richiesta figlio GET

Questo esempio utilizza un'API per gli incidenti Contoso in due passaggi:

  1. La richiesta principale chiama GET /incidents e riceve un elenco di ID degli incidenti.
  2. stepPlaceholdersParsingKql estrae incidentId da ogni record padre.
  3. La richiesta figlio invoca GET /incidents/$incidentId$/details una volta per ogni ID incidente.
  4. Le risposte figlio vengono inviate al flusso come record flat.

Risposta padre

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

Risposta figlio

{
  "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"
}

Configurazione del polling

{
  "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"
        }
      }
    }
  }
}

Esempio: richiesta figlio POST con corpo JSON

Alcune API richiedono l'invio di identificatori dalla risposta padre in un corpo POST anziché nel percorso URL o nella stringa di query. Usare queryParametersTemplate con isPostPayloadJson per questo modello.

In questo esempio, la risposta padre restituisce un incidentId e la richiesta figlio invia tale valore nel corpo JSON di una richiesta POST.

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

Esempio: associare i dati di arricchimento del record figlio al record padre

Questo esempio usa un'API di avviso Contoso in cui la risposta padre contiene campi che devono essere mantenuti e la risposta figlio contiene dati di arricchimento.

Risposta padre

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

Risposta figlio

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

Configurazione del polling

{
  "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 trasformazione DCR può quindi proiettare i campi sia dal record padre che dalla risposta figlio unita.

Per esempio:

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)

Autenticazione dei passaggi secondari e nomi dei campi di impaginazione

La traduzione dei nomi di campo si applica solo al passaggio padre. Ogni elemento in stepCollectorConfigs viene lasciato invariato e non viene rimappato. Di conseguenza, qualsiasi blocco auth o paging all'interno di una fase figlia deve usare i nomi dei campi della fase figlia mostrati nelle tabelle seguenti.

Campi di autenticazione

Nome del campo del passaggio principale Nome campo passaggio figlio
type AuthType
apiKey APIKey
apiKeyName APIKeyName
redirectUri per OAuth2 RedirectionEndpoint
isCredentialsInHeaders per OAuth2 o JWT IsClientSecretInHeader
grantType per OAuth2 FlowName
queryParameters per JWT TokenEndpointQueryParameters
isJsonRequest per JWT IsTokenEndpointPostPayloadJson
userName / password coppie chiave-valore per JWT o autenticazione della sessione UsernameAttributeName e UsernameAttributeValue / PasswordAttributeName e PasswordAttributeValue

Anche per OAuth2 il FlowName valore viene trasformato. Ad esempio, usare ClientCredentials anziché client_credentialse AuthCode anziché authorization_code.

Campi di paging

Nome del campo del passaggio principale Nome campo passaggio figlio
pageSizeParameterName pageSizeParaName

Tutti gli altri nomi dei campi di paging e risposta sono gli stessi per i passaggi padre e figlio.

Limits

Il polling dell'API annidata presenta i limiti seguenti:

Limit Descrizione
Passaggi secondari in stepCollectorConfigs Una configurazione annidata supporta fino a quattro voci in stepCollectorConfigs.
Voci in stepInfo.nextSteps stepInfo.nextSteps consente fino a tre voci.
Riferimenti circolari I riferimenti ai passaggi circolari non sono supportati e vengono rifiutati durante la convalida.

Completare il connettore

Dopo aver configurato le regole di connessione annidate RestApiPoller, completare i componenti rimanenti del connettore CCF:

  • Creare o aggiornare la tabella di destinazione.
  • Creare il DCR e la trasformazione.
  • Creare la definizione dell'interfaccia utente del connettore.
  • Inserisci il connettore in un modello di distribuzione ARM.
  • Distribuire e testare il connettore.

Per il processo end-to-end, vedere Creare un connettore senza codice per Microsoft Sentinel.