【AWS】SecurityHubをTerraformを利用して展開する

# 1.はじめに
こんにちは。
今回はAWS SecurityHubをTerraformを利用して展開する方法について記事にしていきたいと思います。


SecurityHubとは、AWS Configと連携して、セキュリティ業界標準およびベストプラクティスに照らしたAWS環境評価を行うのに有効なサービスです。
参考：[AWS Security Hubとは？](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/what-is-securityhub.html)

SecurityHubの使用を開始すると、ベストプラクティスに沿ったConfigルールが自動的に作成されますが、不要なルールがあった場合は一つずつルールを無効化させる必要があり、かなり手間になってしまいます。
そこでSecurityHubの使用の開始、不要ルールの無効化などを一度に展開できるTerraformコードを作成しましたので共有します。

# 2.構成
[前回の記事](https://qiita.com/risa_someya/items/b060eaab381a5ec60660)でConfigルールをデプロイする構成について紹介しましたので、
今回はSecurityHubを加えた構成で実装していきたいと思います。
![構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/31ddf14f-415c-a5d4-88aa-bb3d8d03a20e.png)


# 3.実装
## 3.1 フォルダ構成
以下のようなフォルダ構成で実装していきます。
リソース名などは変数管理としたいため、Local Valuesを利用します。
参考：[Local Values](https://developer.hashicorp.com/terraform/language/values/locals)
```
Terraform/
└─ dev
    ├─ s3.tf
    ├─ locals.tf
    ├─ security.tf
    └─ terraform.tf
```

## 3.2 terraform.tf
以下の内容でterraform.tfを設定します。
アカウントIDとリージョンをコード内で取得できるようにしたいので、```aws_caller_identity```と```aws_region```のdata source設定を記載しておきます。

```terraform.tf
# providerの定義
provider "aws" {
  region = "ap-northeast-1"
}

terraform {
  required_version = ">=1.0"
  required_providers {
    awscc = {
      source  = "hashicorp/awscc"
      version = "0.45.0"
    }
  }
}

# 実行するAWSアカウント情報取得用
data "aws_caller_identity" "self" {}

# 実行するリージョン情報取得用
data "aws_region" "self" {}
```

## 3.3 s3.tf
S3のバケット名は後述するlocals.tfに記載するため、以下のコード内ではlocalsを参照するようにしています。
ConfigとS3の連携にはS3バケットポリシーの設定が必要となるため、それも記載します。
```s3.tf
# S3 Bucket
resource "aws_s3_bucket" "this" {
  for_each      = toset(local.s3_bucket)
  force_destroy = true

  bucket = "${local.env}-${each.value}"

  tags = {
    Name = "${local.env}-${each.value}"
  }
}

resource "aws_s3_bucket_policy" "config-service" {
  bucket = aws_s3_bucket.this["awsconfig-${data.aws_caller_identity.self.account_id}"].bucket
  policy = data.aws_iam_policy_document.config-service.json
}

data "aws_iam_policy_document" "config-service" {
  version = "2012-10-17"
  statement {
    sid    = "AWSConfigBucketPermissionsCheck"
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["config.amazonaws.com"]
    }
    actions = [
      "s3:GetBucketAcl"
    ]
    resources = [
      "${aws_s3_bucket.this["awsconfig-${data.aws_caller_identity.self.account_id}"].arn}"
    ]
    condition {
      test     = "StringEquals"
      variable = "AWS:SourceAccount"
      values   = ["${data.aws_caller_identity.self.account_id}"]
    }
  }
  statement {
    sid    = "AWSConfigBucketExistenceCheck"
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["config.amazonaws.com"]
    }
    actions = [
      "s3:ListBucket"
    ]
    resources = [
      "${aws_s3_bucket.this["awsconfig-${data.aws_caller_identity.self.account_id}"].arn}"
    ]
    condition {
      test     = "StringEquals"
      variable = "AWS:SourceAccount"
      values   = ["${data.aws_caller_identity.self.account_id}"]
    }
  }
  statement {
    sid    = "AWSConfigBucketDelivery"
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["config.amazonaws.com"]
    }
    actions = [
      "s3:PutObject"
    ]
    resources = [
      "${aws_s3_bucket.this["awsconfig-${data.aws_caller_identity.self.account_id}"].arn}/AWSLogs/${data.aws_caller_identity.self.account_id}/Config/*"
    ]
    condition {
      test     = "StringEquals"
      variable = "s3:x-amz-acl"
      values   = ["bucket-owner-full-control"]
    }
    condition {
      test     = "StringEquals"
      variable = "AWS:SourceAccount"
      values   = ["${data.aws_caller_identity.self.account_id}"]
    }
  }
}

resource "aws_s3_bucket_public_access_block" "config-service" {
  bucket = aws_s3_bucket.this["awsconfig-${data.aws_caller_identity.self.account_id}"].id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```

## 3.4 security.tf
Configに関連するリソースの説明については[前回の記事](https://qiita.com/risa_someya/items/b060eaab381a5ec60660#34-configtf)をご参考ください。

SecurityHubのデプロイのために、以下のリソースを利用します。
* aws_securityhub_account
    * AWSアカウントでSecurityHubを有効化します。
* aws_securityhub_standards_subscription
    * SecurityHubで有効化させるセキュリティ基準を指定します。現在セキュリティ基準は以下がサポートされているので、ここから必要なものだけ選択します。
        * [AWS 基礎セキュリティのベストプラクティス v1.0.0](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/fsbp-standard.html)
        * [CIS AWS Foundations Benchmark v1.2.0 および v1.4.0](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/cis-aws-foundations-benchmark.html)
        * [NIST Special Publication 800-53 Revision 5](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/nist-standard.html)
        * [PCI DSS v3.2.1](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/pci-standard.html)
* aws_securityhub_standards_control
    * 自動で有効化されたルールの中から、不要なルールを無効化させるために利用します。



```security.tf
# IAM
# AWSConfigサービスロールの作成
resource "aws_iam_service_linked_role" "config-service" {
  aws_service_name = "config.amazonaws.com"
}

# Config
# Configレコーダーの有効化
resource "aws_config_configuration_recorder_status" "config-service" {
  name       = aws_config_configuration_recorder.config-service.name
  is_enabled = true
  depends_on = [aws_config_delivery_channel.config-service]
}

# Configレコーダーの作成
resource "aws_config_configuration_recorder" "config-service" {
  name     = "${local.env}-awsconfig"
  role_arn = aws_iam_service_linked_role.config-service.arn
  recording_group {
    all_supported                 = "true"
    include_global_resource_types = "true"
  }
}

# Config配信チャネルの作成
resource "aws_config_delivery_channel" "config-service" {
  name           = "${local.env}-awsconfig"
  s3_bucket_name = aws_s3_bucket.this["awsconfig-${data.aws_caller_identity.self.account_id}"].id
  depends_on     = [aws_config_configuration_recorder.config-service]
}

# SecurityHub
resource "aws_securityhub_account" "securityhub" {
  depends_on = [
    aws_config_configuration_recorder.config-service,
    aws_config_configuration_recorder_status.config-service,
    aws_config_delivery_channel.config-service
  ]
}

# セキュリティ基準の有効化
resource "aws_securityhub_standards_subscription" "securityhub_standards" {
  depends_on    = [aws_securityhub_account.securityhub]
  for_each      = local.securityhub_standards
  standards_arn = each.value.standards_arn
}

# 不要なコントロールを無効化
resource "aws_securityhub_standards_control" "securityhub_disabled_controls" {
  for_each              = local.securityhub_disabled_controls
  standards_control_arn = each.value.standards_control_arn
  control_status        = "DISABLED"
  disabled_reason       = each.value.disabled_reason
  depends_on            = [aws_securityhub_standards_subscription.securityhub_standards]
}
```

## 3.5 locals.tf
```locals.tf```では以下の設定が行えるようにコードに記載します。

* 環境名：dev
* S3バケット名：awsconfig-{AWSアカウントID}
* 有効化するセキュリティ基準：
    * AWS 基礎セキュリティのベストプラクティス v1.0.0
    * CIS AWS Foundations Benchmark v1.2.0
* 無効化するルール： 
    * [ACM.1](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/acm-controls.html#acm-1)：インポートされ ACM によって発行された証明書は、一定期間後に更新する必要があります
    * [ELB.4](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/elb-controls.html#elb-4)：Application Load Balancer は、http ヘッダーを削除するように設定する必要があります
    * [CIS 1.4](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/iam-controls.html#iam-3)：IAM ユーザーのアクセスキーは 90 日以内にローテーションする必要があります

```locals.tf
# ##########
# Environment Parameters
# ##########

locals {
  env = "dev"
}

# ##########
# S3
# ##########

locals {
  s3_bucket = [
    "awsconfig-${data.aws_caller_identity.self.account_id}"
  ]
}

# ##########
# SecurityHub
# ##########
# SecurityHubセキュリティ標準の有効化
locals {
  securityhub_standards = {
    aws-best-practices = {
      standards_arn = "arn:aws:securityhub:${data.aws_region.self.name}::standards/aws-foundational-security-best-practices/v/1.0.0"
    }
    cis = {
      standards_arn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
    }
  }
}

# SecuritHub個別のコントロールの無効化
locals {
  securityhub_disabled_controls = {
    access-keys-rotated-cis-1-4 = {
      standards_control_arn = "arn:aws:securityhub:${data.aws_region.self.name}:${data.aws_caller_identity.self.account_id}:control/cis-aws-foundations-benchmark/v/1.2.0/1.4"
      disabled_reason       = "No access key rotation required."
    }
    acm-certificate-expiration-check-acm-1 = {
      standards_control_arn = "arn:aws:securityhub:${data.aws_region.self.name}:${data.aws_caller_identity.self.account_id}:control/aws-foundational-security-best-practices/v/1.0.0/ACM.1"
      disabled_reason       = "ACM is automatically updated."
    }
    alb-http-drop-invalid-header-enabled-elb-4 = {
      standards_control_arn = "arn:aws:securityhub:${data.aws_region.self.name}:${data.aws_caller_identity.self.account_id}:control/aws-foundational-security-best-practices/v/1.0.0/ELB.4"
      disabled_reason       = "No setting required."
    }
  }
}
```


# 4.デプロイ
まず```terraform plan```を実行してエラーがないことを確認してから```terraform apply```を実行して、AWS環境にデプロイします。

ログにApply complete!と出力されたらデプロイ完了です。


# 5.実機確認
AWSマネジメントコンソールにアクセスし、実際にリソースが作成されているか確認します。
[前回の記事](https://qiita.com/risa_someya/items/b060eaab381a5ec60660#5-%E5%AE%9F%E6%A9%9F%E7%A2%BA%E8%AA%8D)でConfig、S3バケット、IAMロールの作成ができることは確認済みのため、今回はSecurityHubに関連するリソースのみ見ていきます。

* SecuritHub セキュリティ基準の有効化
![キャプチャ03.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/1e8094ff-f1c5-5866-0fdc-53907720040b.jpeg)

* SecurityHub ルールの無効化
    * ACM.1![キャプチャ08.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/e502a020-43f8-e8ff-32bf-1b47efbae56f.jpeg)
    * ELB.4![キャプチャ09.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/a4f23a8c-3db2-28d6-cb40-3eda5cee5dfc.jpeg)
    * CIS 1.4![キャプチャ07.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/6fe0c02e-fe6e-73ae-a4cf-e623e11da7e3.jpeg)

Terraformコードで記載した通りの設定が反映されていることが確認できました。


# 6.おわりに
今回はSecurityHubの有効化の設定と、周辺環境の構築をTerraformを利用して展開する方法について記事にしてみました。
SecurityHubのルールを無効化にする作業は、手作業でやろうとするとかなり手間のかかるものなので、デプロイが容易にできるようにこちらの記事が参考になれば幸いです。

# 7.参考
https://developer.hashicorp.com/terraform/language/values/locals

https://registry.terraform.io/providers/hashicorp/aws/latest/docs