Systems Manager で実現する Windows イベントログの自動アーカイブをやってみた

Systems Manager で実現する Windows イベントログの自動アーカイブをやってみた

Clock Icon2025.03.30

こんにちは。テクニカルサポートチームのShiinaです。

はじめに

コンプライアンス要件や企業のセキュリティポリシーにおいては長期的な監査ログの保管や適切な管理が求められます。
WindowsServer を利用している場合はイベントログの保管が重要となってきます。
AWS マネージドサービスを活用することで、低コストかつ簡単にイベントログをアーカイブする方法があります。
本記事では、Systems Manager を使用した Windows イベントログの自動バックアップ手法について紹介します。

概要

AWS Systems Manager の RunCommand 「AWS-RunPowerShellScript」ドキュメントを活用し、Windows イベントログを自動的に S3 にアップロードする仕組みを構築します。
メンテナンスウィンドウ機能により、毎日決まった時間にイベントログのエクスポートとアーカイブを自動化できます。
wevtutil コマンドを使用してアプリケーション、セキュリティ、システムの各イベントログをエクスポートし、AWS Tools for PowerShell で S3 へアップロードします。
さらに、S3 のストレージクラス移行と オブジェクトのライフサイクル管理を設定することで、コスト効率の高い長期的なログ管理を実現します。

前提

  • AWS Systems Manager エージェント (SSM Agent) がインストールされていること ※
  • AWS Tools for PowerShell がインストールされていること※
  • AWS Systems Manager、S3 サービスに通信できること

※AWS 提供 の Windows AMI の場合はプリインストールされています。
https://docs.aws.amazon.com/ja_jp/ec2/latest/windows-ami-reference/windows-amis.html

サンプルスクリプト

処理概要

  • インスタンスメタデータよりインスタンスID を取得する。
  • evetutil コマンドを利用してアプリケーション、セキュリティ、システムイベントログをエクスポートする。
  • AWS Tools for PowerShell コマンドを利用して S3 にアップロードする。
# Define variables
$backupDir = "C:\BackUp"
$bucketName = "<バケット名>"
$date = Get-Date -Format "yyyyMMdd"
$logFile = "$backupDir\UploadErrorLog_$date.txt"
$logs = @("Application", "Security", "System")

# Retrieve instance ID using IMDSv2
try {
    $token = Invoke-RestMethod -Uri "http://169.254.169.254/latest/api/token" -Method PUT -Headers @{"X-aws-ec2-metadata-token-ttl-seconds"="21600"} -ErrorAction Stop
    $instanceId = Invoke-RestMethod -Uri "http://169.254.169.254/latest/meta-data/instance-id" -Headers @{"X-aws-ec2-metadata-token"=$token} -ErrorAction Stop
} catch {
    Write-Host "Failed to retrieve instance metadata. Error: $_"
    exit 1
}

# Check if the backup directory exists
if (!(Test-Path $backupDir)) {
    New-Item -ItemType Directory -Path $backupDir | Out-Null
}

# Export event logs
$exportedFiles = @()
$uploadSuccess = $true 

foreach ($log in $logs) {
    $logPath = "$backupDir\$date`_$log.evtx"

    # Check if any logs were exported
    try {
        Write-Host "Exporting $log log to $logPath ..."
        wevtutil epl $log $logPath /ow:true
        if (Test-Path $logPath) {
            $exportedFiles += $logPath
            Write-Host "Successfully exported $log log."
        } else {
            throw "Failed to export $log log. File not found: $logPath"
        }
    } catch {
        $errorMessage = "Error exporting $log log: $_"
        Write-Host $errorMessage
        $errorMessage | Out-File -Append -FilePath $logFile
        $uploadSuccess = $false
    }
}

# Check if any logs were exported
if ($exportedFiles.Count -eq 0) {
    Write-Host "No logs were exported. Exiting script."
    exit 1
}

# Upload logs to S3
foreach ($file in $exportedFiles) {
    $fileName = [System.IO.Path]::GetFileName($file)
    $s3Key = "$instanceId/EventLogs/$fileName"
    Write-Host "Uploading $file to s3://$bucketName/$s3Key ..."

    try {
        Write-S3Object -BucketName $bucketName -File $file -Key $s3Key -ErrorAction Stop
    } catch {
        $errorMessage = "Failed to upload $file to S3. Error: $_"
        Write-Host $errorMessage
        $errorMessage | Out-File -Append -FilePath $logFile
        $uploadSuccess = $false
    }
}

# Delete backup folder if all uploads were successful
if ($uploadSuccess) {
    Write-Host "All logs uploaded successfully."
    Remove-Item -Path $backupDir -Recurse -Force
} else {
    Write-Host "Some logs failed to upload. Backup folder will not be deleted."
    Write-Host "Check error log: $logFile"
}

Write-Host "Backup and upload process completed."

やってみた

1. S3 バケット作成とライフサイクル設定

  1. アーカイブ先となる S3 バケットを作成します。
  2. ライフサイクル設定ではセキュリティポリシーや法的要件に応じて適切な保管期間を設定します。
    将来の参照可能性を考慮して移行・削除のタイミングを決定してください。
  • ライフサイクルの設定例
    1年後に Glacier Flexible Retrieval ストレージクラスに移動し、3年後にオブジェクトを削除する。
    rule

2. IAM ロール設定

EC2 インスタンスロールに S3 アップロードに必要な s3:PutObject アクションを許可するインラインポリシーをアタッチします。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Statement1",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::<バケット名>/*"
            ]
        }
    ]
}

iam

3. メンテナンスウィンドウ作成

毎日22:00にタスクを実行するメンテナンスウィンドウを作成してみます。
コンソールを使用する

  1. AWS Systems Manager ナビゲーションメニューよりメンテナンスウィンドウを選択します。

  2. メンテナンスウィンドウの作成を選択します。
    メンテナンスウィンドウ1

  3. 設定項目を入力し、メンテナンスウィンドウを作成を選択します。
    メンテナンスウィンドウ作成

AWS CLIを使用する

aws ssm create-maintenance-window \
    --name "EventLogBackUp" \
    --schedule "cron(0 0 22 ? * * *)" \
    --schedule-timezone "Asia/Tokyo" \
    --duration 1 \
    --cutoff 0 \
    --allow-unassociated-targets

4. ターゲット割り当て

EC2 インスタンスのタグenv:prodを指定したターゲットの割り当てを行ってみます。
コンソールを使用する

  1. メンテナンスウィンドウ一覧より作成したウィンドウ ID を選択します。
    ターゲット作成1

  2. ターゲットタブよりターゲットの登録を選択します。
    ターゲット0

  3. 設定項目を入力し、Register target を選択します。
    ターゲット作成2

AWS CLIを使用する

aws ssm register-target-with-maintenance-window \
    --window-id <ウィンドウ ID> \
    --resource-type INSTANCE \
    --targets "Key=tag:env,Values=prod" \
    --name "prodserver"

5. タスク割り当て

Run Command をタスクに割り当てます。
イベントログのエクスポートと S3 アップロードを行うサンプルスクリプトをパラメータに指定します。
コンソールを使用する

  1. メンテナンスウィンドウ一覧より作成したウィンドウ ID を選択します。
    ターゲット作成1

  2. タスクタブより Run Command タスクの登録を選択します。
    タスク作成1

  3. 設定項目を入力し、Run Command タスクの登録を選択します。
    コマンドドキュメント項目は以下のように指定します。
    ドキュメント:AWS-RunPowerShellScript
    ドキュメントのバージョン:ランタイムのデフォルトバージョン
    パラメータ:サンプルスクリプト
    タスク作成2

AWS CLIを使用する

aws ssm register-task-with-maintenance-window \
    --window-id <ウィンドウ ID>  \
    --targets "Key=WindowTargetIds,Values=<ウィンドウのターゲット ID>" \
    --task-arn "AWS-RunPowerShellScript" \
    --task-type "RUN_COMMAND" \
    --task-invocation-parameters '{
        "RunCommand": {
            "Comment": "EventLogBackUpTask",
            "DocumentVersion": "$DEFAULT",
            "TimeoutSeconds": 600,
            "Parameters": {
                "commands": ["# Define variables","$backupDir = \"C:\\BackUp\"","$bucketName = \"<バケット名>\"","$date = Get-Date -Format \"yyyyMMdd\"","$logFile = \"$backupDir\\UploadErrorLog_$date.txt\"","$logs = @(\"Application\", \"Security\", \"System\")","","# Retrieve instance ID using IMDSv2","try {"," $token = Invoke-RestMethod -Uri \"http://169.254.169.254/latest/api/token\" -Method PUT -Headers @{\"X-aws-ec2-metadata-token-ttl-seconds\"=\"21600\"} -ErrorAction Stop"," $instanceId = Invoke-RestMethod -Uri \"http://169.254.169.254/latest/meta-data/instance-id\" -Headers @{\"X-aws-ec2-metadata-token\"=$token} -ErrorAction Stop","} catch {"," Write-Host \"Failed to retrieve instance metadata. Error: $_\""," exit 1","}","","# Check if the backup directory exists","if (!(Test-Path $backupDir)) {"," New-Item -ItemType Directory -Path $backupDir | Out-Null","}","","# Export event logs","$exportedFiles = @()","$uploadSuccess = $true ","","foreach ($log in $logs) {"," $logPath = \"$backupDir\\$date`_$log.evtx\""," "," # Check if any logs were exported"," try {"," Write-Host \"Exporting $log log to $logPath ...\""," wevtutil epl $log $logPath /ow:true"," if (Test-Path $logPath) {"," $exportedFiles += $logPath"," Write-Host \"Successfully exported $log log.\""," } else {"," throw \"Failed to export $log log. File not found: $logPath\""," }"," } catch {"," $errorMessage = \"Error exporting $log log: $_\""," Write-Host $errorMessage"," $errorMessage | Out-File -Append -FilePath $logFile"," $uploadSuccess = $false"," }","}","","# Check if any logs were exported","if ($exportedFiles.Count -eq 0) {"," Write-Host \"No logs were exported. Exiting script.\""," exit 1","}","","# Upload logs to S3","foreach ($file in $exportedFiles) {"," $fileName = [System.IO.Path]::GetFileName($file)"," $s3Key = \"$instanceId/EventLogs/$fileName\""," Write-Host \"Uploading $file to s3://$bucketName/$s3Key ...\"",""," try {"," Write-S3Object -BucketName $bucketName -File $file -Key $s3Key -ErrorAction Stop"," } catch {"," $errorMessage = \"Failed to upload $file to S3. Error: $_\""," Write-Host $errorMessage"," $errorMessage | Out-File -Append -FilePath $logFile"," $uploadSuccess = $false"," }","}","","# Delete backup folder if all uploads were successful","if ($uploadSuccess) {"," Write-Host \"All logs uploaded successfully.\""," Remove-Item -Path $backupDir -Recurse -Force","} else {"," Write-Host \"Some logs failed to upload. Backup folder will not be deleted.\""," Write-Host \"Check error log: $logFile\"","}","","Write-Host \"Backup and upload process completed.\""]
            }
        }
    }' \
    --priority 1 \
    --max-concurrency "50%" \
    --max-errors "50%" \
    --name "EventLogBackUpTask"

6. 実行履歴確認

指定したメンテナンスウィンドウ時刻が到来した後に Run Command 実行履歴の確認を行います。

  1. メンテナンスウィンドウ一覧より作成したウィンドウ ID を選択します。
    ターゲット作成1

  2. 履歴タブのウィンドウ実行 ID にチェックを入れ、詳細の表示を選択します。
    履歴1

  3. タスク呼び出しの ID にチェックを入れ、詳細の表示を選択します。
    履歴2

  4. Run Command のステータスが成功となっていることを確認します。
    履歴3

7. S3 バケットの確認

S3 バケットにエクスポートされたイベントログがアップロードされているか確認を行います。

  1. S3 バケットを選択します。
  2. エクスポートされたイベントログオブジェクト(.evtx 形式)が保存されていることを確認します。

サンプルスクリプトではプレフィックスとオブジェクト名に日付が付与されます。

  • <バケット名> / <インスタンス ID> / EventLogs / YYYYMMDD_Application.evtx
  • <バケット名> / <インスタンス ID> / EventLogs / YYYYMMDD_Security.evtx
  • <バケット名> / <インスタンス ID> / EventLogs / YYYYMMDD_System.evtx
    s3

まとめ

AWS Systems Manager を活用することで、複数の Windows Server インスタンスに対して、統一的かつ効率的なイベントログのバックアップが可能となります。
タグベースのターゲット管理により、環境に応じた柔軟なログ収集戦略を実現し、運用管理の複雑さを大幅に軽減できます。
メンテナンスウィンドウ機能と RunCommand を組み合わせることで、作業を自動化し、人的ミスのリスクを低減します。
ライフサイクルルールの適用により、低コストな S3 ストレージへのアーカイブが可能となります。

本記事が参考になれば幸いです。

参考

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/walkthrough-powershell.html
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/maintenance-windows.html
https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/wevtutil

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.