15. Terraform을 사용하여 DynamoDB 액세스를 위한 IAM 정책 연결

발행: (2026년 2월 8일 오후 06:11 GMT+9)
10 분 소요
원문: Dev.to

I’m happy to translate the article for you, but I need the full text of the post (the portions you’d like translated). Could you please paste the content you want translated here? I’ll keep the source line and all formatting exactly as you requested.

Source:

Lab Information

DevOps 팀은 보안이 강화된 DynamoDB 테이블을 생성하고 IAM을 사용하여 세분화된 액세스 제어를 적용하는 작업을 맡았습니다. 이 설정을 통해 신뢰할 수 있는 AWS 서비스에서만 테이블에 대한 안전하고 제한된 액세스가 가능하도록 합니다.

Nautilus DevOps Team의 일원으로서, Terraform을 사용하여 다음 작업을 수행하십시오:

  1. Create a DynamoDB Table – 최소 구성으로 datacenter-table이라는 이름의 테이블을 생성합니다.
  2. Create an IAM Roledatacenter-role이라는 이름의 역할을 생성하고, 이 역할이 테이블에 접근하도록 허용합니다.
  3. Create an IAM Policydatacenter-readonly-policy라는 이름의 정책을 만들어 읽기 전용 액세스(GetItem, Scan, Query)를 특정 DynamoDB 테이블에만 부여하고, 해당 정책을 역할에 연결합니다.
  4. Create the main.tf file (별도의 .tf 파일을 만들지 말고) 에 테이블, 역할, 정책을 프로비저닝하도록 작성합니다.
  5. Create the variables.tf file 에 다음 변수를 정의합니다:
    • KKE_TABLE_NAME – DynamoDB 테이블 이름
    • KKE_ROLE_NAME – IAM 역할 이름
    • KKE_POLICY_NAME – IAM 정책 이름
  6. Create the outputs.tf file 에 다음 출력을 정의합니다:
    • kke_dynamodb_table – DynamoDB 테이블 이름
    • kke_iam_role_name – IAM 역할 이름
    • kke_iam_policy_name – IAM 정책 이름
  7. Define the actual values for these variables in the terraform.tfvars file.
  8. Ensure the IAM policy allows only read access and restricts it to the specific DynamoDB table created.

Source:

실습 솔루션

variables.tf

variable "KKE_TABLE_NAME" {
  type = string
}

variable "KKE_ROLE_NAME" {
  type = string
}

variable "KKE_POLICY_NAME" {
  type = string
}

terraform.tfvars

KKE_TABLE_NAME  = "datacenter-table"
KKE_ROLE_NAME   = "datacenter-role"
KKE_POLICY_NAME = "datacenter-readonly-policy"

main.tf

# DynamoDB 테이블 (최소 구성)
resource "aws_dynamodb_table" "datacenter_table" {
  name         = var.KKE_TABLE_NAME
  billing_mode = "PAY_PER_REQUEST"

  hash_key = "id"

  attribute {
    name = "id"
    type = "S"
  }
}

# IAM 역할
resource "aws_iam_role" "datacenter_role" {
  name = var.KKE_ROLE_NAME

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
      Action = "sts:AssumeRole"
    }]
  })
}

# IAM 정책 (특정 DynamoDB 테이블에 대한 읽기 전용 액세스)
resource "aws_iam_policy" "datacenter_policy" {
  name = var.KKE_POLICY_NAME

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = [
        "dynamodb:GetItem",
        "dynamodb:Scan",
        "dynamodb:Query"
      ]
      Resource = aws_dynamodb_table.datacenter_table.arn
    }]
  })
}

# 정책을 역할에 연결
resource "aws_iam_role_policy_attachment" "attach_policy" {
  role       = aws_iam_role.datacenter_role.name
  policy_arn = aws_iam_policy.datacenter_policy.arn
}

outputs.tf

output "kke_dynamodb_table" {
  value = aws_dynamodb_table.datacenter_table.name
}

output "kke_iam_role_name" {
  value = aws_iam_role.datacenter_role.name
}

output "kke_iam_policy_name" {
  value = aws_iam_policy.datacenter_policy.name
}

Terraform 명령 (순서대로 실행)

terraform init
terraform validate
terraform apply
# 프롬프트가 나오면 "yes" 입력
bob@iac-server ~/terraform via 💠 default ➜  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_dynamodb_table.datacenter_table will be created
  + resource "aws_dynamodb_table" "datacenter_table" {
      + arn              = (known after apply)
      + billing_mode     = "PAY_PER_REQUEST"
      + hash_key         = "id"
      + id               = (known after apply)
      + name             = "datacenter-table"
      + read_capacity    = (known after apply)
      + stream_arn       = (known after apply)
      + stream_label     = (known after apply)
      + stream_view_type = (known after apply)
      + tags_all         = (known after apply)
      + write_capacity   = (known after apply)

      + attribute {
          + name = "id"
          + type = "S"
        }

      + point_in_time_recovery (known after apply)

      + server_side_encryption (known after apply)

      + ttl (known after apply)
    }

  # aws_iam_policy.datacenter_policy will be created
  + resource "aws_iam_policy" "datacenter_policy" {
      + arn               = (known after apply)
      + attachment_count = (known after apply)
      + id                = (known after apply)
      + name              = "datacenter-readonly-policy"
      + name_prefix       = (known after apply)
      + path              = "/"
      + policy            = (known after apply)
      + policy_id         = (known after apply)
      + tags_all          = (known after apply)
    }

  # aws_iam_role.datacenter_role will be created
  + resource "aws_iam_role" "datacenter_role" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "ec2.amazonaws.com"
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + name                  = "datacenter-role"
      + path                  = "/"
      + tags_all              = (known after apply)
    }

  # aws_iam_role_policy_attachment.attach_policy will be created
  + resource "aws_iam_role_policy_attachment" "attach_policy" {
      + id        = (known after apply)
      + policy_arn = aws_iam_policy.datacenter_policy.arn
      + role      = aws_iam_role.datacenter_role.name
    }

Plan: 4 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_dynamodb_table.datacenter_table: Creating...
aws_iam_role.datacenter_role: Creating...
aws_iam_policy.datacenter_policy: Creating...
aws_dynamodb_table.datacenter_table: Creation complete after 2s [id=datacenter-table]
aws_iam_role.datacenter_role: Creation complete after 1s [id=datacenter-role]
aws_iam_policy.datacenter_policy: Creation complete after 1s [id=arn:aws:iam::123456789012:policy/datacenter-readonly-policy]
aws_iam_role_policy_attachment.attach_policy: Creating...
aws_iam_role_policy_attachment.attach_policy: Creation complete after 0s [id=datacenter-role-arn:aws:iam::123456789012:policy/datacenter-readonly-policy]

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

kke_dynamodb_table = "datacenter-table"
kke_iam_role_name = "datacenter-role"
kke_iam_policy_name = "datacenter-readonly-policy"

Terraform 적용 출력

aws_iam_role.datacenter_role will be created
  + arn                    = (known after apply)
  + assume_role_policy     = (known after apply)
  + create_date            = (known after apply)
  + description            = (known after apply)
  + force_detach_policies  = false
  + id                     = (known after apply)
  + inline_policy          = (known after apply)
  + max_session_duration   = 3600
  + name                   = "datacenter-role"
  + name_prefix            = (known after apply)
  + path                   = "/"
  + tags_all               = (known after apply)
  + unique_id              = (known after apply)

aws_dynamodb_table.datacenter_table will be created
  + arn                     = (known after apply)
  + billing_mode            = "PAY_PER_REQUEST"
  + hash_key                = "id"
  + id                      = (known after apply)
  + name                    = "datacenter-table"
  + read_capacity           = (known after apply)
  + stream_arn              = (known after apply)
  + stream_enabled          = false
  + stream_view_type        = (known after apply)
  + ttl                     = (known after apply)
  + write_capacity          = (known after apply)

aws_iam_policy.datacenter_policy will be created
  + arn                     = (known after apply)
  + description             = (known after apply)
  + id                      = (known after apply)
  + name                    = "datacenter-readonly-policy"
  + path                    = "/"
  + policy                  = (known after apply)
  + policy_id               = (known after apply)

aws_iam_role_policy_attachment.attach_policy will be created
  + id          = (known after apply)
  + policy_arn  = (known after apply)
  + role        = "datacenter-role"

Plan: 4 to add, 0 to change, 0 to destroy.

### 출력값 변경
- `kke_dynamodb_table`  = "datacenter-table"
- `kke_iam_policy_name` = "datacenter-readonly-policy"
- `kke_iam_role_name`   = "datacenter-role"

이 작업을 수행하시겠습니까?  
Terraform은 위에 설명된 작업을 수행합니다.  
승인하려면 **yes**만 입력 가능합니다.

값을 입력하세요: **yes**

aws_iam_role.datacenter_role: 생성 중...
aws_dynamodb_table.datacenter_table: 생성 중...
aws_iam_role.datacenter_role: 0초 후 생성 완료 [id=datacenter-role]
aws_dynamodb_table.datacenter_table: 3초 후 생성 완료 [id=datacenter-table]
aws_iam_policy.datacenter_policy: 생성 중...
aws_iam_policy.datacenter_policy: 0초 후 생성 완료 [id=arn:aws:iam::000000000000:policy/datacenter-readonly-policy]
aws_iam_role_policy_attachment.attach_policy: 생성 중...
aws_iam_role_policy_attachment.attach_policy: 0초 후 생성 완료 [id=datacenter-role-20260129134119333700000001]

적용 완료! 리소스: 추가 4개, 변경 0개, 삭제 0개.

**출력값**

- `kke_dynamodb_table` = "datacenter-table"
- `kke_iam_policy_name` = "datacenter-readonly-policy"
- `kke_iam_role_name` = "datacenter-role"

> **Source:** ...

## 단계별 설명 (간단하고 명확하게)

### 왜 `PAY_PER_REQUEST`를 DynamoDB에 사용하나요?

- **billing_mode = "PAY_PER_REQUEST"**
  - 용량 계획이 필요 없음  
  - 최소한의 설정  
  - 실험실 및 가벼운 워크로드에 가장 저렴하고 간단함  

### DynamoDB에 해시 키가 필요한 이유

- 모든 DynamoDB 테이블은 최소 하나의 기본 키가 있어야 합니다.  
- `hash_key = "id"`를 사용해 테이블을 최소화하고 유효하게 만들었습니다.

### IAM 역할 vs. IAM 정책 (매우 중요)

| 구성 요소 | 기호 | 의미 |
|-----------|------|------|
| **IAM 정책** | 📜 | 허용되는 작업 |
| **IAM 역할**   | 🪪 | 해당 권한을 부여받는 주체 |
| **연결** | 🔗 | 정책을 역할에 연결함 |

### 왜 이 DynamoDB 작업만 허용하나요?

- `dynamodb:GetItem`  
- `dynamodb:Scan`  
- `dynamodb:Query`  

이들은 **읽기 전용** 작업이며(쓰기, 삭제, 테이블 변경 없음) 최소 권한 원칙을 따릅니다.

### 정책을 하나의 테이블에만 제한하는 이유

```hcl
resource = aws_dynamodb_table.datacenter_table.arn
  • 역할이 오직 datacenter-table에만 접근할 수 있습니다.
  • 다른 DynamoDB 테이블을 읽을 수 없으므로 강력한 보안 경계가 형성됩니다.

terraform apply 시 어떤 일이 일어나나요?

  1. Terraform이 DynamoDB 테이블을 생성합니다.
  2. Terraform이 IAM 역할을 생성합니다.
  3. Terraform이 IAM 정책을 생성합니다.
  4. Terraform이 정책을 역할에 연결합니다.
  5. AWS가 읽기 전용 접근을 적용합니다.
  6. Terraform이 리소스 이름을 출력합니다.

쉬운 기억 모델

  • DynamoDB 테이블 = 📦 데이터 저장소
  • IAM 역할 = 👤 정체성
  • IAM 정책 = 🔑 권한
  • 연결 = 🔗 연결 고리
  • 읽기 전용 = 👀 안전한 접근

피해야 할 흔한 실수

  • ❌ DynamoDB 리소스 ARN에 * 사용
  • ❌ 쓰기 권한 부여
  • ❌ 해시 키 누락
  • ❌ 이름을 하드코딩 (변수 또는 name_prefix 사용)
  • ❌ 출력 이름 불일치
0 조회
Back to Blog

관련 글

더 보기 »

sunpeak은 MCP 앱에 전념한다

개요: MCP Apps는 이제 ChatGPT, Claude, Goose 및 VS Code에서 실행됩니다. Claude는 1월 26일에 MCP App 지원을 발표했으며, ChatGPT는 2월 4일에 이를 따랐습니다. 2월 현재…