관리 메뉴

Cloud Is My Life

[26WKRD1] MySQL RDS에 IAM 인증으로 연결하기 본문

카테고리 없음

[26WKRD1] MySQL RDS에 IAM 인증으로 연결하기

CloudBackend 2026. 4. 1. 21:16

 

개요

RDS에 연결할 때 보통 DB 비밀번호를 환경변수나 Secrets Manager에 저장해서 씁니다. 그런데 AWS에는 IAM 인증이라는 방식이 있습니다. 비밀번호 대신 AWS IAM 토큰을 임시 자격증명으로 사용하는 방식입니다.

비밀번호를 코드나 환경변수에서 관리하지 않아도 되고, 토큰은 15분마다 만료되기 때문에 탈취되더라도 피해가 제한적입니다. IAM 정책으로 DB 접근을 중앙에서 관리할 수 있다는 것도 장점입니다.

이 글에서는 AWS Management Console에서 직접 구성하는 법을 단계별로 다룹니다. Aurora MySQL과 Classic MySQL 두 가지를 모두 커버하고, RDS Proxy 연동까지 이어서 설명합니다.

 

Aurora MySQL 기준

클러스터 생성

RDS 콘솔에서 데이터베이스 생성 → 엔진 Amazon Aurora → Aurora (MySQL Compatible) → 버전은 Aurora MySQL 3.x (MySQL 8.0 호환)을 선택합니다.

연결 설정에서는 앱 서버와 동일한 VPC, 프라이빗 서브넷을 지정하고, 퍼블릭 액세스는 반드시 '아니오'로 설정합니다.

IAM DB 인증 활성화

추가 구성을 펼쳐서 "IAM DB 인증" 체크박스를 활성화합니다. 기존 클러스터라면 클러스터 수정 → IAM DB 인증 → 즉시 적용으로 켤 수 있습니다.

IAM DB 유저 생성

클러스터 생성 후 MySQL에 접속해서 IAM 전용 유저를 만들어야 합니다.

CREATE USER 'myapp'@'%' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';
GRANT ALL PRIVILEGES ON mydb.* TO 'myapp'@'%';
FLUSH PRIVILEGES;

-- 확인
SELECT user, plugin FROM mysql.user WHERE user = 'myapp';
-- plugin 컬럼이 'AWSAuthenticationPlugin' 이어야 합니다

 

이 유저는 비밀번호가 없습니다. AWSAuthenticationPlugin이 IAM 토큰을 검증하는 역할을 합니다.

IAM 정책 연결

앱 서버(EC2, ECS, Lambda 등)의 IAM Role에 아래 정책을 추가합니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "rds-db:connect",
      "Resource": "arn:aws:rds-db:{region}:{account-id}:dbuser:{cluster-resource-id}/myapp"
    }
  ]
}

 

여기서 cluster-resource-id는 RDS 콘솔 → 클러스터 → 구성 탭 → "리소스 ID" 에서 확인할 수 있습니다. cluster-XXXXXXXXX 형식이며, 엔드포인트 주소와는 다른 값입니다.

Classic MySQL이라면 인스턴스의 리소스 ID를 사용하고, 형식은 db-XXXXXXXXX입니다.

 

SSL 인증서 준비

IAM 인증은 SSL/TLS가 필수입니다. AWS가 강제합니다. SSL 없이 연결하면 Access denied 에러가 발생하는데, IAM 설정 문제와 구분이 안 돼서 디버깅이 꽤 고통스러울 수 있습니다.

wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem

global-bundle.pem을 받아두면 모든 리전, 모든 CA를 커버합니다. 컨테이너나 Lambda 배포 시 이미지에 같이 포함시키거나 경로를 환경변수로 관리하는 것이 일반적입니다.


2단계 — RDS Proxy 구성

IAM 인증만 사용하면 한 가지 문제가 있습니다. IAM 토큰 기반 연결은 초당 최대 200개로 제한됩니다. Lambda처럼 동시 실행이 많은 환경에서는 금방 한계에 부딪힙니다. RDS Proxy를 사용하면 이 문제를 해결할 수 있습니다.

Proxy의 동작 방식을 먼저 이해하는 것이 중요합니다.

앱 → (IAM 토큰 + SSL) → RDS Proxy → (Secrets Manager 자격증명) → RDS
 

앱과 Proxy 사이에만 IAM 인증이 적용되고, Proxy와 RDS 사이는 Secrets Manager에 저장한 마스터 자격증명으로 연결합니다. Proxy가 Connection Pool을 직접 관리하기 때문에 앱에서는 토큰 갱신 걱정을 덜 수 있습니다.

Secrets Manager에 DB 자격증명 저장

Proxy가 RDS에 연결할 때 사용할 마스터 자격증명을 Secrets Manager에 저장합니다.

Secrets Manager 콘솔 → 새 보안 암호 저장 → "Amazon RDS 데이터베이스 자격 증명" 선택 → 마스터 사용자명/비밀번호 입력 → 해당 RDS 인스턴스 선택 → 이름은 rds/myapp/master 형식으로 저장합니다.

주의: Secrets Manager에는 IAM 유저(myapp)가 아닌 마스터 자격증명을 저장합니다.

Proxy 생성

RDS 콘솔 → 프록시 → 프록시 생성에서 아래와 같이 설정합니다.

  • 엔진: MySQL (Aurora MySQL도 동일하게 MySQL 선택)
  • IAM 인증 필요 체크 ✅
  • RDS DB 인스턴스: 위에서 만든 RDS/Aurora 선택
  • Secrets Manager 암호: 위에서 만든 시크릿 선택
  • IAM 역할: "새 IAM 역할 생성" 선택 (자동으로 적절한 권한을 가진 Role 생성)
  • VPC, 서브넷, 보안 그룹: RDS와 동일하게 구성

생성 후 "사용 가능" 상태가 되기까지 약 5~10분 소요됩니다.

IAM 정책 ARN 변경

Proxy를 사용하면 앱 서버의 IAM 정책에서 Resource ARN을 Proxy 기준으로 변경해야 합니다. 이 부분을 빠뜨리는 경우가 많습니다.

 
 
json
{
  "Effect": "Allow",
  "Action": "rds-db:connect",
  "Resource": "arn:aws:rds-db:{region}:{account-id}:dbuser:{proxy-resource-id}/myapp"
}

 

proxy-resource-id는 RDS 콘솔 → 프록시 → 프록시 ARN의 마지막 부분입니다. prx-XXXXXXXXX 형식입니다.


3단계 — Python 연결 코드

SQLAlchemy + 토큰 자동 갱신

장기 실행 앱(서버, ECS 등)에서 권장하는 패턴입니다.

import boto3
from sqlalchemy import create_engine, event
from sqlalchemy.pool import QueuePool

REGION = "ap-northeast-2"
HOST = "myapp-proxy.proxy-xxxxxx.ap-northeast-2.rds.amazonaws.com"  # Proxy 엔드포인트
PORT = 3306
DB_USER = "myapp"
DB_NAME = "mydb"
SSL_CA = "/path/to/global-bundle.pem"

def get_iam_token():
    client = boto3.client("rds", region_name=REGION)
    return client.generate_db_auth_token(
        DBHostname=HOST,
        Port=PORT,
        DBUsername=DB_USER,
        Region=REGION,
    )

def get_engine():
    engine = create_engine(
        f"mysql+pymysql://{DB_USER}@{HOST}:{PORT}/{DB_NAME}",
        connect_args={"ssl": {"ca": SSL_CA}},
        poolclass=QueuePool,
        pool_pre_ping=True,
        pool_recycle=1800,  # Proxy 사용 시 30분도 OK
        pool_size=10,
        max_overflow=20,
    )

    # 새 커넥션 생성 시마다 토큰 자동 갱신
    @event.listens_for(engine, "do_connect")
    def inject_token(dialect, conn_rec, cargs, cparams):
        cparams["password"] = get_iam_token()

    return engine

engine = get_engine()
 

do_connect 이벤트 리스너를 걸어두면 SQLAlchemy가 새 커넥션을 만들 때마다 토큰을 새로 발급합니다. 토큰 만료 걱정 없이 사용할 수 있습니다.

Lambda 환경

Lambda는 RDS Proxy가 사실상 필수입니다. Lambda 동시 실행 수만큼 DB 직접 연결이 열리면 max_connections가 금방 터집니다.

import boto3
import pymysql
import os

_conn = None

def get_conn():
    global _conn
    if _conn is None or not _conn.open:
        token = boto3.client("rds", region_name=os.environ["AWS_REGION"]).generate_db_auth_token(
            DBHostname=os.environ["DB_HOST"],
            Port=3306,
            DBUsername=os.environ["DB_USER"],
        )
        _conn = pymysql.connect(
            host=os.environ["DB_HOST"],
            user=os.environ["DB_USER"],
            password=token,
            database=os.environ["DB_NAME"],
            ssl={"ca": "/var/task/global-bundle.pem"},
        )
    return _conn

def handler(event, context):
    conn = get_conn()
    with conn.cursor() as cur:
        cur.execute("SELECT NOW()")
        return {"result": str(cur.fetchone())}

 

전역 변수로 커넥션을 재사용하는 패턴입니다. Lambda 컨테이너가 살아있는 동안 커넥션을 유지합니다.


자주 만나는 에러

Access denied for user 'myapp' IAM 정책 누락, DB 유저 설정 오류, SSL 미설정 중 하나 셋 다 순서대로 확인
SSL connection error SSL 없이 연결 시도 ssl={"ca": "..."} 추가
Token has expired 15분 지난 토큰 재사용 pool_recycle=600 설정 또는 do_connect 이벤트 리스너 사용
Resource ID not found in IAM policy IAM ARN에 잘못된 Resource ID 입력 콘솔에서 리소스 ID 재확인, Proxy 쓰면 Proxy ID로 교체

정리

Aurora vs Classic IAM 인증 방법은 동일, ARN의 Resource ID 형식만 다름
IAM DB 유저 AWSAuthenticationPlugin으로 생성, 비밀번호 없음
IAM 정책 Resource ARN에 DB/Proxy Resource ID 정확히 입력
SSL 필수, global-bundle.pem 사용 권장
RDS Proxy 앱→Proxy는 IAM, Proxy→RDS는 Secrets Manager 마스터 자격증명
Lambda RDS Proxy 필수, 전역 커넥션 재사용

IAM 인증 자체는 설정이 복잡해 보이지만, 한 번 구성해두면 비밀번호 로테이션 걱정 없이 IAM 정책 하나로 DB 접근을 깔끔하게 관리할 수 있습니다.