🧚‍♀️

ゆるりっとIaCをやってみよう #5 IAM Role/SecurityGroup

2022/03/02に公開

こんにちは、あるとです。
前回はネットワーク周りをterraformでコード化しました。

https://zenn.dev/yururitto_ryuji/articles/c443c0c37111b1

何かいけるやん!!この調子でサクサクっとEC2のインスタンスも作っちゃいますかね〜(´꒳`)

と思っていた時期が僕にもありました。(白目

軽い気持ちで初めてみたら、まぁ大変!
調べてはplan、調べてはplanの繰り返し、、、やっとのことで自分なりの形にできたので
記事にしていこうと思います。

EC2ってなんじゃろね

EC2〜ってなんじゃろ〜ね〜(分かる人とは仲良くなれそう)

EC2インスタンスを立てる時、ストレージやネットワークカード、IAMやセキュリティグループ、
接続するためのキーペアを設定してますよね。
それからどこのサブネットに配置して、削除保護したり。。。

気づきました?

マネージメントコンソールからだと画面みながらポチポチっと作っていると
あまり意識しないのですが、結構なリソースが関連してるんだなと実感。

公式などのサンプルを使えばさっくり立ち上がると思うのですが、もうちょっと運用とかも
意識しつつ色々なリソースとその情報をどう扱っていくかという点を考えながら
作っていきたいと思います。
(できるのか・・・?)

IAM Roleを作ってみよう

EC2に関連するリソースとして、まずはIAMとSecurityGroupから作っていきましょう。
公式:aws_iam_role

IAM Roleを作るにあたって、思い描いているのはこんな感じです。

  • 管理ポリシーをアタッチしたい
  • 何かこう見やすい感じにしたい(語彙力

色々調べつつ、使えそうな要素を盛り込んで作ってみました。
例によって authority というディレクトリを新しく作って作業していきます

# authority/local_valiables.tf
locals {
  attach_policies = [
    "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
    "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
  ]
 }

コンソールでIAM Policyの一覧から欲しいpolicyのarnをセット。
name = [a, b, c] というlistで変数を用意
次はコンソールで「信頼関係」というタブで設定されているassume_roleですね。
リソースにjsonencodeで書けるっぽいのですが、長くなると見辛いかなと思うので
新しくfilesディレクトリを作り、その中にjsonファイルを保存しました。
(ansibleっぽくなっていくな・・・)

# authority/files/assume_role_ec2.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

最後にリソース本体ですね。

# authority/resource_iam_role.tf
resource "aws_iam_role" "server_role" {
  name = "${local.env}-${local.server_role}-server_role"
  path = "/"
  assume_role_policy = file("./files/assume_role_ec2.json")
  managed_policy_arns = local.attach_policies
}

個人的にはスッキリしてて良さげな感じです。

assume_role_policy = file("./files/assume_role_ec2.json")
ここで外出ししたファイルを読み込ませてます。

managed_policy_arns = local.attach_policies
ここでlocalで作っておいた管理ポリシーのリストをセットしています。

これなら大量のroleを作っても省スペースな感じで低燃費〜ってなんじゃろねって感じです。
(ここでネタを繋げていくスタイル)

あとはここで作ったroleをec2で使いたいので、 output という要素を利用したいと思います。
outputを使うと作成したリソースの要素を他から参照することができるようになります。

# authority/output.tf
output "server_instance_profile" {
  value = aws_iam_role.server_role.name
}

こんな感じでvalueにセットしたIAM Roleの名前を後でEC2のterraformで取得できるようにしておきます。

SecurityGroupをつくりましょ

IAMはできたので次はSecurityGroupにとりかかりましょう。
公式:aws_security_group
公式:aws_security_group_rule

SecuretyGroupは2種類あるのですが、大雑把に
aws_security_group → インバウンドとアウトバウンドをインラインで一緒に定義
aws_security_group_rule → インバウンドとアウトバウンドを個別で定義できる
という違いがあるようで、「え?結局どっち使えばええねん?」と判断に困った僕は

aws_security_groupで枠を作って、aws_security_group_ruleで中身を入れる!

という方向で自分を納得させました。
それではまずsecurity groupの枠から作ります

# authority/resource_sg.tf
resource "aws_security_group" "server_sg" {
  name = "${local.env}-${local.server_role}-sg"
  vpc_id = ""
  tags = {
    Environmrnt = local.env
  }
}

別のterraformで管理されているリソースの情報が欲しい!!

ぶ・・・ぶいぴーしーあいでー??
最初に作ったvpc_idが必要なのですが、さてどうやって持ってこよう?
リソースのカテゴリ毎にディレクトリを作ってterraformを分けているので、
aws_vpc.mainn_vpc.id という取得方法はとれません。となると・・・

A・・・Data Sourceを使って、現在作られているリソース情報を取得する
B・・・vpcを管理しているtsftateから情報を取得する

の2通りがあると思います。

  • Aの場合
    • terafform外で変更されてしまっていた場合、その情報とtfstateの情報とで差分がでてしまう
    • さくっと情報をもってこれる
  • Bの場合
    • tfstateの情報を正としてあるべき状態を元にリソースを作れる
    • ひと手間かける必要がある

と僕は思いました(まる
個人的にやむ無く運用で手作業が発生したとしても、terraformで追従して
コード側を正としておかないとせっかくコード化したのが無駄になってしまうし、
手作業の変更を忘れて、リソースを展開していくと手戻りのコストがエライ事になりそうだと
思ったので、Bの方向で進めていきたいと思います。
これなら仮に差分が出た時に「あー、そういや手作業でやったやつだわ」って
気づけそうな気がしました。(あくまで個人的な見解)

ここでoutputの出番!

それではVPC_idを取りにいきましょー。
まずoutput.tfを新規作成して、VPCの情報をaws_security_groupで取得できるようにしましょ。
(VPCについての詳細は第2回を参照してください)

https://zenn.dev/yururitto_ryuji/articles/991dc353e4abc4

# networks/output.tf
output "vpc_id" {
  description = "main vpc id"
  value = aws_vpc.main-vpc.id
}

ここで必要な値をoutputで書き出します。
value = aws_vpc.main-vpc.id の部分でvpc_idを他のディレクトリで作っているterraformが
使えるように吐き出します。 確認は terraform output で見てみましょう

$ terraform output
vpc_id = "vpc-0e62d30b251ee1aaf"

ちゃんとidをもってこれてますね。さて、お次は受け取り側です。

# authority/data_source.tf
data "terraform_remote_state" "networks" {
  backend = "s3"
  config = {
    bucket = "yururitto-tfstate-backet"
    key = "tfstate-files/dev/vpc.tfstate"
    region = "ap-northeast-1"
  }
}

ここで terraform_remote_state を使う事でs3のバケットにあるvpc関連の情報が記載された
tfstateファイルにあるoutputの情報を取得できるようになります。

security_groupの最終系はこちら

# authority/valiable.tf
# アクセスを許可するIPリスト
locals {
  allow_ip_lists = [
    "192.168.33.100/32",
    "192.168.33.101/32",
    "192.168.33.102/32"
  ]
}

# authority/resource_sg.tf

# ここで器を作って
resource "aws_security_group" "server_sg" {
  name = "${local.env}-${local.server_role}-sg"
  vpc_id = "${data.terraform_remote_state.networks.outputs.vpc_id}"
  tags = {
    Environmrnt = local.env
  }
}

# インバウンドの紐付け
resource "aws_security_group_rule" "server-inbound" {
  type = "ingress"
  protocol = "tcp"
  from_port = "80"
  to_port = "80"
  cidr_blocks = local.allow_ip_lists
  security_group_id = aws_security_group.server_sg.id
}

# アウトバウンドの紐付け
resource "aws_security_group_rule" "server_outbound" {
  type = "egress"
  protocol = "-1"
  from_port = 0
  to_port = 0
  cidr_blocks = ["0.0.0.0/0"]
  security_group_id = aws_security_group.server_sg.id
}

vpc_id = "${data.terraform_remote_state.networks.outputs.vpc_id}"

このようにnetworksのディレクトリで作ったリソースのoutputからvpc_idを取得することが
できるようになりました。
あとは security_group_id = aws_security_group.server_sg.id で紐づけることで
インバウンドとアウトバウンドを柔軟に設定できる感じにできました。

尺が長くなってしまったけどapplyします!

いや、ほんとコンソールでの作り方なら、あっという間に説明できることが、とんでもない濃さに
なりましたね・・・すいません、もう少しお付き合いください。

$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_iam_role.server_role will be created
  + resource "aws_iam_role" "server_role" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "ec2.amazonaws.com"
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
	
 # (中略)

  # aws_security_group_rule.server_outbound will be created
  + resource "aws_security_group_rule" "server_outbound" {
      + cidr_blocks              = [
          + "0.0.0.0/0",
        ]
      + from_port                = 0
      + id                       = (known after apply)
      + protocol                 = "-1"
      + security_group_id        = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 0
      + type                     = "egress"
    }
    
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

IAM Role完成!!!

セキュリティグループもばっちり!!

最後に

かなりの長さになってしまった上に今回は色々な要素を盛りこみました。

file("./files/assume_role_ec2.json")

で外部ファイルを読み込んだり

data "terraform_remote_state" "networks" {

でoutputさせたリソースの情報を利用してみたりと自分なりに考えて作れたかなと思います。
苦労した分、コード化できた時の達成感はパないですね!
まだまだterraformは初心者なのですが、この調子でリソースを作って記事にしていきたいと
思いますので、またゆるりっとお付き合いいただければと思います。

ありがとうございました!
次回はいよいよEC2に行けるかな?

Discussion