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

A API do Microsoft Graph é um serviço que permite ler, modificar e gerenciar quase todos os aspectos do Azure AD e do Office 365 sob um único ponto de extremidade da API REST. Neste artigo, aprenda como transformar sua API na API do PowerShell Graph.

Pré-requisitos

Se você deseja acompanhar este artigo comigo, certifique-se primeiro de atender aos seguintes critérios:

  • Executando o Windows PowerShell 5.1 (Esta é a versão com a qual testei. Outras versões podem funcionar, mas não são garantidas)
  • Um locatário Azure
  • Autenticado no Azure com uma conta com permissões de administrador global ou permissões de registro de aplicativo na assinatura e um administrador global para aceitar suas solicitações de registro de aplicativo.

Criando uma Identidade de Aplicativo para a API do Microsoft Graph

Para acessar a API do 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 do Azure. Você pode criar uma identidade de aplicativo via o portal do Azure. Para fazer isso:

  • Vá para o Portal Azure e acesse o 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 o seu aplicativo e clique em Registrar.
  • Copie o GUID do ID do Aplicativo para uso posterior.
Registering an application

Criando Segredos para a API do Microsoft Graph

Você pode autenticar no Graph API com 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 abordar como autenticar com ambos os métodos.

AppId/Secret

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

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 escolha quando deseja que ele expire. Agora é apenas uma questão de pedir permissão para acessar os dados que você deseja.

Certificado

Há a possibilidade de criar um certificado autoassinado e enviar sua chave pública para o 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 seu locatário (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 qual armazenamento de certificados você deseja que ele esteja
$StoreLocation     = "Cert:\CurrentUser\My"

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


# Splat 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 a chave privada
Export-Certificate -Cert $CertificatePath -FilePath $CerOutputPath | Out-Null

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

Uploading a certificate

Adicionando permissões à aplicação

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

Assim que eu configurar isso, vou reunir todos os eventos de segurança do meu locatário. Para fazer isso, preciso da permissão mínima de SecurityEvents.Read.All. Com isso, posso reunir e tomar medidas em eventos de viagem impossível, usuários conectando-se via VPN/TOR e similares.

Para adicionar o SecurityEvents.Read.All à sua aplicação, clique em Permissões da API e então em Adicionar Permissão. Isso irá 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 de admin necessário foi definida como Sim para essa 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 Conceder consentimento de admin 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/gravação é uma grande parte da autenticação OAuth. Mas isso nos permite ignorá-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!

Adquira 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 especificando 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 PowerShell e a API do Graph usando o trecho de código abaixo.

# Defina AppId, segredo e escopo, o 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 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 unindo bodylist com '&'
    Body = $Body
    Uri = $Url
}

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

Adquirir 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 ID do Aplicativo/Segredo. Para obter um token de acesso usando um certificado, você precisa:

  1. Crie um cabeçalho de Token da Web Java (JWT).
  2. Crie um payload JWT.
  3. Assine o cabeçalho e o payload JWT com o certificado autoassinado criado anteriormente. Isso criará um token de acesso feito sob medida usado para solicitar um token de acesso ao Microsoft Graph.
  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 de autorização com Authorization=<JWT> no cabeçalho dele.

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

$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 carimbo de data/hora 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 carimbo de data/hora de início de validade 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/retirar para corresponder à codificação base64 da web
    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"

    # Carimbo de data/hora de expiração
    exp = $JWTExpiration

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

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

    # Não para ser usado antes de
    nbf = $NotBefore

    # Assunto 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 hashing
$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 Solicitação

Uma vez que você obteve um token de acesso, seja através do ID/aplicação ou através de um certificado, deverá visualizar um objeto com quatro propriedades.

  • token_type – Tipo de token
  • expires_in – Tempo em segundos durante o qual o token de acesso é válido
  • ext_expires_in – Semelhante a expires_in, mas para resiliência em caso de interrupção no serviço de tokens
  • access_token – O que buscamos

A seguir, você irá criar um cabeçalho utilizando 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 PowerShell Graph

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

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

Neste caso, você precisa de um cabeçalho com Authorization=Bearer <access_token> e uma solicitação GET para o endpoint de Alertas da Graph API. 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, deverá se parecer com algo assim:

$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

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

                            ]
}

Entendendo e Gerenciando a Paginação de 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, ela usará a paginação para entregar o restante dos itens. Ela 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 percorrer todos os itens verificando esta 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 que você 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, já existem muitos módulos por aí 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 isso. Usando as habilidades que você aprendeu neste artigo, você deverá 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 verificar.

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