Dominare PowerShell Graph API: Intuizioni Facili da Seguire

Il Microsoft Graph API è un servizio che ti consente di leggere, modificare e gestire quasi ogni aspetto di Azure AD e Office 365 sotto un singolo punto di estremità REST API. In questo articolo, scopri come trasformare la tua API in PowerShell Graph API.

Prerequisiti

Se vuoi seguirmi in questo articolo, assicurati di soddisfare prima i seguenti criteri:

  • Eseguire Windows PowerShell 5.1 (Questa è la versione con cui ho testato. Altre versioni potrebbero funzionare ma non sono garantite)
  • Un tenant di Azure
  • Autenticato su Azure con un account con autorizzazioni di amministratore globale o autorizzazioni di registrazione dell’app sulla sottoscrizione e un amministratore globale per accettare le richieste di registrazione dell’app.

Creare un’identità dell’applicazione per il Microsoft Graph API

Per accedere al Microsoft Graph API, è necessaria prima un’identità per ottenere un token OAuth. Questo viene fatto principalmente con un’identità dell’applicazione che puoi creare nel portale di Azure. Puoi creare un’identità dell’applicazione tramite il portale di Azure. Per farlo:

  • Vai al Portale di Azure e vai su Azure Active Directory.
  • Fai clic su Registrazioni app sotto Gestisci nel menu a sinistra e fai clic sul pulsante Nuova registrazione.
Authenticating before creating the PowerShell Graph API
  • Inserisci un nome per la tua applicazione e fai clic su Registra.
  • Copia l’ID applicazione (Application Id) per un uso successivo.
Registering an application

Creare segreti per il Microsoft Graph API

Puoi autenticarti al Graph API con due metodi principali: AppId/Secret e autenticazione basata su certificato. Dovrai autenticarti quando ti connetti al Graph API con PowerShell.

Copriamo ora come autenticarsi con entrambi i metodi.

AppId/Secret

Un ID dell’applicazione/segreto è simile a un normale nome utente/password. L’ID dell’applicazione è composto da un GUID invece di un nome utente e la password è solo una stringa randomizzata.

Per creare un segreto, clicca su Certificati e segreti nel menu a sinistra e premi su Nuovo segreto client.

Creating a client secret

Inserisci una descrizione per il segreto e seleziona quando desideri che scada. Ora è solo una questione di chiedere il permesso per poter accedere ai dati che desideri.

Certificato

C’è la possibilità di creare un certificato autofirmato e caricare la sua chiave pubblica su Azure. Questo è il modo preferito e più sicuro di autenticarsi.

Prima dovrai generare un certificato autofirmato. Fortunatamente, questo è fatto facilmente con PowerShell.

# Il nome del tuo tenant (può anche essere qualcosa di più descrittivo)
$TenantName        = "contoso.onmicrosoft.com"

# Dove esportare il certificato senza la chiave privata
$CerOutputPath     = "C:\Temp\PowerShellGraphCert.cer"

# In quale archivio certificati vuoi che sia
$StoreLocation     = "Cert:\CurrentUser\My"

# Data di scadenza del nuovo certificato
$ExpirationDate    = (Get-Date).AddYears(2)


# Splat per leggibilità
$CreateCertificateSplat = @{
    FriendlyName      = "AzureApp"
    DnsName           = $TenantName
    CertStoreLocation = $StoreLocation
    NotAfter          = $ExpirationDate
    KeyExportPolicy   = "Exportable"
    KeySpec           = "Signature"
    Provider          = "Microsoft Enhanced RSA and AES Cryptographic Provider"
    HashAlgorithm     = "SHA256"
}

# Creare il certificato
$Certificate = New-SelfSignedCertificate @CreateCertificateSplat

# Ottenere il percorso del certificato
$CertificatePath = Join-Path -Path $StoreLocation -ChildPath $Certificate.Thumbprint

# Esportare il certificato senza la chiave privata
Export-Certificate -Cert $CertificatePath -FilePath $CerOutputPath | Out-Null

Ora, carica il certificato autofirmato che hai esportato in $CerOutputPath nella tua App Azure cliccando su Certificati e segreti nel menu a sinistra e premendo su Carica certificato.

Uploading a certificate

Aggiungere le autorizzazioni all’applicazione

Conferire le autorizzazioni adeguate all’applicazione è importante, non solo per la funzionalità della tua app, ma anche per la sicurezza. La conoscenza di questo, e (quasi) tutto il resto nell’API Microsoft Graph, può essere trovata nella documentazione.

Una volta che avrò configurato questo, raccoglierò tutti gli eventi di sicurezza dal mio tenant. Per poterlo fare, ho bisogno del permesso minimo di SecurityEvents.Read.All. Con questo posso raccogliere e intervenire sugli eventi di viaggio impossibile, sugli utenti che si connettono tramite VPN/TOR e simili.

Per aggiungere il permesso SecurityEvents.Read.All alla tua applicazione, fai clic su API Permissions e quindi su Add Permission. Questo ti presenterà non solo l’API di Graph, ma anche un sacco di altre applicazioni nell’Azure. La maggior parte di queste applicazioni è facile da collegare una volta che sai come connetterti all’API di Microsoft Graph.

Fai clic su Microsoft Graph > Application Permissions > Security Events e seleziona il SecurityEvents.Read.All. Dopo di che, premi il pulsante Add Permission.

Hai visto che la colonna Admin consent required è impostata su Yes su quel permesso? Questo significa che un amministratore del tenant deve approvare prima che il permesso venga aggiunto all’applicazione.

Tenant admin permission approval

Se sei un amministratore globale, premi il pulsante Grant admin consent for oppure chiedi a un amministratore globale di approvarlo. Chiedere il permesso all’utente anziché a un amministratore che imposta solo il permesso di lettura/scrittura è una parte importante dell’autenticazione OAuth. Ma questo ci consente di evitarlo per la maggior parte dei permessi in Microsoft Graph.

Probabilmente hai già visto questo su Facebook o Google: “Permetti all’applicazione X di accedere al tuo profilo?”

Granting admin consent

Ora sei pronto – effettuiamo il login e otteniamo alcuni dati!

Ottieni un token di accesso (ID applicazione e segreto)

Per fare ciò, dovremo inviare una richiesta per ottenere un token di accesso da un endpoint OAuth di Microsoft Graph. E nel corpo di quella richiesta dobbiamo fornire:

  • client_id – Il tuo ID applicazione – codificato in URL
  • client_secret – Il segreto della tua applicazione – codificato in URL
  • scope – Un URL codificato che specifica cosa si desidera accedere
  • grant_type – Il metodo di autenticazione che stai utilizzando

L’URL per l’endpoint è https://login.microsoftonline.com/<tenantname>/oauth2/v2.0/token. Puoi richiedere un token di accesso con PowerShell e la Graph API utilizzando il frammento di codice seguente.

# Definire AppId, segreto e ambito, nome del tenant e URL dell'endpoint
$AppId = '2d10909e-0396-49f2-ba2f-854b77c1e45b'
$AppSecret = 'abcdefghijklmnopqrstuv12345'
$Scope = "https://graph.microsoft.com/.default"
$TenantName = "contoso.onmicrosoft.com"

$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"

# Aggiungi System.Web per la codifica dell'URL
Add-Type -AssemblyName System.Web

# Crea il corpo
$Body = @{
    client_id = $AppId
	client_secret = $AppSecret
	scope = $Scope
	grant_type = 'client_credentials'
}

# Splat dei parametri per Invoke-Restmethod per un codice più pulito
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    # Crea una stringa unendo bodylist con '&'
    Body = $Body
    Uri = $Url
}

# Richiedi il token!
$Request = Invoke-RestMethod @PostSplat

Ottieni un token di accesso (Utilizzando un certificato)

L’autenticazione alla Microsoft Graph API con un certificato è un po’ diversa dal normale flusso AppId/Secret. Per ottenere un token di accesso usando un certificato, devi:

  1. Creare un’intestazione del token Web Java (JWT).
  2. Creare un payload JWT.
  3. Firmare l’intestazione JWT E il payload con il certificato autofirmato creato in precedenza. Questo creerà un token di accesso autogenerato utilizzato per richiedere un token di accesso al Grafico Microsoft.
  4. Creare un corpo della richiesta contenente:
  • client_id=<ID applicazione>
  • client_assertion=<il JWT>
  • client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • scope=<Ambito codificato URL>
  • grant_type=client_credentials
  1. Eseguire una richiesta POST con il corpo al punto di accesso oauth con Autorizzazione=<JWT> nell’intestazione.

Come fare ciò non era ovvio nella documentazione di Microsoft ma ecco lo script di PowerShell per realizzarlo:

$TenantName = "<your tenant name>.onmicrosoft.com"
$AppId = "<your application id"
$Certificate = Get-Item Cert:\CurrentUser\My\<self signed and uploaded cert thumbprint>
$Scope = "https://graph.microsoft.com/.default"

# Creare l'hash base64 del certificato
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())

# Creare il timestamp JWT per la scadenza
$StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
$JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds
$JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0)

# Creare il timestamp di inizio di validità del JWT
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)

# Creare l'intestazione JWT
$JWTHeader = @{
    alg = "RS256"
    typ = "JWT"
    # Utilizzare il CertificateBase64Hash e sostituire/rimuovere per corrispondere alla codifica web base64
    x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
}

# Creare il payload JWT
$JWTPayLoad = @{
    # Quale endpoint è autorizzato a utilizzare questo JWT
    aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"

    # Timestamp di scadenza
    exp = $JWTExpiration

    # Issuer = la tua applicazione
    iss = $AppId

    # ID JWT: GUID casuale
    jti = [guid]::NewGuid()

    # Non da utilizzare prima
    nbf = $NotBefore

    # Soggetto JWT
    sub = $AppId
}

# Convertire l'intestazione e il payload in base64
$JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))
$EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte)

$JWTPayLoadToByte =  [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))
$EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte)

# Unire l'intestazione e il payload con "." per creare un JWT valido (non firmato)
$JWT = $EncodedHeader + "." + $EncodedPayload

# Ottenere l'oggetto della chiave privata del tuo certificato
$PrivateKey = $Certificate.PrivateKey

# Definire l'algoritmo di firma RSA e hash
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256

# Creare una firma del JWT
$Signature = [Convert]::ToBase64String(
    $PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
) -replace '\+','-' -replace '/','_' -replace '='

# Unire la firma al JWT con "."
$JWT = $JWT + "." + $Signature

# Creare un hash con i parametri del corpo
$Body = @{
    client_id = $AppId
    client_assertion = $JWT
    client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
    scope = $Scope
    grant_type = "client_credentials"

}

$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"

# Utilizzare il JWT autogenerato come Autorizzazione
$Header = @{
    Authorization = "Bearer $JWT"
}

# Scomporre i parametri per Invoke-Restmethod per un codice più pulito
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    Body = $Body
    Uri = $Url
    Headers = $Header
}

$Request = Invoke-RestMethod @PostSplat

Comprensione dell’Output del Token di Accesso Richiesto

Una volta ottenuto un token di accesso tramite l’ID/segreto dell’applicazione o tramite certificato, dovresti vedere un oggetto con quattro proprietà.

  • token_type – Che tipo di token è
  • expires_in – Tempo in secondi per cui il token di accesso è valido
  • ext_expires_in – Simile a expires_in ma per la resilienza in caso di interruzione del servizio token
  • access_token – Per quello che siamo venuti

Successivamente creerai un’intestazione utilizzando token_type e access_token e inizierai a fare richieste con PowerShell all’API di Microsoft Graph.

Fare Richieste all’API di Microsoft Powershell Graph

Ora inizia a fare alcune richieste all’API.

Seguendo il nostro esempio, prima avrai bisogno dell’URL per elencare gli avvisi di sicurezza. Ricorda di consultare la documentazione dell’API di Microsoft Graph per vedere cosa è necessario.

In questo caso, hai bisogno di un’intestazione con Authorization=Bearer <access_token> e una richiesta GET verso il punto di estensione degli Avvisi di Graph API. Ecco come fare questo con PowerShell.

# Creare un'intestazione
$Header = @{
    Authorization = "$($Request.token_type) $($Request.access_token)"
}

$Uri = "https://graph.microsoft.com/v1.0/security/alerts"

# Recuperare tutti gli avvisi di sicurezza
$SecurityAlertsRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"

$SecurityAlerts = $SecurityAlertsRequest.Value

Se hai degli avvisi di sicurezza nella variabile $SecurityAlerts, dovrebbe assomigliare a qualcosa del genere:

$SecurityAlerts | select eventDateTime,Title

eventDateTime                title
-------------                -----
2019-08-05T17:59:47.6271981Z Atypical travel
2019-08-05T08:23:01.7325708Z Anonymous IP address
2019-08-05T08:23:55.5000456Z Anonymous IP address
2019-08-04T22:06:51.063797Z  Anonymous IP address
2019-08-04T21:56:10.981437Z  Anonymous IP address
2019-08-08T09:30:00Z         Creation of forwarding/redirect rule
2019-07-19T13:30:00Z         eDiscovery search started or exported
2019-07-19T08:00:00Z         eDiscovery search started or exported

Ispezionare un singolo avviso di sicurezza come JSON avrà questo aspetto:

"id":  "censored",
    "azureTenantId":  "censored",
    "azureSubscriptionId":  "censored",
    "riskScore":  null,
    "tags":  [

             ],
    "activityGroupName":  null,
    "assignedTo":  null,
    "category":  "AnonymousLogin",
    "closedDateTime":  null,
    "comments":  [

                 ],
    "confidence":  null,
    "createdDateTime":  "2019-08-08T09:46:59.65722253Z",
    "description":  "Sign-in from an anonymous IP address (e.g. Tor browser, anonymizer VPNs)",
    "detectionIds":  [

                     ],
    "eventDateTime":  "2019-08-08T09:46:59.65722253Z",
    "feedback":  null,
    "lastModifiedDateTime":  "2019-08-08T09:54:30.7256251Z",
    "recommendedActions":  [

                           ],
    "severity":  "medium",
    "sourceMaterials":  [

                        ],
    "status":  "newAlert",
    "title":  "Anonymous IP address",
    "vendorInformation":  {
                              "provider":  "IPC",
                              "providerVersion":  null,
                              "subProvider":  null,
                              "vendor":  "Microsoft"
                          },
    "cloudAppStates":  [

                       ],
    "fileStates":  [

                   ],
    "hostStates":  [

                   ],
    "historyStates":  [

                      ],
    "malwareStates":  [

                      ],
    "networkConnections":  [

                           ],
    "processes":  [

                  ],
    "registryKeyStates":  [

                          ],
    "triggers":  [

                 ],
    "userStates":  [
                       {
                           "aadUserId":  "censored",
                           "accountName":  "john.doe",
                           "domainName":  "contoso.com",
                           "emailRole":  "unknown",
                           "isVpn":  null,
                           "logonDateTime":  "2019-08-08T09:45:59.6174156Z",
                           "logonId":  null,
                           "logonIp":  "censored",
                           "logonLocation":  "Denver, Colorado, US",
                           "logonType":  null,
                           "onPremisesSecurityIdentifier":  null,
                           "riskScore":  null,
                           "userAccountType":  null,
                           "userPrincipalName":  "[email protected]"
                       }
                   ],
    "vulnerabilityStates":  [

                            ]
}

Comprensione e Gestione della Paginazione dell’Output dell’API

L’API Microsoft Graph ha un limite per funzione su quanti elementi restituirà. Questo limite è per funzione, ma diciamo che sono 1000 elementi. Ciò significa che puoi ottenere al massimo 1000 elementi nella tua richiesta.

Quando questo limite viene raggiunto, verrà utilizzata la paginazione per consegnare il resto degli elementi. Ciò avviene aggiungendo la proprietà @odata.nextLink alla risposta della tua richiesta. @odata.nextLink contiene un URL che puoi chiamare per ottenere la pagina successiva della tua richiesta.

Puoi leggere tutti gli elementi controllando questa proprietà e utilizzando un loop:

$Uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns"

# Recupera tutti gli avvisi di sicurezza
$AuditLogRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"

$AuditLogs = @()
$AuditLogs+=$AuditLogRequest.value

while($AuditLogRequest.'@odata.nextLink' -ne $null) {
    $AuditLogRequest += Invoke-RestMethod -Uri $AuditLogRequest.'@odata.nextLink' -Headers $Header -Method Get -ContentType "application/json"
}

Conclusione

Dopo aver imparato come autenticarsi all’API Graph, è abbastanza facile raccogliere dati da essa. È un servizio potente che viene utilizzato molto meno di quanto dovrebbe.

Fortunatamente ci sono già molti moduli disponibili per utilizzare l’API Microsoft Graph, ma per soddisfare le tue esigenze potresti dover creare il tuo modulo. Utilizzando le competenze apprese in questo articolo, dovresti essere sulla buona strada.

Per ulteriori informazioni sul controllo dell’accesso degli ospiti in Office 365, ho scritto un articolo dettagliato sul mio blog che ti incoraggio a controllare.

Source:
https://adamtheautomator.com/powershell-graph-api/