🚨 이런 경험 있으신가요?
DB 비밀번호를 코드에 그대로 박아두셨나요?
.env 파일이 Git에 올라간 적 있으신가요?
회사 보안 검사에서 이런 부분들이 적발되면 프로젝트가 중단될 수 있습니다.
이 글에서는 실무에서 바로 적용 가능한 안전한 인증 시스템 구축 방법을 다룹니다.
📌 목차
1️⃣ 현재 개발 현장의 문제점
😱 일반적인 개발 현장의 모습
# 하드코딩 방식
DB_HOST = "192.168.1.100"
DB_USER = "admin"
DB_PASSWORD = "P@ssw0rd123!" # 😱 코드에 그대로 노출
# .env 파일 방식
DB_PASSWORD=P@ssw0rd123!
API_KEY=sk-1234567890abcdef
JWT_SECRET=my-super-secret-key
⚠️ 이 방식이 위험한 이유
1) 코드 유출 = 비밀번호 유출
- Git에 실수로 커밋
- 퇴사자의 로컬 저장소
- 협력사/외주 개발자 공유
2) 암호화의 함정
- 암호화 키는 어디에 저장? → 결국 같은 문제 반복
- "암호화 키를 암호화하는 키"의 무한 반복
3) 권한 관리 불가능
- 서비스 A는 DB만, 서비스 B는 API만 접근해야 하는데
- 모든 서비스가 모든 정보에 접근 가능
4) 감사 추적 불가
- 누가, 언제, 어떤 비밀번호를 사용했는지 모름
2️⃣ 인증 솔루션 비교 - 어떤 걸 선택해야 할까?
🔍 주요 솔루션 한눈에 비교
| 솔루션 | 특징 | 장점 | 단점 | 추천 상황 |
|---|---|---|---|---|
| Spring Security | 코드 내장형 | 완전한 커스터마이징 | 구현 복잡도 높음 | 백엔드 직접 개발 |
| Keycloak | 인증 서버 분리 | SSO, 표준 준수 | 서버 운영 필요 | 여러 서비스 통합 |
| Auth0 | SaaS 외주형 | 빠른 구현 | 비용, 외부 의존 | 스타트업 빠른 출시 |
| Vault | 비밀 관리 중심 | Secret 관리 특화 | 인증은 부가 기능 | 민감 정보 관리 |
💡 솔루션별 역할 정리
Spring Security → 사용자 로그인 처리
Keycloak → 통합 인증 서버 (SSO)
Vault → 비밀 정보 관리 (DB 비밀번호, API 키)
👉 이들은 함께 사용 가능! (상호보완)
🎯 상황별 추천
단일 서비스, 직접 구현하고 싶다면
→ Spring Security
여러 서비스에서 통합 로그인 필요
→ Keycloak
개발 시간 단축, 비용 OK
→ Auth0
DB 비밀번호, API 키 등 민감 정보 관리
→ Vault (이 글의 주제)
3️⃣ Vault 인증 방식 비교
🔐 Vault에서 지원하는 인증 방식들
| 방식 | 적용 대상 | 장점 | 단점 | 언제 쓸까? |
|---|---|---|---|---|
| Token | 개발/테스트 | 단순함 | 보안 취약 | 로컬 개발용 |
| AppRole | 서버 앱 | 머신 인증 최적 | SecretID 배포 필요 | ⭐ 일반 서버/VM |
| Kubernetes | K8s Pod | 자동 인증 가능 | K8s 환경 필수 | ⭐ 쿠버네티스 환경 |
| JWT/OIDC | SSO 연동 | 기존 인증 활용 | 설정 복잡 | SSO 연동 필요 시 |
| LDAP | 운영자 | 사내 계정 연동 | 앱 인증 부적합 | 운영자 로그인용 |
💡 각 인증 방식 쉽게 이해하기
1️⃣ Token 방식
"이미 발급받은 출입증으로 바로 들어가기"
# Vault가 미리 만들어준 토큰
vault_token = "s.1234567890abcdef"
# 바로 접속 (로그인 과정 없음)
client = hvac.Client(url='http://vault:8200', token=vault_token)
언제 쓰나요?
- 로컬 개발할 때
- 빠른 테스트할 때
단점:
- 토큰 유출되면 끝
- 운영 환경에서는 위험
2️⃣ AppRole 방식 (⭐ 가장 많이 씀)
"앱 전용 ID/PW로 로그인하기"
# 앱의 신분증
role_id = "abc-123-def-456" # 공개 가능
secret_id = "xyz-789-ghi-012" # 비공개
# 로그인해서 토큰 받기
client.auth.approle.login(role_id=role_id, secret_id=secret_id)
비유:
- RoleID = 사원증 번호 (누가 봐도 됨)
- SecretID = 사원증 비밀번호 (절대 비공개)
언제 쓰나요?
- Python 서버, Java 서버 등
- VM이나 일반 서버에서 실행되는 앱
3️⃣ Kubernetes 방식
"쿠버네티스가 자동으로 신분 증명해주기"
# Pod 안에서는 자동으로 인증 정보가 있음
# 따로 ID/PW 안 넣어도 됨!
client.auth.kubernetes.login(
role='my-app-role',
jwt=open('/var/run/secrets/kubernetes.io/serviceaccount/token').read()
)
비유:
- 회사 건물(쿠버네티스) 안에 있으면 자동으로 출입 가능
- 따로 사원증 챙길 필요 없음
언제 쓰나요?
- Docker 컨테이너로 실행
- Kubernetes(K8s) 환경
4️⃣ JWT/OIDC 방식
"구글/회사 계정으로 로그인하기"
# 구글/깃허브/회사 SSO 로그인 토큰으로 인증
jwt_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
client.auth.jwt.login(
role='my-role',
jwt=jwt_token
)
비유:
- "구글 계정으로 로그인" 버튼 누르는 것
- 이미 로그인한 정보 재활용
언제 쓰나요?
- 회사에 SSO(통합 로그인)가 있을 때
- GitHub Actions, GitLab CI 같은 곳에서
5️⃣ LDAP 방식
"회사 계정(Active Directory)으로 로그인"
# 회사 이메일/비밀번호로 로그인
client.auth.ldap.login(
username='hong.gildong@company.com',
password='my-company-password'
)
비유:
- 회사 PC 켤 때 쓰는 그 계정
언제 쓰나요?
- 운영자가 Vault UI에 접속할 때
- 사람이 직접 로그인할 때
- 앱 자동 인증에는 부적합
📌 권장 선택 기준
일반 서버/VM에서 Python 앱 실행
→ AppRole (가장 무난)
Kubernetes 환경
→ Kubernetes Auth
운영자가 UI 접근
→ OIDC 또는 LDAP
개발/테스트 환경
→ Token (임시로만)
4️⃣ Vault란 무엇인가?
🎯 핵심 개념
Vault는 "비밀번호 저장소"가 아니라 "비밀번호를 없애는 시스템"입니다.
❌ 기존 방식: 앱 안에 비밀 정보 포함 → 유출 시 끝
✅ Vault 방식: 비밀은 Vault에만, 앱은 필요할 때만 요청
→ 유출돼도 제한적, 추적 가능
🔄 Vault 동작 구조
┌─────────────┐
│ Python 앱 │ ─────► 1. 로그인 요청 (RoleID + SecretID)
└─────────────┘
▼
┌─────────────────┐
│ Vault Server │
└─────────────────┘
│
│ 2. 인증 성공 → Token 발급
│
▼
┌─────────────┐
│ Python 앱 │ ◄───── 3. Secret 반환 (Token으로 요청)
└─────────────┘
┌─────────────────┐
│ DB 비밀번호 │
│ API Key │
│ 암호화 키 │
└─────────────────┘
💎 Vault의 핵심 기능
1) 정적 Secret 관리
- 현재 사용 중인 비밀번호를 안전하게 보관
- 버전 관리 지원 (잘못 바꿨을 때 롤백 가능)
2) 동적 Secret 생성 ⭐ 핵심!
- 필요할 때마다 임시 DB 계정 생성
- 1시간 뒤 자동 삭제
- 유출돼도 의미 없음
3) 권한 관리 (Policy)
- 서비스별로 접근 가능한 Secret 제한
- 최소 권한 원칙 적용
4) 감사 로그
- 누가, 언제, 무엇을 가져갔는지 기록
- 보안 사고 시 추적 가능
5️⃣ Python에서 Vault 도입하기
📝 현재 코드 (Before)
import os
from dotenv import load_dotenv
load_dotenv()
DB_PASSWORD = os.getenv('DB_PASSWORD') # .env에서 읽음
⚙️ Step 1: Vault 서버 준비
# Docker로 개발용 Vault 실행
docker run -d --name=vault \
-p 8200:8200 \
--cap-add=IPC_LOCK \
-e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' \
vault
export VAULT_ADDR='http://localhost:8200'
vault login myroot
📦 Step 2: Secret 저장
vault kv put secret/myapp/db \
host="db.internal" \
port="5432" \
username="myuser" \
password="MySecurePassword123!"
🔑 Step 3: AppRole 설정
# AppRole 인증 활성화
vault auth enable approle
# Role 생성
vault write auth/approle/role/myapp \
secret_id_ttl=24h \
token_ttl=1h \
token_max_ttl=4h \
policies="myapp-policy"
# RoleID 확인
vault read auth/approle/role/myapp/role-id
# SecretID 발급
vault write -f auth/approle/role/myapp/secret-id
🐍 Step 4: Python 코드 수정 (After)
import hvac
import os
class VaultClient:
def __init__(self):
self.client = hvac.Client(url=os.getenv('VAULT_ADDR'))
self._authenticate()
def _authenticate(self):
"""AppRole로 로그인"""
role_id = os.getenv('VAULT_ROLE_ID')
secret_id = os.getenv('VAULT_SECRET_ID')
self.client.auth.approle.login(
role_id=role_id,
secret_id=secret_id
)
def get_db_credentials(self):
"""DB 접속 정보 가져오기"""
secret = self.client.secrets.kv.v2.read_secret_version(
path='myapp/db',
mount_point='secret'
)
return secret['data']['data']
# 사용 예시
vault = VaultClient()
db_config = vault.get_db_credentials()
DB_HOST = db_config['host']
DB_PORT = db_config['port']
DB_USER = db_config['username']
DB_PASSWORD = db_config['password']
🌍 Step 5: 환경변수 정리
# .env 파일 (After) - 민감 정보 제거됨!
VAULT_ADDR=https://vault.company.com
VAULT_ROLE_ID=abc-123-def-456
# VAULT_SECRET_ID는 배포 시스템에서 안전하게 주입
6️⃣ 실무 적용 예제
🎯 시나리오 1: FastAPI + PostgreSQL (동적 계정)
from fastapi import FastAPI
from sqlalchemy import create_engine
import hvac
app = FastAPI()
class VaultDB:
def __init__(self):
self.vault = hvac.Client(url='https://vault.company.com')
self.vault.auth.approle.login(
role_id=ROLE_ID,
secret_id=SECRET_ID
)
def get_engine(self):
# Vault가 즉시 생성하는 임시 DB 계정
creds = self.vault.read('database/creds/myapp-role')
username = creds['data']['username'] # v-approle-abc123
password = creds['data']['password'] # 랜덤 생성
db_url = f"postgresql://{username}:{password}@{DB_HOST}/{DB_NAME}"
return create_engine(db_url)
vault_db = VaultDB()
engine = vault_db.get_engine()
장점:
- 유출돼도 1시간 후 자동 삭제
- 각 앱마다 다른 계정 사용
- 접근 로그 추적 가능
🌐 시나리오 2: 외부 API 키 관리
import requests
def call_external_api():
# API 키를 Vault에서 가져오기
secret = vault_client.secrets.kv.v2.read_secret_version(
path='external/api-keys'
)
api_key = secret['data']['data']['openai_key']
response = requests.post(
'https://api.openai.com/v1/chat/completions',
headers={'Authorization': f'Bearer {api_key}'},
json=payload
)
return response.json()
✅ 마치며
보안은 "귀찮은 규제"가 아니라 "시스템의 신뢰성을 높이는 투자"입니다.
Vault와 같은 도구를 도입하면:
- ✅ 개발자는 안전하게 개발
- ✅ 운영팀은 안심하고 운영
- ✅ 회사는 규정 준수
모두가 win-win할 수 있습니다.
처음엔 복잡해 보이지만, 한 번 구축하면 장기적으로 훨씬 안전하고 관리하기 쉬운 시스템을 운영할 수 있습니다.
📚 참고 자료
#Vault #보안 #Python #DevSecOps #Secret관리 #AppRole #보안검사 #Keycloak #인증시스템 #하드코딩
'DevOps > Security' 카테고리의 다른 글
| Sparrow 보안 검사 탈락 이유: Git에 올린 DB 비밀번호 암호화로 해결하기 (0) | 2026.04.28 |
|---|