PowerShell Graph APIのマスター:理解しやすい洞察

マイクロソフト グラフ APIは、ほぼすべての側面を単一のREST APIエンドポイントの下でAzure ADおよびOffice 365を読み取り、変更、管理できるサービスです。この記事では、APIをPowerShellグラフ APIに変換する方法について学びます。

前提条件

この記事で一緒に進む場合は、まず以下の基準を満たしていることを確認してください:

  • Windows PowerShell 5.1を実行していること(これが私がテストしたバージョンです。他のバージョンでも動作する可能性がありますが、保証されません)
  • Azureテナント
  • グローバル管理者権限またはサブスクリプションに対するアプリ登録権限を持つアカウントでAzureに認証され、アプリ登録リクエストを受け入れるためのグローバル管理者がいること。

Microsoft Graph APIのアプリケーションアイデンティティの作成

Microsoft Graph APIにアクセスするには、最初にOAuthトークンを取得するためのアイデンティティが必要です。これは、Azure Portalで作成できるアプリケーションアイデンティティを使用して主に行われます。次の手順でアプリケーションアイデンティティを作成できます:アプリケーションアイデンティティの作成をクリックしてAzureポータルに移動します。

  • Azureポータルに移動してAzure Active Directoryに移動します。
  • 左側のメニューでApp Registrationsをクリックし、Manageの下のNew registrationボタンをクリックします。
Authenticating before creating the PowerShell Graph API
  • アプリケーションの名前を入力し、Registerをクリックします。
  • 後で使用するためにアプリケーションIDのGUIDをコピーします。
Registering an application

Microsoft Graph APIのシークレットの作成

あなたはGraph APIには、AppId/Secretおよび証明書ベースの認証の2つの主要な方法で認証できます。PowerShellでGraph APIに接続する際には認証が必要です。

両方の方法で認証する手順を見てみましょう。

AppId/Secret

アプリケーションID/シークレットは通常のユーザー名/パスワードと同じです。アプリケーションIDはユーザー名の代わりにGUIDで構成され、パスワードはランダムな文字列です。

シークレットを作成するには、左メニューの証明書とシークレットをクリックし、新しいクライアントシークレットを選択します。

Creating a client secret

シークレットの説明を入力し、有効期限を選択します。これで、必要なデータにアクセスできるように許可を求めるだけです。

証明書

自己署名証明書を作成し、その公開鍵をAzureにアップロードすることが可能です。これが認証の好ましいかつより安全な方法です。

最初に自己署名証明書を生成する必要があります。幸運なことに、PowerShellで簡単に行うことができます。

# テナント名(もっと説明的な名前でも可)
$TenantName        = "contoso.onmicrosoft.com"

# プライベートキーなしで証明書をエクスポートする場所
$CerOutputPath     = "C:\Temp\PowerShellGraphCert.cer"

# 証明書を保存する場所
$StoreLocation     = "Cert:\CurrentUser\My"

# 新しい証明書の有効期限
$ExpirationDate    = (Get-Date).AddYears(2)


# 可読性のためのスプラット
$CreateCertificateSplat = @{
    FriendlyName      = "AzureApp"
    DnsName           = $TenantName
    CertStoreLocation = $StoreLocation
    NotAfter          = $ExpirationDate
    KeyExportPolicy   = "Exportable"
    KeySpec           = "Signature"
    Provider          = "Microsoft Enhanced RSA and AES Cryptographic Provider"
    HashAlgorithm     = "SHA256"
}

# 証明書の作成
$Certificate = New-SelfSignedCertificate @CreateCertificateSplat

# 証明書のパスを取得
$CertificatePath = Join-Path -Path $StoreLocation -ChildPath $Certificate.Thumbprint

# プライベートキーなしで証明書をエクスポート
Export-Certificate -Cert $CertificatePath -FilePath $CerOutputPath | Out-Null

さあ、セルフサイン証明書を$CerOutputPathにエクスポートし、左のメニューの証明書とシークレットをクリックして証明書をアップロードしてAzureアプリケーションにアップロードします。

Uploading a certificate

アプリケーションへの権限の追加

アプリケーションに適切な権限を与えることは重要です – アプリの機能だけでなくセキュリティにも関わります。Microsoft Graph APIのほぼすべての情報はドキュメントに記載されています。

設定が完了したら、テナントからすべてのセキュリティイベントを収集します。そのためには、最低限SecurityEvents.Read.Allの許可が必要です。これにより、不可能なトラベルイベント、VPN/TOR経由で接続するユーザーなどに対処できます。

アプリケーションにSecurityEvents.Read.Allを追加するには、「APIアクセス許可」をクリックしてから「アクセス許可の追加」をクリックします。これにより、Graph APIだけでなく、Azureの他のアプリケーションも表示されます。これらのアプリケーションのほとんどは、Microsoft Graph APIに接続する方法を知っていれば簡単に接続できます。

Microsoft Graphをクリックして、アプリケーションのアクセス許可セキュリティイベントを選択し、SecurityEvents.Read.Allをチェックします。その後、「アクセス許可の追加」ボタンを押します。

管理者の同意が必要列にはいが設定されていることに気付きましたか?これは、許可がアプリケーションに追加される前にテナント管理者の承認が必要であることを意味します。

Tenant admin permission approval

グローバル管理者の場合、管理者の同意を付与するをクリックするか、グローバル管理者に承認を依頼します。管理者が読み取り/書き込み許可を設定する代わりに、ユーザーからの許可を求めることは、OAuth認証の大部分を占めています。ただし、これにより、Microsoft Graphのほとんどの許可をバイパスできます。

おそらく、FacebookやGoogleで既に目にしていると思いますが、「アプリケーションXがプロフィールにアクセスすることを許可しますか?」

Granting admin consent

これで準備完了です。ログインしてデータを取得しましょう!

アクセストークン(アプリケーションIDとシークレット)を取得

するには、Microsoft Graph OAuthエンドポイントにリクエストを投稿する必要があります。そのリクエストのボディには、以下の情報を提供する必要があります:

  • client_id – アプリケーションID – URLエンコード済み
  • client_secret – アプリケーションのシークレット – URLエンコード済み
  • scope – アクセスしたいリソースを指定するURL形式でエンコード済み
  • grant_type – 使用している認証メソッド

エンドポイントのURLは、https://login.microsoftonline.com/<tenantname>/oauth2/v2.0/tokenです。以下のコードスニペットを使用して、PowerShellとGraph APIでアクセストークンをリクエストすることができます。

# AppId、シークレット、スコープ、テナント名、エンドポイントURLを定義する
$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"

# urlencodeのためにSystem.Webを追加する
Add-Type -AssemblyName System.Web

# ボディを作成する
$Body = @{
    client_id = $AppId
	client_secret = $AppSecret
	scope = $Scope
	grant_type = 'client_credentials'
}

# より整ったコードのためにInvoke-Restmethodのパラメータを指定する
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    # bodylistを'&'で結合して文字列を作成する
    Body = $Body
    Uri = $Url
}

# トークンをリクエストする!
$Request = Invoke-RestMethod @PostSplat

アクセストークンの取得(証明書を使用する場合)

証明書を使用してMicrosoft Graph APIに認証する場合、通常のAppId/シークレットのフローとは異なります。証明書を使用してアクセストークンを取得するには、以下の手順を実行する必要があります:

  1. Java Web Token(JWT)ヘッダーを作成します。
  2. JWTペイロードを作成します。
  3. 以前に作成した自己署名証明書でJWTヘッダーとペイロードに署名します。これにより、Microsoft Graphアクセストークンを要求するために使用される自己作成のアクセストークンが作成されます。
  4. 次の内容を含むリクエストボディを作成します:
  • client_id=<アプリケーションID>
  • client_assertion=<JWT>
  • client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • scope=<URLエンコードされたスコープ>
  • grant_type=client_credentials
  1. ヘッダーにAuthorization=<JWT>を含めて、OAuthエンドポイントに対してPOSTリクエストを行います。

これを実現するPowerShellスクリプトは、Microsoftのドキュメントでは明確ではありませんが、以下に示します。

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

# 証明書のbase64ハッシュを作成
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())

# JWT有効期限のためのタイムスタンプを作成
$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)

# JWT有効開始タイムスタンプを作成
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)

# JWTヘッダを作成
$JWTHeader = @{
    alg = "RS256"
    typ = "JWT"
    # CertificateBase64Hashを使用し、Webエンコーディングに一致するように置換/削除
    x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
}

# JWTペイロードを作成
$JWTPayLoad = @{
    # このJWTを使用できるエンドポイント
    aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"

    # 有効期限タイムスタンプ
    exp = $JWTExpiration

    # 発行者 = あなたのアプリケーション
    iss = $AppId

    # JWT ID:ランダムなGUID
    jti = [guid]::NewGuid()

    # 使用前には使用しない
    nbf = $NotBefore

    # JWTサブジェクト
    sub = $AppId
}

# ヘッダとペイロードを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)

# ヘッダとペイロードを"."で結合して有効な(未署名の)JWTを作成
$JWT = $EncodedHeader + "." + $EncodedPayload

# 証明書の秘密鍵オブジェクトを取得
$PrivateKey = $Certificate.PrivateKey

# RSA署名とハッシュアルゴリズムを定義
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256

# JWTの署名を作成
$Signature = [Convert]::ToBase64String(
    $PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
) -replace '\+','-' -replace '/','_' -replace '='

# JWTに署名を"."で結合
$JWT = $JWT + "." + $Signature

# ボディパラメータのハッシュを作成
$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"

# 自己生成したJWTをAuthorizationとして使用
$Header = @{
    Authorization = "Bearer $JWT"
}

# Invoke-Restmethodのためのパラメータを整理するためにパラメータを分割
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    Body = $Body
    Uri = $Url
    Headers = $Header
}

$Request = Invoke-RestMethod @PostSplat

理解リクエストアクセストークンの出力

アプリケーションID/シークレットまたは証明書を介してアクセストークンを取得したら、四つのプロパティを持つオブジェクトが表示されるはずです。

  • token_type – トークンの種類
  • expires_in – アクセストークンの有効期限(秒単位)
  • ext_expires_inexpires_inと同様ですが、トークンサービスの障害に対処するためのもの
  • access_token – 我々が求めていたもの

次に、token_typeaccess_tokenを使用してヘッダーを作成し、PowerShellを使用してMicrosoft Graph APIにリクエストを開始します。

Microsoft Powershell Graph APIへのリクエストの作成

さあ、APIに対していくつかのリクエストを開始します。

例を挙げると、まずセキュリティアラートをリストアップするためのURLが必要です。必要なものはMicrosoft Graph APIドキュメントを参照してください。

この場合、Authorization=Bearer <access_token>を含むヘッダーとGraph API Alertsエンドポイントに向けたGETリクエストが必要です。PowerShellでこれを行う方法は次の通りです。

# ヘッダーの作成
$Header = @{
    Authorization = "$($Request.token_type) $($Request.access_token)"
}

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

# すべてのセキュリティアラートを取得
$SecurityAlertsRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"

$SecurityAlerts = $SecurityAlertsRequest.Value

これで$SecurityAlerts変数にセキュリティアラートがあれば、次のように見えるはずです。

$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

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

                            ]
}

JSONとして単一のセキュリティアラートを検査すると、次のようになります。

API出力ページの理解と管理

Microsoft Graph APIには、返されるアイテムの数に関する各機能ごとの制限があります。この制限は各機能ごとに存在しますが、たとえばアイテムが1000個の場合、そのリクエストで取得できる最大のアイテム数が1000個ということです。

この制限に達すると、残りのアイテムを配信するためにページングが使用されます。これは、リクエストの回答に@odata.nextLinkプロパティを追加することで行われます。@odata.nextLinkには、リクエストの次のページを取得するために呼び出せるURLが含まれています。

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

# すべてのセキュリティアラートを取得
$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"
}

結論

Graph APIに認証する方法を学んだら、そこからデータを収集するのはかなり簡単です。これは、十分に活用されていない強力なサービスです。

幸いにも、既にMicrosoft Graph APIを利用するための多くのモジュールが存在していますが、独自のニーズに合わせるためには、独自のモジュールを作成する必要があるかもしれません。この記事で学んだスキルを使用すれば、十分に進んだ段階にあるはずです。

Office 365でゲストアクセスを制御する詳細な情報については、私のブログで詳細な記事を書いています。ぜひチェックしてみてください

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