Dominando o PowerShell Graph API: Insights Fáceis de Seguir

A API Microsoft Graph é um serviço que permite ler, modificar e gerir quase todos os aspectos do Azure AD e do Office 365 sob um único endpoint da API REST. Neste artigo, aprenda a transformar sua API em PowerShell Graph API.

Pré-requisitos

Se você quiser acompanhar comigo neste artigo, certifique-se de que primeiro cumpra os seguintes critérios:

  • Rodando Windows PowerShell 5.1 (Esta é a versão que testei. Outras versões podem funcionar, mas não são garantidas)
  • Um inquilino Azure
  • Autenticado no Azure com uma conta com permissões de administrador global ou permissões de registro de aplicativos na assinatura e um administrador global para aceitar seus pedidos de registro de aplicativos.

Criando uma Identidade de Aplicativo para a API Microsoft Graph

Para acessar a API Microsoft Graph, você primeiro precisa de uma identidade para obter um token OAuth. Isso é feito principalmente com uma identidade de aplicativo que você pode criar no Portal Azure. Você pode criar uma identidade de aplicativo através do portal Azure. Para fazer isso:

  • Vá até o Portal Azure e vá para Azure Active Directory.
  • Clique em Registros de aplicativos em Gerenciar no menu à esquerda e clique no botão Novo registro.
Authenticating before creating the PowerShell Graph API
  • Insira um nome para sua aplicação e clique em Registrar.
  • Copie o ID do aplicativo para uso posterior.
Registering an application

Criando segredos para a API Microsoft Graph

Você pode autenticar no Graph API usando dois métodos principais: AppId/Secret e autenticação baseada em certificado. Você precisará autenticar ao conectar-se ao Graph API com o PowerShell.

Vamos cobrir como autenticar com ambos os métodos.

AppId/Secret

Um ID/segredo de aplicativo é como um nome de usuário/senha regular. O ID do aplicativo consiste em um GUID em vez de um nome de usuário e a senha é apenas uma sequência aleatória.

Para criar um segredo, clique em Certificados e segredos no menu à esquerda e pressione Novo segredo do cliente.

Creating a client secret

Insira uma descrição para o segredo e selecione quando deseja que ele expire. Agora é apenas uma questão de solicitar permissão para acessar os dados desejados.

Certificado

Existe a possibilidade de criar um certificado autoassinado e fazer upload de sua chave pública no Azure. Esta é a maneira preferida e mais segura de autenticação.

Primeiro, você precisará gerar um certificado autoassinado. Felizmente, isso é feito facilmente com o PowerShell.

# Nome do inquilino (pode ser algo mais descritivo também)
$TenantName        = "contoso.onmicrosoft.com"

# Onde exportar o certificado sem a chave privada
$CerOutputPath     = "C:\Temp\PowerShellGraphCert.cer"

# Em que armazenamento de certificados você deseja que ele esteja
$StoreLocation     = "Cert:\CurrentUser\My"

# Data de expiração do novo certificado
$ExpirationDate    = (Get-Date).AddYears(2)


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

# Criar certificado
$Certificate = New-SelfSignedCertificate @CreateCertificateSplat

# Obter caminho do certificado
$CertificatePath = Join-Path -Path $StoreLocation -ChildPath $Certificate.Thumbprint

# Exportar certificado sem chave privada
Export-Certificate -Cert $CertificatePath -FilePath $CerOutputPath | Out-Null

Agora, faça o upload do certificado autoassinado que você exportou para $CerOutputPath para o seu Aplicativo Azure clicando em Certificados e segredos no menu à esquerda e pressionando Fazer upload do certificado.

Uploading a certificate

Adicionando permissões ao aplicativo

Dar as permissões adequadas ao aplicativo é importante – não apenas para a funcionalidade do seu aplicativo, mas também para a segurança. O conhecimento disso e (quase) tudo o mais na API do Microsoft Graph pode ser encontrado na documentação.

Assim que eu conseguir configurar isso, vou reunir todos os eventos de segurança do meu locatário. Para poder fazer isso, eu preciso de permissão mínima de SecurityEvents.Read.All. Com isso, posso reunir e tomar medidas sobre eventos de Viagem Impossível, usuários conectados via VPN/TOR e assim por diante.

Para adicionar o SecurityEvents.Read.All à sua aplicação – clique em Permissões de API e depois em Adicionar Permissão. Isso apresentará não apenas a API do Microsoft Graph, mas também uma tonelada de outras aplicações no Azure. A maioria dessas aplicações é fácil de conectar assim que você souber como se conectar à API do Microsoft Graph.

Clique em Microsoft Graph > Permissões de Aplicativos > Eventos de Segurança e marque o SecurityEvents.Read.All. Depois disso, pressione o botão Adicionar Permissão.

Você viu que a coluna Consentimento do Admin Necessário foi definida como Sim nessa permissão? Isso significa que um administrador do locatário precisa aprovar antes que a permissão seja adicionada à aplicação.

Tenant admin permission approval

Se você é um administrador global, pressione o Dar consentimento do administrador para ou peça a um Administrador Global para aprovar. Pedir permissão ao usuário em vez de um administrador apenas definir permissão de leitura/escrita é uma grande parte da autenticação OAuth. Mas isso nos permite contorná-lo para a maioria das permissões no Microsoft Graph.

Você provavelmente já viu isso no Facebook ou no Google: “Você permite que a aplicação X acesse seu perfil?”

Granting admin consent

Agora você está pronto – vamos fazer login e obter alguns dados!

Obter um Token de Acesso (ID do Aplicativo e Segredo)

Para isso, precisaremos enviar uma solicitação para obter um token de acesso de um ponto de extremidade OAuth do Microsoft Graph. E no corpo dessa solicitação, precisamos fornecer:

  • client_id – Seu ID de Aplicativo – codificado em URL
  • client_secret – Seu segredo de aplicativo – codificado em URL
  • scope – Uma URL codificada que especifica o que você deseja acessar
  • grant_type – Qual método de autenticação você está usando

O URL para o ponto de extremidade é https://login.microsoftonline.com/<tenantname>/oauth2/v2.0/token. Você pode solicitar um token de acesso com o PowerShell e a API do Graph usando o trecho de código abaixo.

# Defina AppId, segredo, escopo, nome do seu locatário e URL do ponto de extremidade
$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"

# Adicione System.Web para urlencode
Add-Type -AssemblyName System.Web

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

# Divida os parâmetros para Invoke-Restmethod para um código mais limpo
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    # Crie uma string juntando bodylist com '&'
    Body = $Body
    Uri = $Url
}

# Solicite o token!
$Request = Invoke-RestMethod @PostSplat

Obter um Token de Acesso (Usando um Certificado)

A autenticação na API do Microsoft Graph com um certificado é um pouco diferente do fluxo normal de AppId/Secret. Para obter um token de acesso usando um certificado, você precisa:

  1. Crie um cabeçalho de Token Web Java (JWT).
  2. Crie um payload JWT.
  3. Assine o cabeçalho JWT E payload com o certificado autoassinado previamente criado. Isso criará um token de acesso feito sob medida usado para solicitar um token de acesso ao Gráfico da Microsoft.
  4. Crie um corpo de solicitação contendo:
  • client_id=<ID do aplicativo>
  • client_assertion=<o JWT>
  • client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • scope=<escopo codificado em URL>
  • grant_type=client_credentials
  1. Faça uma solicitação POST com o corpo para o endpoint oauth com Authorization=<JWT> no cabeçalho.

Como fazer isso não era óbvio na documentação da Microsoft, mas aqui está o script do PowerShell para fazer isso acontecer:

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

# Criar hash base64 do certificado
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())

# Criar timestamp JWT para expiração
$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)

# Criar timestamp de início de validade do JWT
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)

# Criar cabeçalho JWT
$JWTHeader = @{
    alg = "RS256"
    typ = "JWT"
    # Usar o CertificateBase64Hash e substituir/remover para corresponder à codificação web de base64
    x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
}

# Criar carga útil JWT
$JWTPayLoad = @{
    # Qual endpoint está autorizado a usar este JWT
    aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"

    # Timestamp de expiração
    exp = $JWTExpiration

    # Emissor = sua aplicação
    iss = $AppId

    # ID do JWT: guid aleatório
    jti = [guid]::NewGuid()

    # Não utilizar antes de
    nbf = $NotBefore

    # Assunto do JWT
    sub = $AppId
}

# Converter cabeçalho e carga útil para 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)

# Juntar cabeçalho e carga útil com "." para criar um JWT válido (não assinado)
$JWT = $EncodedHeader + "." + $EncodedPayload

# Obter o objeto de chave privada do seu certificado
$PrivateKey = $Certificate.PrivateKey

# Definir algoritmo de assinatura RSA e hash
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256

# Criar uma assinatura do JWT
$Signature = [Convert]::ToBase64String(
    $PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
) -replace '\+','-' -replace '/','_' -replace '='

# Juntar a assinatura ao JWT com "."
$JWT = $JWT + "." + $Signature

# Criar um hash com parâmetros do 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"

# Usar o JWT auto-gerado como Autorização
$Header = @{
    Authorization = "Bearer $JWT"
}

# Espalhar os parâmetros para Invoke-Restmethod para código mais limpo
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    Body = $Body
    Uri = $Url
    Headers = $Header
}

$Request = Invoke-RestMethod @PostSplat

Compreendendo a Saída do Token de Acesso

Uma vez que tenha obtido um token de acesso, seja através do ID/segredo da aplicação ou por meio de um certificado, você deverá ver um objeto com quatro propriedades.

  • token_type – O tipo de token
  • expires_in – O tempo em segundos que o token de acesso é válido
  • ext_expires_in – Similar ao expires_in, mas para garantir a resiliência em caso de interrupção do serviço de token
  • access_token – O que buscamos

Em seguida, você criará um cabeçalho usando token_type e access_token e começará a fazer solicitações com o PowerShell para a API do Microsoft Graph.

Fazendo Solicitações para a API do Microsoft Graph com o PowerShell

Agora, comece a fazer algumas solicitações para a API.

No nosso exemplo, você precisará primeiro da URL para listar os alertas de segurança. Lembre-se de consultar a documentação da API do Microsoft Graph para ver o que é necessário.

Neste caso, você precisa de um cabeçalho com Authorization=Bearer <access_token> e uma solicitação GET para o ponto de extremidade de Alertas da API do Graph. Veja como fazer isso com o PowerShell.

# Criar cabeçalho
$Header = @{
    Authorization = "$($Request.token_type) $($Request.access_token)"
}

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

# Obter todos os alertas de segurança
$SecurityAlertsRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"

$SecurityAlerts = $SecurityAlertsRequest.Value

Agora, se você tiver algum alerta de segurança na variável $SecurityAlerts, ele deve se parecer com isso:

$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

Inspeccionar um único alerta de segurança como JSON parecerá assim:

"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":  [

                            ]
}

Compreensão e Gerenciamento da Paginação da Saída da API

A API do Microsoft Graph tem um limite por função sobre quantos itens ela retornará. Esse limite é por função, mas digamos que sejam 1000 itens. Isso significa que você só pode obter no máximo 1000 itens em sua solicitação.

Quando esse limite é atingido, ele usará a paginação para entregar o restante dos itens. Ele faz isso adicionando a propriedade @odata.nextLink à resposta da sua solicitação. @odata.nextLink contém um URL que você pode chamar para obter a próxima página da sua solicitação.

Você pode ler todos os itens verificando essa propriedade e usando um loop:

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

# Buscar todos os alertas de segurança
$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"
}

Conclusão

Depois de aprender a autenticar na API do Graph, é bastante fácil coletar dados dela. É um serviço poderoso que é usado muito menos do que deveria.

Felizmente, existem muitos módulos por aí já para utilizar a API do Microsoft Graph, mas para atender às suas próprias necessidades, você pode precisar criar seu próprio módulo para ela. Usando as habilidades que você aprendeu neste artigo, você deveria estar bem encaminhado.

Para mais informações sobre o controle de acesso de convidados no Office 365, escrevi um artigo detalhado no meu blog que eu encorajo você a dar uma olhada.

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