한줄 요약:
API 키·비밀번호는 코드에 남기지 말고 .env/비밀저장소, 키 회전, 최소권한, 로그 마스킹으로 지킨다.
👉 “유출돼도 피해 최소화, 유출되기 전에 탐지”가 목표!
1. 목표 : 보안·비밀관리
.env/환경변수/비밀저장소(Secret Manager)로 키 안전 보관- 키 회전(교체) 절차 자동화
- 최소 권한(Least Privilege) 원칙 적용
- 로그 민감정보 마스킹 & 데이터 암호화
1) 코드에 비밀번호 금지 — .env + gitignore
✅ 기본 세팅
project/
├─ .env # 키/토큰만 저장 (배포용은 별도 관리)
├─ .env.sample # 키 이름만 예시, 값은 비워둠
├─ .gitignore # .env 업로드 금지
└─ app.py
.gitignore
.env
*.pem
*.key
.env.sample (공유용)
OPENAI_API_KEY=
WP_URL=
WP_USER=
WP_APP_PASS=
SLACK_WEBHOOK=
파이썬 로드
# app.py
import os
from dotenv import load_dotenv
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
raise RuntimeError("OPENAI_API_KEY missing")
팀에선 실제 값 없는
.env.sample만 Git에 올려서 구성 힌트만 제공.
2) 배포 환경별 분리 (.env.prod / .env.dev)
- 로컬:
.env.dev - 서버/배포:
.env.prod(서버 환경변수로 주입, 파일 미배포)
# env_loader.py
import os
from dotenv import load_dotenv
PROFILE = os.getenv("PROFILE", "dev") # dev / prod
load_dotenv(f".env.{PROFILE}", override=True)
서버에선:
PROFILE=prod python orchestrator.py
3) 키 회전(Rotation) — “교체가 쉬워야 안전하다”
절차(간단)
- 새 키 발급 →
.env에*_NEW로 추가 - 코드에서 우선순위:
*_NEW있으면 새 키 사용 - 배포/검증 후, 기존 키 폐기(비활성화)
.env정리
# secrets.py
import os
def get_key():
new = os.getenv("OPENAI_API_KEY_NEW")
cur = os.getenv("OPENAI_API_KEY")
return new or cur
예약 회전: 월 1회 크론으로 새 키 생성→배포→구키 폐기 자동 스크립트화 권장.
4) 최소 권한(Least Privilege) 적용 팁
| 대상 | 권한 축소 예시 |
|---|---|
| 워드프레스 앱 비번 | **필요 범위(글 생성/미디어 업로드)**만 허용, 관리 기능 금지 |
| 클라우드 키(스토리지/DB) | 읽기 전용/특정 버킷·테이블 한정 |
| 슬랙 웹훅 | 채널 1개 한정, 회수·재발급 쉬운 구조 |
| 서버 사용자 | 서비스 계정 분리, 루트 사용 금지 |
키는 실수로 유출될 수 있다는 가정으로 설계. 유출돼도 피해가 제한되도록 권한 최소화.
5) 로그 민감정보 마스킹 (PII/Secrets)
민감정보가 로그에 절대 남지 않도록 필터를 건다.
# logging_mask.py
import logging, re
SENSITIVE = [
(re.compile(r"(sk-[A-Za-z0-9]{20,})"), "sk-****"), # 예: API Key 패턴
(re.compile(r"(\b\d{3}-\d{2}-\d{5}\b)"), "***-**-*****"), # 주민/사번 예시 패턴
(re.compile(r"(\b\d{16}\b)"), "****-****-****-****"), # 카드번호 등
]
class MaskFilter(logging.Filter):
def filter(self, record):
msg = str(record.getMessage())
for p, repl in SENSITIVE:
msg = p.sub(repl, msg)
record.msg = msg
return True
logger = logging.getLogger("secure")
handler = logging.StreamHandler()
handler.addFilter(MaskFilter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# 사용 예
logger.info("키 확인: sk-1234567890ABCDEFGHIJK, 카드 1234123412341234")
# 출력: 키 확인: sk-****, 카드 ****-****-****-****
규칙은 점진적 강화: 유출 우려가 보이면 즉시 패턴 추가.
6) 데이터 암호화(저장/전송)
- 전송 중: HTTPS(기본), SFTP/SSH
- 저장 시: 토큰/쿠키/식별자 등은 암호화 저장(복호화는 사용할 때만)
# encrypt.py - 대칭키 예시(Fernet)
from cryptography.fernet import Fernet
# 1회 생성 후 키는 안전 보관(환경변수/비밀저장소)
# key = Fernet.generate_key(); print(key)
from dotenv import load_dotenv; import os
load_dotenv()
FERNET_KEY = os.getenv("FERNET_KEY").encode()
f = Fernet(FERNET_KEY)
enc = f.encrypt("secret-data".encode())
dec = f.decrypt(enc).decode()
키는
.env에 보관하되 서버 환경변수 주입을 우선.
백업 시 키·데이터 분리 저장(동일 경로 보관 금지).
7) 비밀저장소(Secret Manager) 도입 고려
규모가 커지면 OS 환경변수만으로 부족하다.
- AWS Secrets Manager / Parameter Store
- GCP Secret Manager
- Azure Key Vault
- HashiCorp Vault
장점: IAM 연동, 액세스 로깅, 자동 회전, 버전 관리.
패턴: 애플리케이션 시작 시 비밀저장소에서 키 fetch → 메모리에만 보관.
8) CI/CD에서 비밀 다루기
- GitHub Actions/파이프라인 Secrets 기능 사용
- 배포 시
.env파일 대신 런타임 환경변수로 주입 - 로그에
set -x비활성화,echo $SECRET금지 - 실패 워크플로 로그 자동 삭제/보관기간 단축
9) 사고(유출) 대응 플랜
- 즉시 폐기: 노출된 키 비활성화(회수)
- 로테이션: 새 키 발급 → 서비스 재배포
- 감사: 최근 로그/접속기록 확인
- 차단: WAF/IP 차단·레이트 리밋 강화
- 재발 방지: 마스킹 패턴/권한/리뷰 프로세스 보강
연습 중요: 월 1회 모의 회전/폐기 훈련으로 리스크 최소화.
2. 체크리스트
- 코드/리포에 비밀번호 하드코딩 0건
.env는 Git에서 무시,.env.sample만 배포- 환경별 분리(.env.dev / .env.prod)
- 키 회전 자동화(NEW 우선, 구키 폐기)
- 최소 권한으로 API/계정 제한
- 로그 마스킹 필터 적용 및 규칙 점검
- 암호화/HTTPS 강제
- 비밀저장소/CI Secrets 사용 검토
- 유출 대응 시나리오 문서화 & 훈련
3. 요약 한 줄
“키는 언젠가 새는 것”을 전제로 보관/회전/권한/로그를 설계하면, 유출돼도 피해가 작고 복구가 빠르다. ✅
이전 강좌 👈 [운영#1] 모니터링·알림·코스트 관리
다음 강좌 👉 [운영#3] 장애 복구 자동화(리트라이·백오프·대체 경로·휴면 후 재시도)