Introduzione ai grafici personalizzati in Microsoft Sentinel (anteprima)

I grafici personalizzati in Microsoft Sentinel consentono a ricercatori e analisti della sicurezza di creare rappresentazioni di grafi personalizzate dei dati di sicurezza. Creando grafici personalizzati, è possibile modellare modelli di attacco specifici, analizzare le minacce ed eseguire algoritmi di grafo avanzati per individuare relazioni nascoste all'interno dell'ambiente digitale. Questa guida illustra i passaggi per creare e gestire grafici personalizzati usando i notebook di Jupyter nell'estensione Microsoft Sentinel Visual Studio Code.

Questo articolo è incentrato sulla creazione manuale di grafici personalizzati usando il codice. Per un'esperienza basata sull'intelligenza artificiale, vedere Creazione di grafici personalizzati assistiti dall'intelligenza artificiale in Microsoft Sentinel.

Prerequisiti

Per creare grafici personalizzati in Microsoft Sentinel, è necessario quanto segue:

Abilitare il connettore Microsoft Entra ID per inserire le tabelle degli asset Microsoft Entra usate nel codice di esempio di questo articolo. Per altre informazioni, vedere Inserimento di dati di asset nel data lake Microsoft Sentinel.

Autorizzazioni

Per interagire con i grafici personalizzati, sono necessarie le autorizzazioni XDR seguenti in Sentinel data lake. Nella tabella seguente sono elencati i requisiti di autorizzazione per le operazioni a grafo comuni:

Operazione sul grafo Autorizzazioni necessarie
Modellare e compilare un grafico notebook Usa un ruolo RBAC unificato personalizzato di Microsoft Defender XDR con autorizzazioni dati (gestione) per la raccolta dati di Microsoft Sentinel.
Rendere persistente un grafico nel tenant Usare uno dei ruoli di Microsoft Entra ID seguenti:
Operatore della sicurezza
Amministratore della sicurezza
Amministratore globale
Interrogare un grafo persistente Usare un ruolo RBAC unificato personalizzato di Microsoft Defender XDR con autorizzazioni elementi di base dei dati di sicurezza (lettura) per la raccolta dati di Microsoft Sentinel.

Importante

È necessario disporre delle autorizzazioni per leggere i dati usati nel grafico. Se non si ha accesso a un set di dati specifico, tali dati non verranno inclusi nel grafico. Per creare un grafico, non è necessario essere limitati da un ambito Sentinel. Un utente con autorizzazioni limitate non può creare un grafico personalizzato.

I ruoli di Microsoft Entra ID forniscono un ampio accesso a tutte le aree di lavoro nel data lake. Per altre informazioni, vedere Ruoli e autorizzazioni in Microsoft Sentinel.

Installare Visual Studio Code e l'estensione Microsoft Sentinel

Creare grafici personalizzati usando i notebook di Jupyter nell'estensione Microsoft Sentinel Visual Studio Code. Per altre informazioni, vedere Installare Visual Studio Code e l'estensione Microsoft Sentinel

Creare un grafico personalizzato

Per creare e usare grafici personalizzati, completare la procedura seguente:

  1. Modellare un grafico personalizzato
  2. Rendere persistente il grafico personalizzato pianificando un processo grafico
  3. Visualizzare e gestire grafici personalizzati

Modellare un grafico personalizzato

Creare un grafico personalizzato usando un notebook di Jupyter nell'estensione Microsoft Sentinel Visual Studio Code.

La procedura seguente illustra come creare il primo grafico personalizzato usando un notebook di esempio.

Configurare il notebook e connettersi al data lake

Usare la procedura seguente per creare un notebook e connettersi al data lake Microsoft Sentinel.

  1. In Visual Studio Code con l'estensione Microsoft Sentinel installata selezionare l'icona Microsoft Sentinel nel menu a sinistra.

  2. Selezionare Accedi per visualizzare i grafici

  3. Viene visualizzata una finestra di dialogo con il testo L'estensione 'Microsoft Sentinel' vuole accedere usando Microsoft. Selezionare Consenti per accedere.

  4. Accedere con le credenziali.

  5. Dopo aver eseguito l'accesso, selezionare + quindi Crea nuovo notebook.

  6. Assegnare un nome al file notebook e salvarlo in un percorso appropriato nell'area di lavoro.

    Screenshot della pagina di accesso per i grafici in Visual Studio Code.

  7. Selezionare Seleziona kernel in alto a destra nella finestra del notebook per selezionare un pool di calcolo spark.

  8. Selezionare Microsoft Sentinel, quindi selezionare uno dei pool spark disponibili.

    Screenshot della pagina di selezione del kernel in Visual Studio Code.

    Consiglio

    È possibile usare le richieste di intelligenza artificiale per creare un notebook grafico personalizzato. Per altre informazioni, vedere Creazione di grafici personalizzati assistiti dall'intelligenza artificiale in Microsoft Sentinel.

  9. Eseguire una cella a selezionando l'icona del triangolo della cella di esecuzione a sinistra della cella. La prima volta che si esegue una cella, potrebbe essere richiesto di selezionare un kernel se non ne è già stata selezionata una.

    La prima volta che si esegue una cella, l'avvio della sessione Spark richiede circa cinque minuti.

    Screenshot che mostra l'esecuzione della prima cella in Visual Studio Code.

Creare un grafico

L'esempio seguente genera un grafico per esplorare le appartenenze ai gruppi di Microsoft Entra e comprendere le relazioni tra gruppi annidati. Il codice di esempio ti aiuta a iniziare con un semplice caso d'uso per comprendere la funzionalità di grafo personalizzato e sfruttare la potenza dell'attraversamento del grafo nelle tue indagini. È possibile creare un grafico da qualsiasi tabella disponibile nel data lake Sentinel.

  1. Connettiti al tuo workspace e leggi le tabelle delle risorse di Entra per iniziare a costruire il grafo.

    from pyspark.sql import functions as F
    from sentinel_lake.providers import MicrosoftSentinelProvider
    
    lake_provider = MicrosoftSentinelProvider(spark=spark)
    
    # Use the "System tables" workspace which contains the Entra* Assets tables
    # If you are data is in a different workspace, update this variable accordingly and ensure the tables are present
    LOG_ANALYTICS_WORKSPACE = "System tables"
    
    # Dynamically get the latest snapshot time from EntraUsers
    snapshot_time = (
        lake_provider.read_table("EntraUsers", LOG_ANALYTICS_WORKSPACE)
        .df.agg(F.max("_SnapshotTime").alias("max_snapshot"))
        .collect()[0]["max_snapshot"]
        .strftime("%Y-%m-%dT%H:%M:%SZ")
    )
    print(f"Using snapshot_time: {snapshot_time}")
    
    snapshot_filter = (F.col("_SnapshotTime") == F.lit(snapshot_time).cast("timestamp"))
    
    # Load EntraMembers - edges: group contains user/group/servicePrincipal
    df_members = (
        lake_provider.read_table("EntraMembers", LOG_ANALYTICS_WORKSPACE)
        .filter(
            snapshot_filter
            & (F.col("sourceType") == "group")
            & (F.col("targetType").isin("user", "group", "servicePrincipal"))
        )
    )
    
    # Load EntraGroups - nodes
    df_groups = (
        lake_provider.read_table("EntraGroups", LOG_ANALYTICS_WORKSPACE)
        .filter(snapshot_filter)
        .select("id", "displayName", "mailEnabled")
    )
    
    # Load EntraUsers - nodes
    df_users = (
        lake_provider.read_table("EntraUsers", LOG_ANALYTICS_WORKSPACE)
        .filter(snapshot_filter)
        .select("id", "accountEnabled", "displayName", "department",
                "lastPasswordChangeDateTime", "userPrincipalName", "usageLocation")
    )
    
    # Load EntraServicePrincipals - nodes
    df_service_principals = (
        lake_provider.read_table("EntraServicePrincipals", LOG_ANALYTICS_WORKSPACE)
        .filter(snapshot_filter)
        .select("accountEnabled", "id", "displayName", "servicePrincipalType",
                "tenantId", "organizationId")
    )
    
    # Fix for Spark 3.x Parquet datetime rebase issue. Required when reading Parquet files
    # written by Spark 2.x which used the Julian calendar, whereas Spark 3.x uses Proleptic
    # Gregorian. Without these settings, timestamp columns (e.g. lastPasswordChangeDateTime)
    # may throw errors or return incorrect values. Safe to remove if all data was written by
    # Spark 3.x (typical for current Fabric/Sentinel environments).
    spark.conf.set("spark.sql.parquet.datetimeRebaseModeInRead", "CORRECTED")
    spark.conf.set("spark.sql.parquet.datetimeRebaseModeInWrite", "CORRECTED")
    spark.conf.set("spark.sql.parquet.int96RebaseModeInRead", "CORRECTED")
    spark.conf.set("spark.sql.parquet.int96RebaseModeInWrite", "CORRECTED")
    
  2. Preparare il nodo e i dataframe perimetrali necessari per la compilazione del grafico

    # ============================================================
    # NODE PREPARATION
    # ============================================================
    
    # EntraUser nodes - keyed by user id
    user_nodes = (
        df_users.df
        .select(
            F.col("id"),
            F.col("displayName"),
            F.col("accountEnabled"),
            F.col("department"),
            F.col("lastPasswordChangeDateTime"),
            F.col("userPrincipalName"),
            F.col("usageLocation")
        )
    )
    
    # EntraGroup nodes - keyed by group id
    group_nodes = (
        df_groups.df
        .select(
            F.col("id"),
            F.col("displayName"),
            F.col("mailEnabled")
        )
    )
    
    # EntraServicePrincipal nodes - keyed by SP id
    sp_nodes = (
        df_service_principals.df
        .select(
            F.col("id"),
            F.col("displayName"),
            F.col("accountEnabled"),
            F.col("servicePrincipalType"),
            F.col("tenantId"),
            F.col("organizationId")
        )
    )
    
    # ============================================================
    # EDGE PREPARATION
    # ============================================================
    
    # Edge: EntraGroup --Contains--> EntraUser
    edge_group_contains_user = (
        df_members.df
        .filter(F.col("targetType") == "user")
        .select(
            F.col("sourceId").alias("SourceGroupId"),
            F.col("targetId").alias("TargetUserId")
        )
        .distinct()
        .withColumn("EdgeKey", F.concat_ws("_", F.col("SourceGroupId"), F.col("TargetUserId")))
    )
    
    # Edge: EntraGroup --Contains--> EntraGroup (nested groups)
    edge_group_contains_group = (
        df_members.df
        .filter(F.col("targetType") == "group")
        .select(
            F.col("sourceId").alias("SourceGroupId"),
            F.col("targetId").alias("TargetGroupId")
        )
        .distinct()
        .withColumn("EdgeKey", F.concat_ws("_", F.col("SourceGroupId"), F.col("TargetGroupId")))
    )
    
    # Edge: EntraGroup --Contains--> EntraServicePrincipal
    edge_group_contains_sp = (
        df_members.df
        .filter(F.col("targetType") == "servicePrincipal")
        .select(
            F.col("sourceId").alias("SourceGroupId"),
            F.col("targetId").alias("TargetSPId")
        )
        .distinct()
        .withColumn("EdgeKey", F.concat_ws("_", F.col("SourceGroupId"), F.col("TargetSPId")))
    )
    
  3. Definisci lo schema del grafo e associalo ai DataFrame dei nodi e degli archi che hai preparato in precedenza durante questa procedura

    from sentinel_graph import GraphSpecBuilder, Graph
    
    # Define the graph schema 
    
    entra_group_graph_spec = (
        GraphSpecBuilder.start()
    
        # === NODES ===
    
        .add_node("EntraUser")
        .from_dataframe(user_nodes)  # Native Spark DataFrame (from .df + .select + .distinct)
        .with_columns(
            "id", "displayName", "accountEnabled",
            "department", "lastPasswordChangeDateTime", "userPrincipalName", "usageLocation",
            key="id", display="displayName"
        )
    
        .add_node("EntraGroup")
        .from_dataframe(group_nodes)  # Native Spark DataFrame
        .with_columns(
            "id", "displayName", "mailEnabled",
            key="id", display="displayName"
        )
    
        .add_node("EntraServicePrincipal")
        .from_dataframe(sp_nodes)  # Native Spark DataFrame
        .with_columns(
            "id", "displayName", "accountEnabled",
            "servicePrincipalType", "tenantId", "organizationId",
            key="id", display="displayName"
        )
    
        # === EDGES ===
    
        # EntraGroup --ContainsUser--> EntraUser
        .add_edge("ContainsUser")
        .from_dataframe(edge_group_contains_user)  # Native Spark DataFrame
        .source(id_column="SourceGroupId", node_type="EntraGroup")
        .target(id_column="TargetUserId", node_type="EntraUser")
        .with_columns("SourceGroupId", "TargetUserId", "EdgeKey",
                      key="EdgeKey", display="EdgeKey")
    
        # EntraGroup --ContainsGroup--> EntraGroup (nested groups)
        .add_edge("ContainsGroup")
        .from_dataframe(edge_group_contains_group)  # Native Spark DataFrame
        .source(id_column="SourceGroupId", node_type="EntraGroup")
        .target(id_column="TargetGroupId", node_type="EntraGroup")
        .with_columns("SourceGroupId", "TargetGroupId", "EdgeKey",
                      key="EdgeKey", display="EdgeKey")
    
        # EntraGroup --ContainsServicePrincipal--> EntraServicePrincipal
        .add_edge("ContainsServicePrincipal")
        .from_dataframe(edge_group_contains_sp)  # Native Spark DataFrame
        .source(id_column="SourceGroupId", node_type="EntraGroup")
        .target(id_column="TargetSPId", node_type="EntraServicePrincipal")
        .with_columns("SourceGroupId", "TargetSPId", "EdgeKey",
                      key="EdgeKey", display="EdgeKey")
    
    ).done()
    
  4. Convalidare lo schema del grafo

    # Check the schema of the graph spec to ensure it's correct
    entra_group_graph_spec.show_schema()
    
  5. Compilare il grafico, inclusa la preparazione dei dati e la pubblicazione del grafico

    # Build the graph from the spec - this will validate the spec and prepare it for querying
    # Alter options is to use Graph.prepare() to prepare the graph nodes and edges in the lake
    # and then use Graph.publish() to create the graph. You would typically call prepare() and publish()
    # seperately to understand the cost of Graph API calls that are triggeterd by Graph.publish()
    # see https://learn.microsoft.com/azure/sentinel/billing?tabs=simplified%2Ccommitment-tiers
    intra_group_graph = Graph.build(entra_group_graph_spec)
    

    Nota

    I grafici creati durante le sessioni interattive del notebook vengono rimossi quando la sessione del notebook viene chiusa. Per rendere persistente il grafico per il riutilizzo e la condivisione, vedere Rendere persistente il grafico personalizzato

Ora hai creato un grafo nel notebook.

Per visualizzare una rappresentazione visiva del grafico, in una nuova cella incollare ed eseguire la query GQL (Graph Query Language) di esempio seguente. Questa query analizza le appartenenze annidate ai gruppi di Microsoft Entra fino a otto livelli di profondità e visualizza le relazioni risultanti:

# Query 1: Find nested group relationships nexting up to 8 levels deep
# Update the Entra Group name that you want to traverse from
query_nested_groups = """
MATCH p=(g1:EntraGroup)-[cg]->{1,8}(g2)
WHERE g1.displayName = 'tmplevel3'
RETURN *
"""
intra_group_graph.query(query_nested_groups).show()

Questo codice esegue una query GQL (Graph Query Language) di esempio per recuperare tutte le appartenenze a gruppi annidati fino a 8 livelli in profondità Il grafico risultante viene visualizzato nell'output

Screenshot che mostra la visualizzazione di un grafico in Visual Studio Code.

Rendere persistente il grafico personalizzato

Dopo aver creato il codice del grafo nel notebook, è possibile eseguire il notebook in una sessione interattiva o pianificare un processo a grafo. I grafici creati durante la sessione interattiva del notebook sono temporanei e sono disponibili solo nel contesto della sessione notebook. Per salvare il grafico e condividerlo con il tuo team, pianifica un processo del grafico per rigenerarlo frequentemente. Una volta salvato il grafico, è possibile accedervi da: l'esperienza Grafo nel portale Microsoft Defender, in Sentinel, i notebook di Visual Studio Code e le API di query di Graph.

  1. Nel notebook del grafo selezionare Crea processo pianificato, quindi selezionare Crea un processo grafico.

    Una schermata con il pulsante Crea attività pianificata in un notebook per grafi.

  2. Nel modulo Crea processo grafico immettere il nome e la descrizione del grafo e verificare che il notebook del grafo corretto sia incluso in Percorso.

  3. Per compilare il grafico senza configurare una pianificazione di aggiornamento, selezionare Su richiesta nella sezione Pianificazione e quindi selezionare Invia per creare il grafico.

    Nota

    I grafici creati con la pianificazione su richiesta hanno una conservazione predefinita di 30 giorni e vengono eliminati alla scadenza.

  4. Per compilare il grafico in cui i dati del grafo vengono aggiornati regolarmente, selezionare Pianificato nella sezione Pianificazione .

    1. Seleziona una frequenza di ripetizione per il processo. È possibile scegliere tra Minuti, Orari, Settimanali, Giornalieri o Mensili.

    2. Vengono visualizzate altre opzioni per configurare la pianificazione, a seconda della frequenza selezionata. Ad esempio, il giorno della settimana, l'ora del giorno o il giorno del mese.

    3. Seleziona un'Ora di inizio per avviare la pianificazione.

    4. Seleziona un orario di Termina il per interrompere l'esecuzione della pianificazione. Se non si desidera impostare un orario di fine per la programmazione, selezionare Imposta il processo per l'esecuzione indefinita. Le date e gli orari sono nel tuo fuso orario.

    5. Selezionare Invia per salvare la configurazione del processo e pubblicare il processo. Il processo di compilazione del grafico viene avviato nel tenant. Visualizzare il grafico appena creato e il relativo stato più recente nell'estensione Sentinel.

    Una schermata della pagina di creazione del processo del grafo.

Visualizzare e gestire grafici personalizzati

Dopo aver creato un processo grafico, è possibile visualizzare e gestire il grafico nel tenant dall'estensione Microsoft Sentinel in Visual Studio Code.

  1. Nell'elenco dei grafici selezionare il grafo materializzato per visualizzarne i dettagli.

  2. Selezionare la scheda Dettagli processo per visualizzare lo stato del processo grafico, inclusi l'ultima esecuzione, la fase di esecuzione successiva e gli eventuali errori rilevati durante il processo di compilazione.

  3. Selezionare Esegui ora per attivare manualmente una compilazione a grafo al di fuori degli orari pianificati. Lo stato viene modificato in Accodato, quindi "In corso" durante la compilazione del grafico.

    Screenshot che mostra la scheda dei dettagli del processo grafico in Visual Studio Code.

  4. Al termine della compilazione del grafico, lo stato viene aggiornato in Pronto. Selezionare la scheda Dettagli grafico per visualizzare le informazioni sul grafico.

    Screenshot della scheda dei dettagli del grafico.

  5. È ora possibile interrogare e visualizzare il proprio grafo dalla visualizzazione del grafo in Microsoft Sentinel nel portale Defender. Per altre informazioni, vedere Visualizzare i grafici nel grafico di Microsoft Sentinel (anteprima).

Aggiornare grafici personalizzati

Dopo aver aggiornato il codice del grafo del notebook, esegui il notebook localmente per validare le modifiche prima di aggiornare un job del grafo.

Modificare i dettagli del grafico personalizzato esistente

Usare questo flusso di lavoro per modificare un processo grafico esistente.

  1. Nel pannello Graph di Microsoft Sentinel in Visual Studio Code, seleziona Modifica processo Graph per il grafico che vuoi aggiornare, quindi vai a Nome del grafico>Dettagli processo.

  2. Aggiornare i campi da modificare:

    • Nome grafico (facoltativo). L'aggiornamento del nome di un grafo crea un nuovo grafo nel proprio tenant. Il grafico esistente rimane invariato.
    • Descrizione (facoltativa). L'aggiornamento della descrizione sostituisce la descrizione corrente del grafico e ricompila il grafico.
    • Configurazione del cluster (facoltativo). L'aggiornamento della configurazione del cluster modifica le dimensioni di calcolo e ricompila il grafico.
    • Frequenza del processo (facoltativo). L'aggiornamento della frequenza del processo modifica la pianificazione ricorrente e ricompila il grafico.

Screenshot che mostra il flusso di lavoro Edit graph job (Modifica processo grafico) nel pannello Microsoft Sentinel Graph in Visual Studio Code.

Sovrascrivere un grafo personalizzato esistente

Usare questo flusso di lavoro quando si vuole ricreare un grafo e sovrascrivere un grafo esistente con lo stesso nome.

  1. Aprire il notebook aggiornato.

  2. Selezionare Crea processo pianificato nell'interfaccia notebook di Sentinel, quindi selezionare Crea un processo grafico.

  3. Nel modulo Crea grafico specificare un nome del grafo.

    • Se si specifica un nuovo nome di grafo, viene creato un nuovo grafico nel tenant e il grafico esistente rimane disponibile.
    • Se si riutilizza un nome di grafo esistente per il tenant, il grafico esistente viene sovrascritto dopo la conferma. I nomi dei gragrafi sono univoci e non sono supportati nomi di gragrafi duplicati.
  4. Verificare che Path faccia riferimento al notebook aggiornato.

  5. Aggiornare la descrizione per il grafico.

  6. Selezionare Configurazione del cluster e Frequenza dei processi, come per la creazione di un nuovo grafico.

  7. Aggiornare le informazioni sulla pianificazione , se necessario.

  8. Aggiornare di nuovo il campo Descrizione in base alle esigenze.

  9. Selezionare Invia per ricreare il grafico nel tenant.

Screenshot che mostra il modulo Crea grafico usato per sovrascrivere un grafico personalizzato esistente in Visual Studio Code.