Office 365 の監査ログを Microsoft Sentinel に格納する - Office 365 Management API と Data Collector API (PowerShell 利用版)

# 本記事の内容

本記事では、Office 365 の監査ログを [Office 365 Management API](https://docs.microsoft.com/ja-jp/office/office-365-management-api/office-365-management-apis-overview) （[Audit.Generalサブスクリプション](https://docs.microsoft.com/ja-jp/office/office-365-management-api/office-365-management-activity-api-reference#working-with-the-office-365-management-activity-api)）と [Data Collector API](https://docs.microsoft.com/ja-jp/azure/azure-monitor/platform/data-collector-api) を利用して、Azure Log Analytics (Microsoft Sentinel) に格納していきます。本記事では PowerShell を利用して行っていきますが、他の言語でも可能です。

2022年7月時点では、Office 365 のログのうち、Microsoft Sentinel に[標準のコネクター](https://docs.microsoft.com/ja-jp/azure/sentinel/connect-office-365)で格納できるのは SherePoint/OneDriveとExchange, Teams の監査ログのみのため、それ以外のログをどう格納していくことができるか見ていきます。

## 本記事に先立ち

本記事は、下記記事の内容をベースに進めていきます。Office 365 Management API の AADでのアクセス許可の設定やAudit.General サブスクリプション、Log Analyticsのワークスペースの作成などは事前に行っているものとします。

・[Office 365 Management API の使い方 with PowerShell](https://qiita.com/YoshiakiOi/items/16866f40c29618f05f39)
・[Azure Log Analytics Data Collector API で データを Log Analytics (Azure Sentinel) に格納する with PowerShell](https://qiita.com/YoshiakiOi/items/b5c91da79d6c442f509c)

また、下記の GitHub レポジトリの内容をベースにしています。
[Deploy Function App for getting Office 365 Management API data into Azure Sentinel](https://github.com/Azure/Azure-Sentinel/tree/master/DataConnectors/O365%20Data)

# PowerShell コード

基本的にはManagement APIによるデータの取得から、Data Collector API によるログ格納まで一気に実行するコードですが、分けてご紹介します。

## 変数の定義
まず、Office 365 Management API と Log Analytics Data Collector API を利用するために必要な変数を定義しておきます。

```PowerShell
$ClientID = "<GUID> from AAD App Registration"
$ClientSecret = "<clientSecret> from AAD App Registrtion"
$loginURL = "https://login.microsoftonline.com/"
$tenantdomain = "<domain>.onmicrosoft.com"
$TenantGUID = "<tenantguid> from AAD"
$resource = "https://manage.office.com"
$workspaceID ="<ID> from Log Analytics workspace>"
$workspaceKey = "<key> from Log Analytics workspace>"
$customLogName = "Office365ManagementAPI"
```

## AAD アクセストークンの取得
次に、AADアクセストークンを取得します。

```PowerShell
$body = @{grant_type="client_credentials";resource=$resource;client_id=$ClientID;client_secret=$ClientSecret}
$oauth = Invoke-RestMethod -Method Post -Uri $loginURL/$tenantdomain/oauth2/token?api-version=1.0 -Body $body
$headerParams = @{'Authorization'="$($oauth.token_type) $($oauth.access_token)"}
```

## Office 365 Management API のデータを取る際に必要な時間の情報を定義
次に、Office 365 Management API のデータを取る際に必要な時間の情報を定義します。過去7日以内かつ、startTime と endTime の間が24時間以内になるようにします。

```PowerShell
$currentUTCtime = (Get-Date).ToUniversalTime()
$endTime = $currentUTCtime.AddHours(-60) | Get-Date -Format yyyy-MM-ddThh:mm:ss
$startTime = $currentUTCtime.AddHours(-84) | Get-Date -Format yyyy-MM-ddThh:mm:ss
$contentTypes = "Audit.General"
$recordTypes = 0
```

## Office 365 Management API と Data Collector API の実行
Office 365 Management API を実行し、データを取得した後、そのイベントごとに Foreach ループを用いて、Data Collector API (function Write-OMSLogfile) で Log Analytics にデータを格納します。

```PowerShell
$contentTypes = $contentTypes.split(",")
    #Loop for each content Type like Audit.General
    foreach($contentType in $contentTypes){
        $listAvailableContentUri = "https://manage.office.com/api/v1.0/$tenantGUID/activity/feed/subscriptions/content?contentType=$contentType&PublisherIdentifier=$publisher&startTime=$startTime&endTime=$endTime"
        do {
            #List Available Content
            $contentResult = Invoke-RestMethod -Method GET -Headers $headerParams -Uri $listAvailableContentUri

            #Loop for each Content
            foreach($obj in $contentResult){
                #Retrieve Content
                $data = Invoke-RestMethod -Method GET -Headers $headerParams -Uri ($obj.contentUri)
                #Loop through each Record in the Content
                foreach($event in $data){
                    #Filtering for Record types
                    #Get all Record Types
                    if($recordTypes -eq "0"){
                        #MCASのアラートは除く
                        if(($event.Source) -ne "Cloud App Security"){
                            #Write each event to Log A
                            $writeResult = Write-OMSLogfile (Get-Date).ToUniversalTime() $customLogName $event $workspaceId $workspaceKey
                            #$writeResult
                        }
                    }
                    else{
                        #Get only certain record types
                        $types = ($recordTypes).split(",")
                        if(($event.RecordType) -in $types){
                            #MCASのアラートは除く
                            if(($event.Source) -ne "Cloud App Security"){
                                #write each event to Log A
                                $writeResult = Write-OMSLogfile (Get-Date).ToUniversalTime() $customLogName $event $workspaceId $workspaceKey
                            }
                        }
                        
                    }
                }
            }

    function Write-OMSLogfile {
        [cmdletbinding()]
            Param(
            [Parameter(Mandatory = $true, Position = 0)]
            [datetime]$dateTime,
            [parameter(Mandatory = $true, Position = 1)]
            [string]$type,
            [Parameter(Mandatory = $true, Position = 2)]
            [psobject]$logdata,
            [Parameter(Mandatory = $true, Position = 3)]
            [string]$CustomerID,
            [Parameter(Mandatory = $true, Position = 4)]
            [string]$SharedKey
        )
        Write-Verbose -Message "DateTime: $dateTime"
        Write-Verbose -Message ('DateTimeKind:' + $dateTime.kind)
        Write-Verbose -Message "Type: $type"
        write-Verbose -Message "LogData: $logdata"    

        function Build-signature ($CustomerID, $SharedKey, $Date, $ContentLength, $method, $ContentType, $resource) {
            $xheaders = 'x-ms-date:' + $Date
            $stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource
            $bytesToHash = [text.Encoding]::UTF8.GetBytes($stringToHash)
            $keyBytes = [Convert]::FromBase64String($SharedKey)
            $sha256 = New-Object System.Security.Cryptography.HMACSHA256
            $sha256.key = $keyBytes
            $calculateHash = $sha256.ComputeHash($bytesToHash)
            $encodeHash = [convert]::ToBase64String($calculateHash)
            $authorization = 'SharedKey {0}:{1}' -f $CustomerID,$encodeHash
            return $authorization
        }

        # Function to create and post the request
        Function Post-LogAnalyticsData ($CustomerID, $SharedKey, $Body, $Type) {
            $method = "POST"
            $ContentType = 'application/json'
            $resource = '/api/logs'
            $rfc1123date = ($dateTime).ToString('r')
            $ContentLength = $Body.Length
            $signature = Build-signature `
                -customerId $CustomerID `
                -sharedKey $SharedKey `
                -date $rfc1123date `
                -contentLength $ContentLength `
                -method $method `
                -contentType $ContentType `
                -resource $resource
            $uri = "https://" + $customerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"
            $headers = @{
                "Authorization" = $signature;
                "Log-Type" = $type;
                "x-ms-date" = $rfc1123date
                "time-generated-field" = $dateTime
            }
            $response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $ContentType -Headers $headers -Body $body -UseBasicParsing
            Write-Host -message ('Post Function Return Code ' + $response.statuscode)
            return $response.statuscode
        }

        # Check if time is UTC, Convert to UTC if not.
        # $dateTime = (Get-Date)
        if ($dateTime.kind.tostring() -ne 'Utc'){
            $dateTime = $dateTime.ToUniversalTime()
            Write-Verbose -Message $dateTime
        }

        # Add DateTime to hashtable
        #$logdata.add("DateTime", $dateTime)
        $logdata | Add-Member -MemberType NoteProperty -Name "DateTime" -Value $dateTime

        #Build the JSON file
        $logMessage = ConvertTo-Json $logdata -Depth 20
        Write-Verbose -Message $logMessage

        #Submit the data
        $returnCode = Post-LogAnalyticsData -CustomerID $CustomerID -SharedKey $SharedKey -Body ([System.Text.Encoding]::UTF8.GetBytes($logMessage)) -Type $type
        Write-Verbose -Message "Post Statement Return Code $returnCode"
        return $returnCode
    }
            
            #Handles Pagination
            $nextPageResult = Invoke-WebRequest -Method GET -Headers $headerParams -Uri $listAvailableContentUri
            If(($nextPageResult.Headers.NextPageUrl) -ne $null){
                $nextPage = $true
                $listAvailableContentUri = $nextPageResult.Headers.NextPageUrl
            }
            Else{$nextPage = $false}
        } until ($nextPage -eq $false)

    }
```

# クエリの実行結果
Log Analytics側でクエリを実行すると下記のようにデータを確認できます。Office365ManagementAPI_CLにデータが入っていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/675efc73-5b37-8c2e-694e-3473307da7aa.png)

# まとめ
Office 365 Management API と Data Collector API を PowerShell を用いて実行して、Office 365 の監査ログを Log Analytics (Azure Sentinel) に格納できることを確認しました。上記の手法を用いると、Audit.General や DLP.All など Sentinel のコネクターでは取得できないレコードタイプのログを Sentinel に格納できるようになります。

＊本稿は、個人の見解に基づいた内容であり、所属する会社の公式見解ではありません。また、いかなる保証を与えるものでもありません。正式な情報は、各製品の販売元にご確認ください。