CloudFormation の StackSets を利用した GuardDuty の有効化

アソビュー Advent Calendar 2019の6日目。

こんにちは。アソビューで SRE チームを担当している秋元です。

アソビューのシステムやサービス運用をする上での取り組みを紹介していきたいと思います。 今回は、CloudFormation の StackSets を利用した GuardDuty の有効化について書きたいと思います。

弊社ではインフラのプロビジョニングツールとして Terraform を使用することが多いですが、GuardDuty の場合、複数の AWS アカウント、リージョンで有効化する必要があり Terraform で作成すると面倒だったので、CloudFormation の StackSets を利用してみました。

GuardDuty

脅威の検出

GuardDuty は、CloudTrail、VPC フローログ、DNS ログなどを解析して悪意のある操作や不正な動作をモニタリングする脅威検出サービスです。

docs.aws.amazon.com

IAM ユーザーのアクセスキーが不正に利用されていないかや、EC2 上で起動しているプログラムが不正な通信を行なっていないかなども検知してくれます。
普段使用しているリージョン、アカウントはもちろんですが、普段使用していないリージョン、アカウントでも有効化することが推奨されています。

通知

脅威が検知されると、CloudWatch イベントが作成されます。
弊社では、検出されたイベントを Datadog に送り、通知するようにしています。

また、GuardDuty では、他のアカウントと関連づけることができます。

docs.aws.amazon.com

弊社では、特定のアカウントをマスターアカウント、その他をメンバーアカウントとし、発生した GuardDuty のイベントをマスターアカウントに集約、Datadog に送信しています。

f:id:akmtr:20191127160446p:plain

CloudFormation StackSets

非常に簡単にセキュリティの脅威を検知できる GuardDuty ですが、有効化を行うにあたりリージョンごとに設定する必要があります。
そのため、例えば Terraform で設定を管理しようとすると、(アカウント数)×(リージョン数)だけ provider を作成する必要があり面倒という問題があります。
そこで弊社では CloudFormation の StackSets という機能を使って設定を行なっています。

CloudFormation StackSets は、一つの CloudFormation のテンプレートを使って、複数の AWS アカウント、リージョンにスタックを作成できる機能です。
スタックの作成は StackSet を作成するアカウントでも別のアカウントでも行えます。 イメージとしてはこんな感じです。

f:id:akmtr:20191127160728p:plain

StackSets でスタックを作成するにあたり、事前に IAM ロールを作成する必要があります。

  • AdministrationRole
    • StackSets を作成するアカウントに作成
    • ExecutionRole に AssumeRole できる必要がある。
  • ExecutionRole
    • スタックを作成するアカウントに作成
    • スタックを作成するのに必要なポリシーが付与されている必要がある。
    • AdministrationRole から AssumeRole されることができる必要がある。

Terraform で記載すると下記のようになります。 (マスターアカウントのアカウント ID=012345678901

  • AdministrationRole
resource "aws_iam_role" "cloudformation_admin_role" {
  name = "StackSetAdministrationRole"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudformation.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {}
    }
  ]
}
EOF
}

resource "aws_iam_policy" "cloudformation_admin_policy" {
  name        = "cloudformation_admin_policy"
  description = "cloudformation_admin_policy"
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "1",
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
        "Resource": "arn:aws:iam::*:role/StackSetExecutionRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "cloudformation_admin_attach" {
  role       = "${aws_iam_role.cloudformation_admin_role.name}"
  policy_arn = "${aws_iam_policy.cloudformation_admin_policy.arn}"
}

resource "aws_iam_role" "cloudformation_execute_role" {
  name = "StackSetExecutionRole"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "012345678901"
      },
      "Action": "sts:AssumeRole",
      "Condition": {}
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "cloudformation_execute_attach" {
  role       = "${aws_iam_role.cloudformation_execute_role.name}"
  policy_arn = "[スタックを作成するのに必要な IAM ポリシーの ARN]"
}
  • ExecutionRole
resource "aws_iam_role" "cloudformation_execute_role" {
  name = "StackSetExecutionRole"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "012345678901"
      },
      "Action": "sts:AssumeRole",
      "Condition": {}
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "cloudformation_execute_attach" {
  role       = "${aws_iam_role.cloudformation_execute_role.name}"
  policy_arn = "[スタックを作成するのに必要な IAM ポリシーの ARN]"
}

少しわかりにくい点としては、マスターアカウントでも ExecutionRole でスタックを作成するので作成する必要があります。

ここで一点注意があります。 StackSets に対応していないリージョンもあるので、その場合は別途 GuardDuty の設定をする必要があります。

StackSets を利用した GuardDuty の有効化

マスターアカウント、メンバーアカウントのテンプレートは下記の通りです。

  • マスターアカウント
AWSTemplateFormatVersion: 2010-09-09
Description: Creates an AWS::GuardDuty::Detector resource and optionally an AWS::GuardDuty::Master resource in a set of accounts and regions.

Resources:
  Detector:
    Type: AWS::GuardDuty::Detector
    Properties:
      Enable: True

  MemberA:
    Type: AWS::GuardDuty::Member
    Properties:
      Status: Invited
      MemberId: 23456789012
      Email: xxxx@hoge.com
      DetectorId: !Ref Detector
      DisableEmailNotification: True

  MemberB:
    Type: AWS::GuardDuty::Member
    Properties:
      Status: Invited
      MemberId: 34567890123
      Email: yyyy@hoge.com
      DetectorId: !Ref Detector
      DisableEmailNotification: True
  • メンバーアカウント
AWSTemplateFormatVersion: 2010-09-09
Description: Creates an AWS::GuardDuty::Detector resource and optionally an AWS::GuardDuty::Master resource in a set of accounts and regions.

Resources:
  Detector:
    Type: AWS::GuardDuty::Detector
    Properties:
      Enable: True

  Master:
    Type: AWS::GuardDuty::Master
    Properties:
      DetectorId: !Ref Detector
      MasterId: "012345678901"

上記では、

  • マスターアカウントのアカウント ID: 012345678901
  • メンバーアカウントのアカウント ID: 23456789012, 34567890123

という構成です。

作成している内容は下記の通りです。

  • マスターアカウント
    • GuardDuty の有効化
    • メンバーアカウントを招待
  • メンバーアカウント
    • GuardDuty の有効化
    • マスターアカウントからの招待を承認

このテンプレートを使用して、全リージョン、全メンバーアカウントを指定してスタックを作成しています。

感想

やってみた感想は下記の通りです。

  • よかった点
    • Terraform で書くよりもすっきり書くことができた。
  • よくなかった点
    • StackSets に対応していないリージョンもあるので、その場合は別途 GuardDuty の設定をする必要があるのが面倒。
    • 実行する部分はマネージメントコンソールから実行するか、何かしらのスクリプトを書く必要がある。
    • Terraform など、CloudFormation 以外のプロビジョニングツールを使っている場合はメンテナンスコストが上がってしまう。

すでに Terraform など別のプロビジョニングツールを使用している場合、メンテナンスコストが上がってしまうのであまり好ましくないかもしれませんが、GuardDuty のアカウント連携の記載方法あたりが個人的にはわかりづらかったので参考になればと思います。