Inspector + EventBridge + Systems Manager Automationで脆弱性自動パッチ適用してみた

Inspector + EventBridge + Systems Manager Automationで脆弱性自動パッチ適用してみた

Clock Icon2025.07.03

こんにちは、ゲームソリューション部のsoraです。
今回は、Inspector + EventBridge + Systems Manager Automationで脆弱性自動パッチ適用してみたことについて書いていきます。

構成

以下が今回作成する構成です。
InspectorにてEC2インスタンスの脆弱性を検知して、HIGHMIDIUMにあたる脆弱性はEventBridgeでキャッチ、Systems ManagerのAutmationにてRun Commandを呼び出して、EC2インスタンスにパッチを適用する構成になっています。
sr-inspector-automation-01

ただし、先に結果だけ言うと、Automationが実行されることまでは確認しましたが自動パッチ適用まではできていません。
仕組み自体は問題なかったのですが、複数の脆弱性があったときにリブートを含む処理が重複してAutomationの実行完了までには至りませんでした。

正しく動くようにするためには、重複制御の仕組みを追加で入れるか、そもそも重複していても動くような処理に限定する必要があります。
ただ、実運用だと自動でパッケージのバージョンを上げるパターンはあまりないかと思うので、あくまで参考程度に見ていただければと思います。
(機会があれば重複制御もした上での実装も試してみようと思います。)

Inspectorの有効化

Inspectorを使用するため、有効化していなければ有効化しておきます。
対象のリソースが存在していなかったため、インスタンスのところにも表示されていません。
sr-inspector-automation-02

Terraformでのリソース作成(Eventbridge + Systems Managerなど)

必要なリソースをTerraformにて作成します。
EC2インスタンスをマネージドノードにするために必要なインスタンスプロファイルも作成しています。
ログ出力周りのリソースは不要ですが作成しています。
IAMロール周りは外部ファイルで記載していますが今回は割愛します。

Terraformコード(クリックで展開)
### Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

### Data sources
data "aws_caller_identity" "current" {}

### Resources
# CloudWatch Log Group for EventBridge
resource "aws_cloudwatch_log_group" "eventbridge_logs" {
  name              = "/aws/eventbridge/inspector-automation"
  retention_in_days = 7
  tags = {
    Name = "EventBridge-Inspector-Logs"
  }
}
# Set resource policy to allow EventBridge to write logs
resource "aws_cloudwatch_log_resource_policy" "eventbridge_logs_policy" {
  policy_name = "EventBridgeLogsPolicy"
  policy_document = templatefile("${path.module}/policies/cloudwatch-logs-resource-policy.json", {
    log_group_arn = aws_cloudwatch_log_group.eventbridge_logs.arn
  })
}

# IAM Role for EventBridge Automation Target
resource "aws_iam_role" "eventbridge_ssm_role" {
  name = "EventBridgeSSMRole"
  assume_role_policy = file("${path.module}/policies/eventbridge-assume-role-policy.json")
}
resource "aws_iam_role_policy" "eventbridge_ssm_policy" {
  name = "EventBridgeSSMPolicy"
  role = aws_iam_role.eventbridge_ssm_role.id
  policy = templatefile("${path.module}/policies/eventbridge-ssm-policy.json", {
    account_id = data.aws_caller_identity.current.account_id
    dlq_arn    = aws_sqs_queue.eventbridge_automation_dlq.arn
  })
}

# IAM Role for Automation
resource "aws_iam_role" "automation_role" {
  name = "AutomationRole"
  assume_role_policy = file("${path.module}/policies/automation-assume-role-policy.json")
}
resource "aws_iam_role_policy_attachment" "automation_role_policy" {
  role       = aws_iam_role.automation_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole"
}
resource "aws_iam_role_policy" "automation_additional_policy" {
  name = "AutomationAdditionalPolicy"
  role = aws_iam_role.automation_role.id
  policy = file("${path.module}/policies/automation-additional-policy.json")
}

# IAM Role for EC2
resource "aws_iam_role" "ec2_ssm_role" {
  name = "EC2-SSM-Role"
  assume_role_policy = file("${path.module}/policies/ec2-ssm-assume-role-policy.json")
}
resource "aws_iam_role_policy_attachment" "ec2_ssm_policy" {
  role       = aws_iam_role.ec2_ssm_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

# Inspector2用の追加権限
resource "aws_iam_role_policy" "ec2_inspector2_policy" {
  name = "EC2Inspector2Policy"
  role = aws_iam_role.ec2_ssm_role.id
  policy = file("${path.module}/policies/ec2-inspector2-policy.json")
}

resource "aws_iam_instance_profile" "ec2_ssm_profile" {
  name = "EC2-SSM-Profile"
  role = aws_iam_role.ec2_ssm_role.name
}

# Patch Baseline
resource "aws_ssm_patch_baseline" "security_baseline" {
  name             = "SecurityPatchBaseline"
  description      = "Security patches for Ubuntu"
  operating_system = "UBUNTU"
  approval_rule {
    approve_after_days = 0
    patch_filter {
      key    = "PRIORITY"
      values = ["Required", "Important"]
    }
  }
  tags = {
    Name = "SecurityPatchBaseline"
  }
}

# Patch Group Registration
resource "aws_ssm_patch_group" "security_patch_group" {
  baseline_id = aws_ssm_patch_baseline.security_baseline.id
  patch_group = "SecurityPatching"
}

# Automation Document
resource "aws_ssm_document" "process_inspector_finding" {
  name            = "ProcessInspectorFinding"
  document_type   = "Automation"
  document_format = "YAML"
  content = yamlencode({
    schemaVersion = "0.3"
    description   = "Process Inspector findings and apply security patches"
    assumeRole    = "{{ AutomationAssumeRole }}"
    parameters = {
      InstanceId = {
        type        = "String"
        description = "EC2 Instance ID from Inspector finding"
      }
      Severity = {
        type        = "String"
        description = "Severity of the finding"
      }
      AutomationAssumeRole = {
        type        = "String"
        description = "IAM role for automation"
        default     = aws_iam_role.automation_role.arn
      }
    }
    mainSteps = [
      {
        name   = "ApplySecurityPatches"
        action = "aws:runCommand"
        inputs = {
          DocumentName = "AWS-RunPatchBaseline"
          InstanceIds  = ["{{ InstanceId }}"]
          Parameters = {
            Operation = "Install"
            RebootOption = "RebootIfNeeded"
          }
        }
        isEnd = true
      }
    ]
  })
  tags = {
    Name = "ProcessInspectorFinding"
  }
}

# EventBridge Rule
resource "aws_cloudwatch_event_rule" "inspector_to_automation" {
  name        = "InspectorToAutomation"
  description = "Trigger automation when Inspector finds vulnerabilities"
  event_pattern = jsonencode({
    source      = ["aws.inspector2"]
    detail-type = ["Inspector2 Finding"]
    detail = {
      status = ["ACTIVE"]
      severity = ["HIGH", "CRITICAL"]
      type     = ["PACKAGE_VULNERABILITY"]
    }
  })
  tags = {
    Name = "InspectorToAutomation"
  }
}

# EventBridge Target
## Automation
resource "aws_cloudwatch_event_target" "automation_target" {
  rule     = aws_cloudwatch_event_rule.inspector_to_automation.name
  arn      = aws_ssm_document.process_inspector_finding.arn
  role_arn = aws_iam_role.eventbridge_ssm_role.arn
  dead_letter_config {
    arn = aws_sqs_queue.eventbridge_automation_dlq.arn
  }
  input_transformer {
    input_paths = {
      instanceId = "$.detail.resources[0].id"
      severity   = "$.detail.severity"
    }
    input_template = <<EOF
{
  "AutomationAssumeRole": ["${aws_iam_role.automation_role.arn}"],
  "InstanceId": ["<instanceId>"],
  "Severity": ["<severity>"]
}
EOF
  }
}
## CloudWatch Logs
resource "aws_cloudwatch_event_target" "log_target" {
  rule = aws_cloudwatch_event_rule.inspector_to_automation.name
  arn  = aws_cloudwatch_log_group.eventbridge_logs.arn
}

# SQS Dead Letter Queue for EventBridge
resource "aws_sqs_queue" "eventbridge_automation_dlq" {
  name = "eventbridge-automation-dlq"
}

# Inspector2 Enable
resource "aws_inspector2_enabler" "inspector_enable" {
  account_ids    = [data.aws_caller_identity.current.account_id]
  resource_types = ["EC2"]
}

動作確認

準備ができたので、EC2インスタンスを起動します。
EC2インスタンスはUbuntu Server 24.04を使用しました。
Inspector Agentはデフォルトでインストールされていて、マネージドノードとするためのIAMロールはTerraformで作成したロールを使用します。

Inspectorにてマネージドノードとして表示されていて、Systems Managerにて関連付けのステータスが成功していれば問題なく動作しています。
sr-inspector-automation-03

起動して数分経つと、いくつかの脆弱性が表示されます。
sr-inspector-automation-04

EventBridgeを見てみると、正常に実行されていることがわかります。
※Inspectorで表示されている脆弱性にて、CriticalとHighが11個に対して、22個出ているのはターゲットとしてCloudWatch Logsも含めた2つになっているため2倍になっています。
sr-inspector-automation-05

Automationを確認してみると、実行しようとはしているもののエラーになっています。
これは構成の部分でも記載しましたが、パッチ適用中にリブートが発生する形になっていて他の処理が中断されることで発生しているものだと思われます。
(機会があれば重複制御もした上での実装も試してみようと思います。)
sr-inspector-automation-06
sr-inspector-automation-07

最後に

今回は、Inspector + EventBridge + Systems Manager Automationで脆弱性自動パッチ適用してみたことを記事にしました。
どなたかの参考になると幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.