1. はじめに
こんにちは。
最近は社内でも有志でTerraform勉強会があったり、案件でも使う場面があったりとTerraformに触れる機会が多くなってきました。
私はAWSサービスの中でもAWSConfigを扱うことが多いのですが、案件で扱うConfigルールは数十単位になるため手動設定はかなりの手間になってしまうということから、数年前からConfigルール設定のIaC化に取り組んできました。
今回はその中でもTerraformで実装する方法について共有したいと思います。
2. 構成
AWS Configを設定する際に必要な基本的な設計要素のみで今回は実装していきたいと思います。
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_identity
とaws_region
のdata source設定を記載しておきます。
# 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 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文の使い方についてはまた別の記事にしたいと思います。
# 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 {
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マネジメントコンソールにアクセスし、実際にリソースが作成されているか確認します。
Terraformコードで記載した通りの設定が反映されていることが確認できました。
6. おわりに
今回はAWS Configと周辺環境をTerraformを利用して展開する方法について記事にしてみました。
依存関係や設定する権限が不足していたなど、実際にTerraformを実行してみて初めて気づいた注意点などもあったので、こちらの記事が参考になれば幸いです。
7. 参考