こんにちは、サイオステクノロジーの小川です。
前回はAzure ポータルからダウンロードして監査ログを取得する方法について紹介しました。
詳細は以下の記事をご覧ください。
今回は、Azure ADの監査ログをMicrosoft Graphを利用して、監査ログを自動取得する方法を紹介します。
監査ログとは
Azure AD 内のさまざまな機能によって行われたすべての変更についてログによる追跡可能性を提供します。 監査ログの例として、ユーザー、アプリ、グループ、ロール、ポリシーの追加や削除など、Azure AD 内のあらゆるリソースに加えられた変更があります。
Microsoft Graph APIを利用し、ログを取得する
Microsoft Graph APIを利用する方法ではスクリプトですべての処理を行うので、ポータルにログインするなどの操作が不要になります。タスクスケジューラ等で、定期的に実行することで、監査ログをローカルに保持することが可能になります。
アプリケーションとの接続用証明書とNugetのモジュール取得のスクリプトこちらの内容を参照させていただきました。
1. Azure ADに接続する証明書を作成する
以下スクリプトを適当なフォルダに配置し、実行します。今回はC:\AuditLog配下で実行することとします。
本設定では証明書の有効期限は10年で作成しています。
# Create self signed certificate $cert = New-SelfSignedCertificate -Subject "CN=SelfSignedCert" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -NotAfter (Get-Date).AddYears(10) $cert # Export new self signed certificate as .cer file $cerfile = ".\SelfSignedCert.cer" Export-Certificate -Cert $cert -FilePath $cerfile
2. 処理に必要なライブラリをNugetで取得するスクリプトを準備し、実行する
証明書を用いたトークン取得処理はライブラリを用いて行います。処理に必要なライブラリはNugetで取得します。
以下スクリプトを C:\AuditLog 配下に配置し、実行します。
# Download NuGet.exe
$sourceNugetExe = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
$targetNugetExe = ".\nuget.exe"
Remove-Item .\Tools -Force -Recurse -ErrorAction Ignore
Invoke-WebRequest $sourceNugetExe -OutFile $targetNugetExe
Set-Alias nuget $targetNugetExe -Scope Global -Verbose
# Download Microsoft.IdentityModel.Clients.ActiveDirectory.dll
./nuget install Microsoft.IdentityModel.Clients.ActiveDirectory -O .\Tools
md .\Tools\Microsoft.IdentityModel.Clients.ActiveDirectory
$prtFolder = Get-ChildItem ./Tools | Where-Object {$_.Name -match 'Microsoft.IdentityModel.Clients.ActiveDirectory.'}
move .\Tools\$prtFolder\lib\net45\*.* .\Tools\Microsoft.IdentityModel.Clients.ActiveDirectory
Remove-Item .\Tools\$prtFolder -Force -Recurse
# Remove NuGet.exe
Remove-Item nuget.exe
3. Azureにアクセスするためのアプリケーションを作成する
1. Azureポータルにグローバル管理者権限を持つアカウントでサインインします。
2. アプリの登録を選択し、アプリケーションを新規作成します。
3. アプリケーションの任意の表示名とアカウントの種類「この組織ディレクトリのみに含まれるアカウント (xxx のみ – シングル テナント)」を選択し、登録します。(クライアントIDとテナントIDを控えます。)
4. 作成したアプリに移動し、「APIのアクセス許可」を選択します。
5. APIのアクセス許可の画面で「+アクセス許可の追加」を選択し、「Microsoft Graph」の「アプリケーションの許可」を選択します。
6. 「AuditLog.Read.All」と「Directory.Read.All」を有効にします。
7. 「xxxに管理者の同意を与えます」をクリックし、「はい」を選択します。
4. アプリケーション認証に利用する証明書を登録する
1. 3で作成したアプリケーションを選択し、「証明書とシークレット」に移動します。
2. 「証明書のアップロード」を選択し、1で作成した「SelfSignedCert.cer」をアプロードします。
3. アップロード証明書の拇印を控えておく。
5. Azure ADの監査ログを取得するスクリプトを作成する
1. 以下スクリプトを C:\AuditLog 配下に配置し、ファイルを開きます。
2. tenantID, clientID, thumprintに3で控えたクライアントID・テナントIDと4で控えた拇印の値で置き換えます。
3. ログの取得期間と取得するログの種別を指定します。以下では7日前からのログでUserに関するログを取得する設定にしています。
※監査ログすべてを取得する場合は、(targetResources・・・)の項目の設定をしないことで、指定した日付の監査ログを取得することができます。
-
- 取得開始日時指定:$startdate
- 取得終了日時指定:$enddate
- 取得ログファイル表示日時:$outputdate
- 取得ログフィルター:$url の $filter=(activityDateTime ge $startdate) and (activityDateTime le $enddate) and (targetResources/any(t:t/type eq ‘User’))
※その他の条件で取得するログをフィルタする場合は、derectoryAuditのフィルタオプション一覧をご覧ください。フィルタ条件に指定できる設定が記載されております。
4. 3の設定が完了したらファイルを保存し、powershellで実行します。
Add-Type -Path ".\Tools\Microsoft.IdentityModel.Clients.ActiveDirectory\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
# Authorization & resource Url
$tenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx"
$resource = "https://graph.microsoft.com"
$clientID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$thumprint = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$data = @()
# Authorization & resource Url
$authUrl = "https://login.microsoftonline.com/$tenantId/"
# Get certificate
$cert = Get-ChildItem -path cert:\CurrentUser\My | Where-Object {$_.Thumbprint -eq $thumprint}
# Create AuthenticationContext for acquiring token
$authContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext $authUrl, $false
# Create credential for client application
$clientCred = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate $clientID, $cert
# Acquire the authentication result
$authResult = $authContext.AcquireTokenAsync($resource, $clientCred).Result
# Get Signin Log period (UTC)
$startdate = (Get-Date).AddDays(-7).ToString("yyyy-MM-dd")+"T09:00:00.0000000Z"
$enddate = (Get-Date).AddDays(0).ToString("yyyy-MM-dd")+"T09:00:00.0000000Z"
$outputdate = (Get-Date).AddDays(-7).ToString("yyyyMMdd")
if ($null -ne $authResult.AccessToken) {
# Compose the access token type and access token for authorization header
$headerParams = @{'Authorization' = "$($authResult.AccessTokenType) $($authResult.AccessToken)"}
$url = "$resource/v1.0/auditLogs/directoryAudits?`$filter=(activityDateTime ge $startdate) and (activityDateTime le $enddate) and (targetResources/any(t:t/type eq 'User'))"
Write-Output "Fetching data using Uri: $url"
Do {
$myReport = (Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url)
$myReportValue = ($myReport.Content | ConvertFrom-Json ).value
ForEach ($values In $myReportValue) {
$data += New-Object PSObject ($values | Select id,
category,
correlationId,
result,
resultReason,
activityDisplayName,
activityDateTime,
loggedByService,
operationType,
initiatedBy,
targetId,
displayName,
type,
userPrincipalName,
modifiedProperties,
key,
value)
ForEach ($resources In $values.targetResources) {
$data += New-Object PSObject ($resources | Select @{label = "operationType" ;expression = {$values.operationType}},
@{label = "targetId" ;expression = {$resources.id}},
displayName,
userPrincipalName,
groupType)
ForEach ($properties In $resources.modifiedProperties) {
$data += New-Object PSObject ($properties | Select @{label = "operationType" ;expression = {$values.operationType}},
@{label = "userPrincipalName" ;expression = {$resources.userPrincipalName}},
@{label = "modifiedProperties" ;expression = {$properties}})
}
}
ForEach ($details In $values.additionalDetails) {
$data += New-Object PSObject ($details | Select key, value)
}
}
# Get url from next link
$url = ($myReport.Content | ConvertFrom-Json).'@odata.nextLink'
} while($url -ne $null)
}
else {
Write-Host "ERROR: No Access Token"
}
# output CSV
$data | Export-CSV "output_$outputdate.csv" -encoding "utf8" -NoTypeInformation
Write-Host "Audit log is exported to $outfile"
取得したログを確認してみる
上記のスクリプトで取得したCSVファイルを確認してみましょう。
以下はID同期で1アカウントを追加した際の監査ログになります。
operationTypeとUserPrincipalNameはすべての行に明示的に出力するようにスクリプトを記載しているので、どの項目でどのアカウントに関する記載なのかが一目瞭然です!
"id","category","correlationId","result","resultReason","activityDisplayName","activityDateTime","loggedByService","operationType","initiatedBy","targetId","displayName","type","userPrincipalName","modifiedProperties","key","value"
"Directory_7f0e7ab8-1c69-4fc0-9104-21baaabf05db_9RU4I_15761160","UserManagement","7f0e7ab8-1c69-4fc0-9104-21baaabf05db","success","","Add user","2021-05-18T12:01:28.9201835Z","Core Directory","Add","@{app=; user=}",,,,,,,
,,,,,,,,"Add",,"8fe29c9d-a905-404d-b2a6-eff1c89f9fe3",,,"test001@xxx.xxx.com",,,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=AccountEnabled; oldValue=[]; newValue=[true]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=DisplayName; oldValue=[]; newValue=[""test 001""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=GivenName; oldValue=[]; newValue=[""001""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=JobTitle; oldValue=[]; newValue=[""一般""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=LastDirSyncTime; oldValue=[]; newValue=[""2021-05-18T12:01:28Z""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=MailNickname; oldValue=[]; newValue=[""test001""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=ProxyAddresses; oldValue=[]; newValue=[""SMTP:test001@xxx.xxx.com"",""smtp:test001@xxx.onmicrosoft.com""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=SourceAnchor; oldValue=[]; newValue=[""test001""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=Surname; oldValue=[]; newValue=[""test""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=UserPrincipalName; oldValue=[]; newValue=[""test001@xxx.xxx.com""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=UserType; oldValue=[]; newValue=[""Member""]}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=Included Updated Properties; oldValue=; newValue=""AccountEnabled, DisplayName, GivenName, JobTitle, LastDirSyncTime, MailNickname, ProxyAddresses, SourceAnchor, Surname, UserPrincipalName, UserType""}",,
,,,,,,,,"Add",,,,,"test001@xxx.xxx.com","@{displayName=Action Client Name; oldValue=; newValue=""DirectorySync""}",,
テキストベースで少し見づらいですが、Excleのシート等で確認するとより見やすいです。
各属性の項目でoldValueとnewValueで更新されている箇所を確認することができるので、いつどの値が更新したのかを長期で保持することが可能になります。




本サイトは大変参考になりました。
1点質問させていただきます。
作成されているPowerShellスクリプトで、UPN(userPrincipalName)でフィルターすることは可能でしょうか?
MicrosoftがAzure ADのサインインログでは(下記のサイト)、フィルター機能が実装されているので、質問させていただきました。
https://github.com/jpazureid/get-signin-activity-reports-with-certificate
お忙しいところ恐縮ですが、ご回答いただければ幸いです。
翁様
本ブログを読んでくださりさりありがとうございます。
ご質問いただいた点について
「Microsoft Graph APIを利用し、ログを取得する」の5.3に記載している取得ログフィルターの設定を変更してUPNでフィルタをかけることが可能です。
スクリプト内の$urlを以下に変更します。(test@example.comは適当なUPNに設定してください。)
$url = “$resource/v1.0/auditLogs/directoryAudits?`$filter=(activityDateTime ge $startdate) and (activityDateTime le $enddate) and (initiatedBy/user/userPrincipalName eq ‘test@example.com’)”
設定したユーザーの監査ログが取得できます。
その他フィルターに設定可能なパラメータについては https://learn.microsoft.com/ja-jp/graph/api/directoryaudit-list?view=graph-rest-1.0&tabs=http#attributes-supported-by-filter-parameter に記載されております。