정적 DB 자격증명의 문제
애플리케이션이 데이터베이스에 접속할 때 흔히 쓰는 방식은 .env나 설정 서버에 고정된 계정과 비밀번호를 넣어두는 것이다. 이 방식은 세 가지 문제를 안고 있다. 첫째, 비밀번호가 좀처럼 바뀌지 않아 유출 시 노출 기간이 사실상 무제한이다. 둘째, 여러 서비스가 같은 계정을 공유해 사고 발생 시 어느 서비스가 원인인지 추적하기 어렵다. 셋째, 로테이션을 하려면 배포와 재기동이 필요해 실무에서는 미뤄지기 일쑤다.
HashiCorp Vault의 데이터베이스 시크릿 엔진은 이 문제를 근본적으로 바꾼다. Vault가 요청 시점에 DB에 임시 계정을 직접 생성하고, TTL이 지나면 자동으로 삭제한다. 애플리케이션은 더 이상 영구 비밀번호를 들고 있지 않는다.
동작 원리
흐름은 단순하다. Vault에 관리자 권한 계정을 한 번 등록해두면, 이후 애플리케이션은 Vault에 "DB 자격증명을 달라"고 요청한다. Vault는 미리 정의한 role의 SQL 문을 실행해 랜덤한 사용자명·비밀번호를 가진 계정을 만들고, 그 값을 반환하면서 lease를 발급한다. lease TTL이 끝나면 Vault가 revocation SQL을 실행해 계정을 지운다.
| 구분 | 정적 자격증명 | Vault 동적 시크릿 |
|---|---|---|
| 수명 | 사실상 영구 | TTL 기반(예: 1시간) |
| 계정 소유 | 서비스 간 공유 | 요청마다 고유 |
| 로테이션 | 수동, 재배포 필요 | 자동, 무중단 |
| 감사 추적 | 어려움 | lease 단위로 명확 |
시크릿 엔진과 커넥션 설정
PostgreSQL을 예로 설정한다. Vault에 붙일 관리 계정은 다른 사용자를 만들고 삭제할 수 있는 권한이 있어야 한다. 커넥션 URL에는 Vault가 접속에 쓸 root 계정을 넣는다.
vault secrets enable database
vault write database/config/app-postgres \
plugin_name=postgresql-database-plugin \
allowed_roles="app-readwrite" \
connection_url="postgresql://{{username}}:{{password}}@db.internal:5432/appdb?sslmode=require" \
username="vault_admin" \
password="$VAULT_DB_ADMIN_PW"
# root 계정 비밀번호를 Vault가 즉시 교체하여 사람도 모르게 만든다
vault write -force database/rotate-root/app-postgres
마지막 rotate-root는 실무에서 자주 놓치는 단계다. 이걸 실행하면 초기 설정에 쓴 관리자 비밀번호를 Vault가 아무도 모르는 값으로 바꿔버려, 설정 파일에 남은 비밀번호로는 더 이상 접속할 수 없게 된다.
role 정의
role은 어떤 SQL로 계정을 만들고 얼마나 유지할지 정의한다. 권한은 최소한으로 부여하는 것이 핵심이다.
vault write database/roles/app-readwrite \
db_name=app-postgres \
creation_statements="
CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
revocation_statements="
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"{{name}}\";
DROP ROLE IF EXISTS \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
이제 vault read database/creds/app-readwrite를 호출할 때마다 새 계정이 발급된다. default_ttl은 기본 수명, max_ttl은 갱신을 반복해도 넘을 수 없는 상한이다.
애플리케이션 연동
애플리케이션은 시작 시 자격증명을 받아 커넥션 풀을 구성하고, lease 만료 전에 갱신하거나 재발급받아야 한다. 아래는 파이썬에서 발급받는 최소 예시다.
import hvac, psycopg2
client = hvac.Client(url="https://vault.internal:8200", token=vault_token)
resp = client.secrets.database.generate_credentials(name="app-readwrite")
data = resp["data"]
lease_id = resp["lease_id"]
lease_ttl = resp["lease_duration"] # 초 단위
conn = psycopg2.connect(
host="db.internal", dbname="appdb",
user=data["username"], password=data["password"], sslmode="require",
)
# TTL의 2/3 지점에서 재발급하도록 스케줄링한다 (예: 백그라운드 태스크)
중요한 점은 발급된 계정이 언젠가 사라진다는 전제로 코드를 짜야 한다는 것이다. 커넥션이 끊겼을 때 재연결하면서 새 자격증명을 받아오는 로직이 필요하다.
운영 시 주의점
몇 가지 함정이 있다. lease 관리를 애플리케이션이 하지 않으면 TTL 만료 후 갑자기 DB 인증 오류가 난다. 재발급 시점은 만료 직전이 아니라 여유를 두고(예: TTL의 2/3 지점) 잡아야 한다.
- Vault 장애 시 신규 발급이 막히므로, 기존 lease가 유효한 동안 서비스가 버틸 수 있도록 TTL을 너무 짧게 잡지 않는다.
- PostgreSQL은 계정을 DROP할 때 해당 계정이 소유한 객체가 있으면 실패한다. 애플리케이션 계정이 테이블을 생성하지 않도록 권한을 분리해야 revocation이 깔끔하게 동작한다.
- 계정명 최대 길이 제약이 있는 DB에서는 role의
username_template로 접두어와 길이를 조정한다.
동적 시크릿은 "비밀번호를 안전하게 보관"하는 문제를 "비밀번호를 애초에 오래 두지 않는" 문제로 바꾼다. 초기 도입 비용은 있지만, 유출 대응과 감사 부담이 크게 줄어든다.