테라폼 실전 강좌 #7 IAM 코드화: 인스턴스 역할, 최소 권한, GitHub Actions OIDC

4 분 소요

앞의 여섯 편 동안 미뤄 온 권한 문제를 한 번에 풉니다. 인스턴스는 SSM 파라미터를 읽어야 하고(#6), Session Manager로 접속을 받아야 하고(#3), 나중에는 CI가 AWS에 배포해야 합니다. 전부 IAM입니다. IAM은 콘솔에서 다루면 JSON 덩어리와 씨름하는 영역인데, 테라폼으로 옮기면 정책이 리뷰 가능한 코드가 되고 리소스 참조로 ARN 오타가 사라져서, IaC의 이득이 가장 크게 체감되는 영역이기도 합니다.

신뢰 정책: 누가 이 역할을 맡을 수 있는가 #

IAM 역할은 두 정책의 조합입니다. 누가 맡을 수 있는가(신뢰 정책)와 맡으면 무엇을 할 수 있는가(권한 정책)입니다. 기초 #4에서 jsonencode로 JSON을 만드는 법을 봤지만, IAM에는 더 나은 도구가 있습니다. aws_iam_policy_document 데이터 소스로, HCL 문법으로 정책을 쓰면 JSON으로 변환해 줍니다. validate 단계에서 구조 오류가 걸리고 참조가 자연스럽게 들어가서 IAM 코드의 표준 도구로 자리 잡았습니다.

iam.tf
# iam.tf (새 파일)
data "aws_iam_policy_document" "ec2_assume" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "web" {
  name_prefix        = "myapp-${var.env}-web-"
  assume_role_policy = data.aws_iam_policy_document.ec2_assume.json
}

권한 정책: 필요한 것만, 리소스 단위로 #

인스턴스에 필요한 권한은 둘입니다. Session Manager 동작(AWS 관리형 정책 연결)과 우리 시크릿 읽기(직접 작성)입니다.

iam.tf
resource "aws_iam_role_policy_attachment" "ssm_core" {
  role       = aws_iam_role.web.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

data "aws_iam_policy_document" "web_permissions" {
  statement {
    actions   = ["ssm:GetParameter"]
    resources = [aws_ssm_parameter.db_password.arn]
  }

  statement {
    actions   = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.assets.arn}/*"]
  }
}

resource "aws_iam_role_policy" "web" {
  name_prefix = "web-"
  role        = aws_iam_role.web.id
  policy      = data.aws_iam_policy_document.web_permissions.json
}

최소 권한의 핵심이 resources 줄에 있습니다. ssm:GetParameter를 "*"에 열지 않고 지난 편에 만든 파라미터 리소스의 ARN 참조로 좁혔습니다. 파라미터 이름이 바뀌어도 정책이 따라오고, 이 역할로는 다른 시크릿을 읽을 수 없습니다. 콘솔에서 ARN을 복사해 붙이던 시절의 오타·누락이 참조 한 줄로 사라지는, IAM 코드화의 대표 장면입니다.

인스턴스 프로파일: 역할을 EC2에 붙이는 어댑터 #

EC2는 역할을 직접 받지 못하고 인스턴스 프로파일이라는 포장을 요구합니다. 역사적인 이유의 한 겹이라 그냥 관용구로 기억하면 됩니다.

iam.tf
resource "aws_iam_instance_profile" "web" {
  name_prefix = "myapp-${var.env}-web-"
  role        = aws_iam_role.web.name
}
compute.tf
# compute.tf의 launch template에 추가
resource "aws_launch_template" "web" {
  # ... 기존 내용 ...

  iam_instance_profile {
    name = aws_iam_instance_profile.web.name
  }
}

apply 후 ASG가 인스턴스를 교체하면(launch template 새 버전), 콘솔의 Session Manager에서 접속 버튼이 활성화됩니다. #3에서 약속한 “SSH 포트 없는 접속"이 완성된 것입니다. 접속해서 지난 편의 aws ssm get-parameter 명령을 실행해 보면 시크릿 읽기 권한까지 동작하는 것을 확인할 수 있습니다.

GitHub Actions OIDC: 액세스 키 없는 CI #

마지막으로 사람이 아닌 것의 인증 하나를 미리 준비합니다. CI(GitHub Actions)가 AWS에 배포하려면 자격 증명이 필요한데, 액세스 키를 발급해 GitHub 시크릿에 넣는 방식은 유출과 로테이션 문제를 떠안습니다. 현행 표준은 OIDC 연동입니다. GitHub가 워크플로마다 발급하는 단기 토큰을 AWS가 신뢰하게 만들어, 저장된 키 없이 역할을 맡는 방식입니다.

iam.tf
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
}

data "aws_iam_policy_document" "github_assume" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = [aws_iam_openid_connect_provider.github.arn]
    }

    condition {
      test     = "StringEquals"
      variable = "token.actions.githubusercontent.com:aud"
      values   = ["sts.amazonaws.com"]
    }

    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      values   = ["repo:my-org/myapp:*"]
    }
  }
}

resource "aws_iam_role" "github_actions" {
  name_prefix        = "myapp-${var.env}-gha-"
  assume_role_policy = data.aws_iam_policy_document.github_assume.json
}

핵심은 두 번째 condition입니다. sub 클레임을 repo:my-org/myapp:*로 제한해 우리 저장소의 워크플로만 이 역할을 맡을 수 있습니다. 이 제한이 없으면 아무 GitHub 저장소나 역할을 맡을 수 있는 구멍이 되므로, OIDC 구성에서 가장 중요한 줄입니다. 역할에 어떤 배포 권한을 줄지, 그리고 워크플로 쪽 설정은 CI에서 plan·apply를 돌리는 운영 시리즈 #1에서 완성하겠습니다.

정리 #

이번 글에서 다룬 내용입니다.

  • IAM 역할은 신뢰 정책과 권한 정책의 조합이며, aws_iam_policy_document 데이터 소스가 IAM 코드의 표준 도구입니다
  • 권한은 리소스 ARN 참조로 좁힙니다. 최소 권한이 오타 없이 유지되는 것이 IAM 코드화의 최대 이득입니다
  • EC2에는 인스턴스 프로파일이라는 어댑터로 역할을 붙이고, launch template에 연결합니다. 이것으로 Session Manager 접속과 시크릿 읽기가 완성됐습니다
  • CI는 액세스 키 대신 OIDC로 역할을 맡습니다. sub 클레임을 저장소로 제한하는 condition이 이 구성의 안전핀입니다
  • CI 역할의 배포 권한과 워크플로 작성은 운영 시리즈 #1에서 이어집니다

다음 글(#8 ECS Fargate 배포)에서는 컴퓨팅 계층을 세대교체합니다. AMI와 user_data의 한계를 짚고, 같은 ALB 뒤에서 EC2와 컨테이너를 나란히 돌리며 전환하겠습니다.

X