無呼吸になって死ぬ前にAWS Configでやっておきたいこと(terraformでAWS Config設定編)

俺です。

今更ながら俺を守るAWS Configをまじめにはじめました。
昔と違いterraformでAWS Config Ruleは、ほぼinput_parameterにJSON食わす程度で設定できるようになっているのですごく簡単です。
地味にインターネッツに実装例が載って無いっぽい?とおもったのでサンプル載せておきますね...
module化できたら最高なんだけどめんどいからやってません。

AWS Configは自分が操作してないはず、定義していないはずのAWSリソースもチェックできるので
複数人で1つのAWSアカウントを管理している時は、お互いの作業ミスに気づいたり、サービスとして最低限あるべき姿を描くので、ガバナンス強化に役立てることができますね。

チームで仕事をしてるなら、事故ってからおめーこんなこともやっておかねーのかよ呼吸と同じだろってしょーーもないこと言い合う前に、
AWS Configを書いて共有して、殺らなかった結果死ぬ。から身を守りましょう。


# 参考

[寺のdoc](https://www.terraform.io/docs/providers/aws/r/config_config_rule.html)

# 俺の環境

```
Terraform v0.11.3
+ provider.aws v1.9.0
```

# ファイル構成


- aws_config.tf
- aws_config_custom.tf
- aws_sns.tf
 
## aws_config.tf

- AWS Configを使うためのIAM RoleとPolicyの作成
- AWS Configの有効化
- AWS Config Snapshotを保存するS3を有効化(AES-256)
- AWS Config ruleで全体的に有効になってないといけないルールを記載(sshフルオープンとかS3バケットのpublic化とか)

```
resource "aws_config_configuration_recorder" "aws-config" {
  name     = "aws-config"
  role_arn = "${aws_iam_role.aws-config.arn}"
}

resource "aws_iam_role" "aws-config" {
  name = "aws-config"

  assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "config.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
POLICY
}

resource "aws_iam_role_policy" "aws-config" {
  name = "aws-config"
  role = "${aws_iam_role.aws-config.id}"

  policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
        "Action": "config:Put*",
        "Effect": "Allow",
        "Resource": "*"
    },
    {
        "Action": [ "s3:Put*" ],
        "Effect": "Allow",
        "Resource": [
            "${aws_s3_bucket.aws-config.arn}",
            "${aws_s3_bucket.aws-config.arn}/*"
      ]
    }
  ]
}
POLICY
}


resource "aws_s3_bucket" "aws-config" {
  bucket = "${var.project}-aws-config"
}

resource "aws_config_delivery_channel" "aws-config" {
  name           = "${var.project}-aws-config"
  s3_bucket_name = "${aws_s3_bucket.aws-config.bucket}"
  sns_topic_arn = "${aws_sns_topic.lambda_to_slack.arn}"
  snapshot_delivery_properties {
    delivery_frequency = "Three_Hours"
  }
}

resource "aws_config_configuration_recorder_status" "aws-config" {
  name       = "${aws_config_configuration_recorder.aws-config.name}"
  is_enabled = true
  depends_on = ["aws_config_delivery_channel.aws-config"]
}
# add your custom rule is aws_config_custom.tf
# see config rule list
# https://docs.aws.amazon.com/ja_jp/config/latest/developerguide/managed-rules-by-aws-config.html
resource "aws_config_config_rule" "ssh" {
    name = "restricted-ssh"
    source {
        owner = "AWS"
        source_identifier = "INCOMING_SSH_DISABLED"
    }
    scope {
        compliance_resource_types = ["AWS::EC2::SecurityGroup"]
    }
  depends_on = ["aws_config_configuration_recorder.aws-config"]
}
resource "aws_config_config_rule" "r1" {
  name = "autoscaling-group-elb-healthcheck-required"
  source {
    owner             = "AWS"
    source_identifier = "AUTOSCALING_GROUP_ELB_HEALTHCHECK_REQUIRED"
  }
  depends_on = ["aws_config_configuration_recorder.aws-config"]
}
resource "aws_config_config_rule" "r2" {
  name = "db-instance-backup-enabled"
  source {
    owner             = "AWS"
    source_identifier = "DB_INSTANCE_BACKUP_ENABLED"
  }
  depends_on = ["aws_config_configuration_recorder.aws-config"]
}
resource "aws_config_config_rule" "r3" {
  name = "s3-bucket-logging-enabled"
  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_LOGGING_ENABLED"
  }
  depends_on = ["aws_config_configuration_recorder.aws-config"]
}
resource "aws_config_config_rule" "r4" {
  name = "s3-bucket-public-read-prohibited"
  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_PUBLIC_READ_PROHIBITED"

  }
  depends_on = ["aws_config_configuration_recorder.aws-config"]
}
resource "aws_config_config_rule" "r5" {
  name = "s3-bucket-public-write-prohibited"
  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_PUBLIC_WRITE_PROHIBITED"

  }
  depends_on = ["aws_config_configuration_recorder.aws-config"]
```

# aws_config_custom.tf

- システム/サービス特有のリソースのルールを書く
- 特定のResourceIDがあるべき姿をルールにする(管理サーバのsshはフルオープンになってないよねとか、管理サーバで稼働する管理画面のIP制限は有効だよねとか)

例) 特定のSGを443または80ポートが0.0.0.0/0開放されていないルールにする

```
resource "aws_config_config_rule" "mng-cms" {
    name = "restricted-mng-cms"
    source {
        owner = "AWS"
        source_identifier = "RESTRICTED_INCOMING_TRAFFIC"
    }
    scope {
        compliance_resource_types = ["AWS::EC2::SecurityGroup"]
        compliance_resource_id = "${aws_security_group.mng-cms-elb.id}"
    }
    input_parameters = "{\"blockedPort1\":\"443\",\"blockedPort2\":\"80\"}"
  depends_on = ["aws_config_configuration_recorder.aws-config"]
}
resource "aws_config_config_rule" "develop-cms" {
    name = "restricted-develop-cms"
    source {
        owner = "AWS"
        source_identifier = "RESTRICTED_INCOMING_TRAFFIC"
    }
    scope {
        compliance_resource_types = ["AWS::EC2::SecurityGroup"]
        compliance_resource_id = "${aws_security_group.develop-cms-elb.id}"
    }
    input_parameters = "{\"blockedPort1\":\"443\",\"blockedPort2\":\"80\"}"
  depends_on = ["aws_config_configuration_recorder.aws-config"]
}
```

# aws_sns.tf

やられた、やらかしたこに気づくのが重要です。
カジュアルにslackへpostしています。
SNS Topic + Lambdaめんどくせーーってずーっと思ってたら[神様](http://lambdar.hatenablog.com/entry/2017/05/21/020208)がterraform moduleとして公開されてるのを見つけたので使ってます。最高。
id:builtinnyaさんに超絶感謝。
repoは[コチラ](https://github.com/builtinnya/aws-sns-slack-terraform)

`terraform get` で幸せが待ってます。

```
# 神=> https://github.com/builtinnya/aws-sns-slack-terraform
module "sns_to_slack" {
  source = "github.com/builtinnya/aws-sns-slack-terraform/module"

  slack_webhook_url = "${var.oreno_slack_webhook_url}"
  slack_channel_map = "{ \"lambda_to_slack\": \"#notify-hoghoge\" }"
}

resource "aws_sns_topic" "lambda_to_slack" {
  name = "lambda_to_slack"
}

resource "aws_lambda_permission" "allow_lambda_sns_to_slack" {
  statement_id = "AllowSNSToSlackExecutionFromSNS"
  action = "lambda:invokeFunction"
  function_name = "${module.sns_to_slack.lambda_function_arn}"
  principal = "sns.amazonaws.com"
  source_arn = "${aws_sns_topic.lambda_to_slack.arn}"
}

resource "aws_sns_topic_subscription" "lambda_sns_to_slack" {
  topic_arn = "${aws_sns_topic.lambda_to_slack.arn}"
  protocol = "lambda"
  endpoint = "${module.sns_to_slack.lambda_function_arn}"
}
```

# 通知のスクショ

こんなかんじで通知がとんできます。
今回はLambdaを使わずAWSのマネージドルールだけ使ってRule設定しいます。
変更を検知して通知が飛んで来るので、ちょっとメッセージは修正せんとなというところ。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/42755/6365b8bf-327b-9a60-e32c-083b883d8c87.png)


おわり。