18
7

More than 1 year has passed since last update.

【AWS】AWS ConfigルールをTerraformを利用して展開する

Last updated at Posted at 2023-04-26

1. はじめに

こんにちは。
最近は社内でも有志でTerraform勉強会があったり、案件でも使う場面があったりとTerraformに触れる機会が多くなってきました。

私はAWSサービスの中でもAWSConfigを扱うことが多いのですが、案件で扱うConfigルールは数十単位になるため手動設定はかなりの手間になってしまうということから、数年前からConfigルール設定のIaC化に取り組んできました。
今回はその中でもTerraformで実装する方法について共有したいと思います。

2. 構成

AWS Configを設定する際に必要な基本的な設計要素のみで今回は実装していきたいと思います。
構成.png

3. 実装

3.1 フォルダ構成

以下のようなフォルダ構成で実装していきます。
リソース名などは変数管理としたいため、Local Valuesを利用します。
参考:Local Values

Terraform/
└─ dev
    ├─ config.tf
    ├─ s3.tf
    ├─ locals.tf
    └─ terraform.tf

3.2 terraform.tf

以下の内容でterraform.tfを設定します。
アカウントIDとリージョンをコード内で取得できるようにしたいので、aws_caller_identityaws_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}"
  }
}

# S3 Bucket policy
resource "aws_s3_bucket_policy" "config-service" {
  bucket = "${local.env}-awsconfig-${data.aws_caller_identity.self.account_id}"
  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}"]
    }
  }
}

# S3 Public Access Block
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 config.tf

IAMロールはConfigのサービスリンクロールを作成します。
Configの配信先S3バケットに、3.3で作成したS3バケットを指定します。

Configを有効化・Configルールを作成するためには以下のリソースを利用します。

  • aws_config_configuration_recorder
  • aws_config_configuration_recorder_status
  • aws_config_delivery_channel
  • aws_config_config_rule

設定レコーダー(aws_config_configuration_recorder)が設定されてからでないと配信チャネル(aws_config_delivery_channel)の設定ができないといったように設定内容に依存関係が存在するため、depends_onで依存関係を明記してあげます。

Configルールの設定パラメータはlocals.tfからcan文を用いて取得するように記載しております。
can文の使い方についてはまた別の記事にしたいと思います。

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

# 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]
}

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"
  }
}

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]
}

#Config rule
resource "aws_config_config_rule" "config_rules" {
  for_each = local.config_rules
  name     = each.key
  source {
    owner             = "AWS"
    source_identifier = each.value.source_identifier
  }
  input_parameters = can(each.value.input_parameters) ? each.value.input_parameters : null
  scope {
    compliance_resource_types = can(each.value.compliance_resource_types) ? each.value.compliance_resource_types : null
  }
  maximum_execution_frequency = can(each.value.maximum_execution_frequency) ? each.value.maximum_execution_frequency : null

  depends_on = [aws_config_configuration_recorder.config-service]
}

3.5 locals.tf

locals.tfでは以下の内容を記載します。

  • 環境名:dev
  • S3バケット名:awsconfig-{AWSアカウントID}
  • Configルール:
    • wafv2-logging-enabled
    • db-instance-backup-enabled
    • api-gw-endpoint-type-check
    • cloudwatch-alarm-action-check
    • fms-webacl-resource-policy-check
locals.tf
# 環境名
locals {
  env = "dev"
}

# S3バケット名
locals {
  s3_bucket = [
    "awsconfig-${data.aws_caller_identity.self.account_id}"
  ]
}

# Configルール有効化
locals {
  config_rules = {
    wafv2-logging-enabled = {
      source_identifier           = "WAFV2_LOGGING_ENABLED"
      maximum_execution_frequency = "TwentyFour_Hours"
    }
    db-instance-backup-enabled = {
      source_identifier = "DB_INSTANCE_BACKUP_ENABLED"
      compliance_resource_types = [
        "AWS::RDS::DBInstance"
      ]
    }
    api-gw-endpoint-type-check = {
      source_identifier = "API_GW_ENDPOINT_TYPE_CHECK"
      compliance_resource_types = [
        "AWS::ApiGateway::RestApi"
      ]
      input_parameters = jsonencode(
        {
          endpointConfigurationTypes = "EDGE"
        }
      )
    }
    cloudwatch-alarm-action-check = {
      source_identifier = "CLOUDWATCH_ALARM_ACTION_CHECK"
      compliance_resource_types = [
        "AWS::CloudWatch::Alarm"
      ]
      input_parameters = jsonencode(
        {
          alarmActionRequired            = "true",
          insufficientDataActionRequired = "false",
          okActionRequired               = "false"
        }
      )
    }
    fms-webacl-resource-policy-check = {
      source_identifier = "FMS_WEBACL_RESOURCE_POLICY_CHECK"
      compliance_resource_types = [
        "AWS::ApiGateway::Stage",
        "AWS::ElasticLoadBalancingV2::LoadBalancer",
        "AWS::WAFRegional::WebACL"
      ]
      input_parameters = jsonencode(
        {
          webACLId = "00000000-0000-0000-0000-000000000000,11111111-1111-1111-1111-111111111111"
        }
      )
    }
  }
}

4. デプロイ

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

> terraform apply
data.aws_region.self: Reading...
data.aws_caller_identity.self: Reading...
data.aws_region.self: Read complete after 0s [id=ap-northeast-1]
data.aws_caller_identity.self: Read complete after 0s [id={account_id}]

Terraform used the selected providers to generate the following execution plan.    
Resource actions are indicated with the following symbols:
  + create
 <= read (data resources)

Terraform will perform the following actions:

~~~中略~~~

Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

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

5. 実機確認

AWSマネジメントコンソールにアクセスし、実際にリソースが作成されているか確認します。

  • IAMロール
    IAMロール_0424.JPG

  • S3バケット
    S3.JPG

  • S3バケットポリシー
    S3_バケットポリシー.JPG

  • Config設定
    Config_0424.JPG

  • Configルール
    Configルール_0424.JPG

    • wafv2-logging-enabled
      wafv2-logging-enabled.JPG

    • db-instance-backup-enabled
      db-instance-backup-enabled.JPG

    • api-gw-endpoint-type-check
      api-gw-endpoint-type-check.JPG

    • cloudwatch-alarm-action-check
      cloudwatch-alarm-action-check.JPG

    • fms-webacl-resource-policy-check
      fms-webacl-resource-policy-check.JPG

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

6. おわりに

今回はAWS Configと周辺環境をTerraformを利用して展開する方法について記事にしてみました。
依存関係や設定する権限が不足していたなど、実際にTerraformを実行してみて初めて気づいた注意点などもあったので、こちらの記事が参考になれば幸いです。

7. 参考

18
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
7