0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TerraformでEC2に最小権限のIAM Roleを設計する

Posted at

はじめに

TerraformでAWSリソースを管理する中で、 IAMポリシーの作成・アタッチは避けて通れない重要な要素です。しかし、「ポリシーを書いて終わり」ではなく、本当に想定どおりの権限で動作しているかを確認することが実務では求められます。

本記事では、Terraformを使って

  • IAMポリシー(S3 ReadOnly)の作成
  • EC2にIAMロールをアタッチ
  • EC2からS3へのアクセス可否を実際に検証

という一連の流れを整理します。

また、単一構成に留まらず、
module化による再利用性の向上と、envs/dev・prod による環境分離を取り入れたディレクトリ構成についても紹介します。

TerraformでIAM設計や構成管理を実務目線で学びたい方の参考になれば幸いです。

フォルダ構成

terraform-iam/
├── envs/
│   ├── dev/
│   │   ├── locals.tf
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── providers.tf
│   │   ├── terraform.tfvars
│   │   └── variables.tf
│   └── prod/
│       ├── locals.tf
│       ├── main.tf
│       ├── outputs.tf
│       ├── providers.tf
│       ├── terraform.tfvars
│       └── variables.tf
├── modules/
│   ├── ec2/
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── iam/
│   │   ├── instance_profile.tf
│   │   ├── outputs.tf
│   │   ├── policy.tf
│   │   ├── role.tf
│   │   └── variables.tf
│   └── s3/
│       ├── main.tf
│       ├── outputs.tf
│       └── variables.tf
├── scripts/
│   └── verify.sh
├── terraform.tfstate
└── versions.tf

本構成では、前回までに紹介した module 化に加えて、環境(dev / prod)を明確に分離する構成 を採用しています。

envs/ ディレクトリについて

envs/ 配下には、環境ごとの Terraform 実行単位を配置しています。

  • dev:検証・学習用環境
  • prod:本番想定環境

それぞれのディレクトリは 独立した Terraform プロジェクト として扱われ、以下のような特徴があります。

  • terraform apply は 対象の環境ディレクトリに移動してから実行
  • terraform.tfvars により 環境ごとの値を切り替え
  • 同じ module を使い回しつつ、構成やサイズだけを変更可能

この構成により、

  • devでは小さいインスタンスで検証
  • prodでは本番相当の設定

といった実務でよくある環境分離を再現できます。

locals.tf について

今回新しく追加したのがlocals.tfです。
localsは、Terraform 内で使える定数・共通値の定義に利用します。
以下はlocals.tfの一例です。

locals.tf
locals {
  common_tags = {
    Project     = "terraform-iam"
    Environment = "dev"
    ManagedBy   = "Terraform"
  }
}

このように定義した値は、以下のように参照できます。

tags = local.common_tags

locals.tf を使う理由

locals.tf を使うことで、以下のメリットがあります。
① 共通値の一元管理
タグ、名前のプレフィックス、環境名(dev / prod)などを 1か所で管理 できます。

② 環境差分を明確にできる
envs/dev/locals.tfenvs/prod/locals.tfと分けることで、dev と prod の違いが明確になり、どこが環境依存なのか分かりやすいという構成になります。

IAMポリシー作成とアタッチの流れ

本章では、本記事のメインであるIAMカスタムポリシーの作成から、EC2 へのアタッチまでの流れを説明します。
今回の目的は以下の通りです。

  • EC2 に IAM ロールを付与する
  • S3 に対して ReadOnly(Get / List のみ) の権限を持たせる
  • 実際に EC2 から S3 へアクセスし、権限制御を確認できる状態にする

全体の流れ

今回の IAM 周りの構成は、以下の流れで作成しています。

IAM Policy(S3 ReadOnly + SSM)
        ↓
IAM Role
        ↓
IAM Instance Profile
        ↓
EC2 にアタッチ

Terraform ではこの一連の流れを module/iam 配下で定義しています。

modules/iam 配下の構成について

modules/iam/
├── instance_profile.tf
├── policy.tf
├── role.tf
├── outputs.tf
└── variables.tf

IAM モジュールでは、IAM ポリシー・ロール・インスタンスプロファイルを役割ごとにファイル分割しています。
Terraform 的には 1 ファイルにまとめることも可能ですが、IAM は構成要素が多くなりやすいため、責務を明確にする目的でファイルを分けています。

role.tf

role.tfでは、EC2 が引き受ける IAM ロール を定義しています。

EC2 用 AssumeRole ポリシーの定義

data "aws_iam_policy_document" "assume_role" {
  statement {
    effect = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

ここでは「誰がこの IAM Role を引き受けられるか」を定義しています。

  • sts:AssumeRole
    → IAM Role を引き受けるために必須のアクション
  • ec2.amazonaws.com
    → EC2 サービスのみがこの Role を利用可能

つまりこのポリシーは、「EC2 インスタンスのみがこの IAM Role を Assume(引き受け)できる」という意味になります。

IAM Role の作成

resource "aws_iam_role" "this" {
  name               = var.role_name
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
  description        = "EC2 AssumeRole"
  tags               = var.common_tags
}

ここで実際の IAM Role リソースを作成しています。

  • name
    → Role 名は変数で管理し、環境ごとの切り替えを可能に
  • assume_role_policy
    → 先ほど定義した AssumeRole ポリシーを適用
  • tags
    → 共通タグを付与し、リソース管理性を向上

SSM 用のマネージドポリシーをアタッチ

resource "aws_iam_role_policy_attachment" "ssm" {
  role       = aws_iam_role.this.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

ここでは AWS が提供するマネージドポリシーAmazonSSMManagedInstanceCore を IAM Role にアタッチしています。

policy.tf

ここでは IAM Role にアタッチするカスタムポリシーを作成します。

S3 ReadOnly 用ポリシーの定義

data "aws_iam_policy_document" "custom_policy" {
  statement {
    sid    = "AllowS3ReadOnly"
    effect = "Allow"

    actions = [
      "s3:GetObject",
      "s3:ListBucket"
    ]

    resources = [
      var.s3_bucket_arn,       # バケット本体
      "${var.s3_bucket_arn}/*" # バケット内のオブジェクト
    ]
  }

このステートメントではS3 の読み取り専用権限を定義しています。

  • s3:ListBucket
    → バケット内のオブジェクト一覧取得
  • s3:GetObject
    → オブジェクトのダウンロード

ポイントはresourcesの指定です。

  • バケット本体(ListBucket 用)
  • バケット配下のオブジェクト(GetObject 用)

を両方指定する必要がある点は、S3 ポリシーでよくハマるポイントです。

PutObject をあえて含めていないため、EC2 からのアップロードは失敗する(=ReadOnly が保証される)構成になっています。

SSM 操作用ポリシーの定義

  statement {
    sid    = "AllowSSM"
    effect = "Allow"

    actions = [
      "ssm:StartSession",
      "ssm:DescribeSessions",
      "ssm:TerminateSession"
    ]

    resources = ["*"]
  }
}

こちらはSession Managerを利用するための権限です。

  • StartSession
    → セッション開始
  • DescribeSessions
    → セッション一覧確認
  • TerminateSession
    → セッション終了

SSM のセッション管理系 API はリソースレベル制御が難しいため、resources = ["*"]としています。

IAM カスタムポリシーの作成

resource "aws_iam_policy" "this" {
  name   = var.policy_name
  policy = data.aws_iam_policy_document.custom_policy.json
}

ここで、先ほど定義したポリシードキュメントをIAM のカスタムポリシーとして作成しています。

  • ポリシー名は変数管理
  • JSON を直接書かず Terraform で構造化

することで、可読性が高く、修正もしやすい構成になります。

IAM Role へのポリシーアタッチ

resource "aws_iam_role_policy_attachment" "custom" {
  role       = aws_iam_role.this.name
  policy_arn = aws_iam_policy.this.arn
}

最後に、作成したカスタムポリシーを前回作成した IAM Role にアタッチします。
これにより EC2 インスタンスは、

  • 指定した S3 バケットへの ReadOnly 操作
  • Session Manager による接続

のみが許可された状態になります。

instance_profile.tf

最後に、IAM Role を EC2 インスタンスへ紐付けるための設定を行います。
EC2 は IAM Role を直接は参照できず、IAM Instance Profile を経由して Role を利用します。
そのため、EC2 に権限を付与する場合は、

IAM Role → Instance Profile → EC2

という構成になります。

IAM Instance Profile の定義

resource "aws_iam_instance_profile" "this" {
  name = "${var.role_name}-profile"
  role = aws_iam_role.this.name
}

このリソースでは、IAM Role を内包した Instance Profileを作成しています。

  • name
    → Role 名に-profileを付与し、用途が分かる命名に
  • role
    → これまでに作成した IAM Role を指定

この Instance Profile を EC2 作成時に指定することで、EC2 インスタンスはその IAM Role の権限を利用できるようになります。

躓いたポイントと解決方法

Terraform の各ファイルを作成・説明した後、実際に terraform apply を実行する中でいくつか躓いた点がありました。
ここでは 実際に発生したエラーと、その原因・対策 をまとめます。

EC2 が起動しない(subnet が空)

発生した状況
EC2 作成時に以下の data リソースを使用していました。

data "aws_subnets" "default" {}

しかし、EC2 が作成されずエラーとなりました。
原因

  • デフォルト VPC は存在していた
  • しかし デフォルトサブネットを削除していた
  • その結果、data.aws_subnets.default.ids が空配列になっていた
    → subnet_id に渡す値がなく、EC2 作成に失敗。

対策

  • 明示的に subnet を作成する
  • 取得した subnet の中身を terraform console や output で確認

学び
data リソースの値は、apply 前に必ず確認する癖をつける

躓いた箇所②:instance_id や outputs が取得できない

発生した状況
aws_instance.thiscountを設定していました。

resource "aws_instance" "this" {
  count = var.instance_count
  ...
}

しかし output で以下のように書くとエラーになりました。

output "instance_id" {
  value = aws_instance.this.id
}

原因

  • count を使った resource は リスト扱い
  • 単一リソースのように .id では参照できない

対策
以下の形式で参照する必要があります。

aws_instance.this[0].id

学び

  • count / for_eachを使った時点で 参照方法が変わる
  • outputs は resource の型(単数 or 複数)を意識する

まとめ

本記事では、Terraform を使って EC2 に最小権限の IAM Role を付与する設計を整理しました。
具体的には、

  • IAM カスタムポリシー(S3 ReadOnly + SSM)の作成
  • EC2 用 IAM Role の定義(AssumeRole)
  • IAM Instance Profile を介した EC2 への紐付け
  • module 化 + envs/dev・prod による環境分離構成

といった一連の流れを、実務を意識した構成で作成してみました。

次回は、本記事で作成した IAM Role を EC2 に適用し、EC2 から S3 への Get / List / Put 操作を実際に行い、ReadOnly 権限が正しく効いているかを検証していきます。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?