API 키는 코드에 넣지 말고 환경별 시크릿 저장소에서 런타임에 주입해야 합니다. 이 글은 개발자와 소규모 운영팀을 위한 예방·로테이션·유출 대응 안내서입니다.
시크릿 매니저 하나로 유출을 “원천 방지”할 수는 없습니다. 애플리케이션이 키를 읽은 뒤 로그에 출력하거나, 과도한 권한을 가진 계정이 값을 조회하거나, 이미 Git 기록에 키가 남아 있다면 여전히 사고가 날 수 있습니다. 따라서 저장, 접근 통제, 탐지, 교체, 사고 대응을 한 흐름으로 운영해야 합니다.
먼저 적용할 7가지
- 소스 코드, 저장소, 티켓, 메신저에 실제 키를 넣지 않는다.
- 개발·검증·운영 환경마다 서로 다른 키와 시크릿 경로를 사용한다.
- 애플리케이션에는 필요한 시크릿을 읽는 권한만 부여한다.
- 키 값이 아니라 누가·언제·무엇을 조회하거나 변경했는지 감사 로그로 남긴다.
- 커밋 전 검사와 GitHub secret scanning·push protection을 함께 사용한다.
- 로테이션 주기는 일률적인 60일·90일이 아니라 키의 위험도, 공급자 기능, 규정, 교체 실패 영향을 기준으로 정한다.
- 노출이 의심되면 저장소 정리보다 폐기와 재발급을 먼저 한다.
AWS도 Secrets Manager 권장 사항을 환경마다 그대로 적용해야 하는 처방이 아닌 일반 지침으로 설명합니다. AWS 문서는 암호화 저장, 캐시, 로테이션, 최소권한, CloudTrail·CloudWatch 등을 통한 모니터링을 함께 다룹니다. (AWS Secrets Manager 모범 사례, 확인 2026-07-20)
.env와 .gitignore는 어디까지 안전한가
.env는 로컬 개발에서 설정을 코드 밖으로 분리하는 데 유용하지만, 시크릿 매니저가 아닙니다. 파일 권한, 백업 프로그램, 악성 확장 프로그램, 셸 기록, 지원 요청에 첨부한 압축 파일을 통해 값이 복사될 수 있습니다. 운영 서버에 .env를 수동 배포하면 누가 언제 값을 바꿨는지 추적하기도 어렵습니다.
.gitignore도 새 파일이 Git 추적 대상이 되는 것을 줄일 뿐입니다. 이미 커밋되어 추적 중인 파일은 .gitignore에 추가해도 기록에서 사라지지 않습니다. 기본 규칙은 다음처럼 둘 수 있습니다.
# 로컬 시크릿 파일
.env
.env.*
# 변수 이름만 공유하는 예시 파일은 추적
!.env.example
.env.example에는 실제 값이 아닌 눈에 띄는 자리표시자만 둡니다.
THIRD_PARTY_API_KEY=YOUR_API_KEY
다음 Node.js 예제는 환경변수 존재 여부만 확인하며 값을 출력하거나 외부 API를 호출하지 않습니다.
// check-secret-config.mjs
const value = process.env.THIRD_PARTY_API_KEY;
if (!value || value === "YOUR_API_KEY") {
console.error("설정 오류: THIRD_PARTY_API_KEY에 실제 배포 환경의 시크릿을 주입하세요.");
process.exit(1);
}
console.log("설정 확인 완료: 시크릿 값은 출력하지 않았습니다.");
로컬 동작 확인에는 실제 키 대신 공급자 형식을 닮지 않은 테스트 문자열을 사용합니다.
node --check check-secret-config.mjs
THIRD_PARTY_API_KEY='LOCAL_TEST_VALUE' node check-secret-config.mjs
예상 결과:
설정 확인 완료: 시크릿 값은 출력하지 않았습니다.
이 테스트는 설정 검사 코드의 문법과 분기만 검증합니다. 실제 시크릿 저장소 권한이나 외부 API 인증을 검증하지 않습니다.
이미 커밋된 .env가 무시되지 않는 이유
다음 실습은 임시 로컬 저장소와 무해한 문자열만 사용합니다. 원격 저장소로 푸시하지 않습니다.
set -eu
DEMO_DIR="$(mktemp -d)"
trap 'rm -rf "$DEMO_DIR"' EXIT
cd "$DEMO_DIR"
git init -q
git config user.name 'Local Demo'
git config user.email 'local-demo@example.invalid'
printf '%s\n' 'THIRD_PARTY_API_KEY=LOCAL_TEST_VALUE' > .env
git add .env
git commit -qm 'track local demo env file'
printf '%s\n' '.env' > .gitignore
git add .gitignore
git commit -qm 'ignore future env files'
printf 'ignore 규칙: '
git check-ignore -v --no-index .env
printf '여전히 추적 중: '
git ls-files .env
git rm --cached -q .env
printf '추적 해제 후 staged 상태: '
git status --short
이 글을 작성하며 같은 예제를 로컬에서 실행한 결과, .gitignore 규칙이 표시된 뒤에도 git ls-files .env가 .env를 반환했습니다. git rm --cached .env 이후에는 .env 삭제와 .gitignore 추가가 staged 상태로 표시됐습니다. 즉, 추적 해제는 다음 커밋부터 파일을 제외할 뿐 과거 커밋의 값이나 이미 복제된 사본을 무효화하지 않습니다.
실제 키였다면 위 Git 명령보다 먼저 발급자 콘솔이나 승인된 관리 절차에서 키를 폐기해야 합니다. GitHub도 민감한 데이터를 기록에서 지우기 전에 자격 증명을 폐기하거나 로테이션하라고 안내하며, 기록 재작성은 커밋 해시 변경, 협업자 사본의 재오염, 서명 손실 같은 부작용이 있다고 설명합니다. (GitHub 저장소에서 민감한 데이터 제거, 확인 2026-07-20)
운영에서는 시크릿을 어떻게 주입할까
권장 흐름은 코드 저장소 → 배포 시스템 → 워크로드 신원 → 시크릿 저장소 → 프로세스 메모리입니다. 저장소와 배포 명세에는 시크릿 값이 아니라 시크릿의 이름이나 경로만 둡니다.
1. 환경을 분리한다
| 구분 | 개발 | 검증 | 운영 |
|---|---|---|---|
| 키 | 개발 전용 | 검증 전용 | 운영 전용 |
| 시크릿 경로 | dev/app/vendor-key |
staging/app/vendor-key |
prod/app/vendor-key |
| 허용 주체 | 개발 워크로드 | 검증 워크로드 | 운영 워크로드 |
| 로그·경보 | 개발 계정 | 검증 계정 | 운영 보안 채널 |
운영 키를 로컬 개발에 재사용하지 않습니다. 환경별 키를 분리하면 개발 저장소가 노출돼도 운영 키까지 즉시 영향을 받는 상황을 줄이고, 어느 환경에서 사용됐는지 추적하기 쉬워집니다.
2. 최소권한을 적용한다
애플리케이션 역할에는 필요한 시크릿의 읽기 권한만 부여하고, 목록 조회·생성·삭제·정책 변경 권한은 분리합니다. 사람의 상시 조회 권한도 기본값으로 두지 않습니다. AWS 공식 지침 역시 IAM 역할·정책, 리소스 정책, ABAC를 이용한 최소권한과 광범위한 리소스 정책 차단을 권장합니다. (AWS Secrets Manager 모범 사례, 확인 2026-07-20)
다음은 정책 구조를 설명하기 위한 JSON 골격입니다. 실제 계정 ID, 리전, 시크릿 이름으로 바꾸고 보안 담당자의 검토를 받아야 하며, 이 글에서는 AWS에 적용하지 않았습니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOnlyOneProductionSecret",
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:REGION:ACCOUNT_ID:secret:prod/app/vendor-key-*"
}
]
}
Resource: "*"나 관리 권한 전체를 편의상 부여하지 않습니다. 고객 관리형 KMS 키를 쓴다면 Secrets Manager 권한뿐 아니라 KMS 복호화 권한과 키 정책도 함께 검토해야 합니다.
3. 값이 아닌 이벤트를 감사한다
감사 로그에는 요청 주체, 대상 시크릿, 승인·거부, 조회·변경 시각, 인증·인가 오류, 관리 작업을 남깁니다. 시크릿 값, 요청 헤더, 전체 환경변수는 기록하지 않습니다. OWASP는 요청자와 역할, 승인 여부, 사용·만료·갱신, 인증·인가 오류, 관리 작업 등을 감사 대상으로 제시합니다. (OWASP Secrets Management Cheat Sheet — Auditing, 확인 2026-07-20)
로그도 삭제·변조 권한을 분리하고, 조직의 사고 대응·규정 요구에 맞는 보존 기간과 경보를 정해야 합니다. “대량 조회”, “평소와 다른 주체”, “업무 시간 외 정책 변경”, “폐기된 키 재사용” 같은 사건에 우선순위를 둡니다.
4. 조회 횟수와 장애 경로를 설계한다
애플리케이션이 모든 요청마다 시크릿 저장소를 호출하면 지연, API 사용량, 비용과 장애 의존성이 커질 수 있습니다. 공급자가 지원하는 캐시나 에이전트를 사용하되, 교체된 값이 언제 반영되는지 TTL과 재시도 정책을 문서화합니다. AWS는 지원되는 클라이언트 캐시 구성 요소 사용을 권장합니다. (AWS Secrets Manager 모범 사례 — 캐시, 확인 2026-07-20)
시크릿 저장소 장애를 이유로 운영 키를 소스 코드나 공개 문서에 백업하지 않습니다. 대신 제한된 담당자, 다중 승인, 짧은 사용 시간, 사용 즉시 경보를 포함한 비상 접근 절차를 마련하고 복구 훈련을 합니다. OWASP도 백업·복원 시험과 별도 보호된 break-glass 절차를 다룹니다. (OWASP — Downtime, Break-glass, Backup and Restore, 확인 2026-07-20)
로테이션 주기는 어떻게 정할까
모든 API 키를 60일 또는 90일에 한 번 바꾸라는 단일 기준은 없습니다. OWASP는 시크릿의 기능과 보호 대상에 따라 수명이 크게 달라질 수 있다고 설명하며, 잠재적으로 침해된 시크릿은 폐기해야 한다고 권고합니다. (OWASP — Secret Lifecycle, 확인 2026-07-20)
다음 순서로 정책을 정합니다.
- 즉시 교체 조건: 저장소·로그·티켓 노출, 담당자 계정 침해, 비정상 사용, 소유자 퇴사, 발급자 경보.
- 수명 결정 요소: 공급자의 만료·복수 키 지원, 키 권한과 결제 영향, 규정, 탐지 능력, 배포 실패 시 영향.
- 정기 교체 방식: 가능하면 새 키 발급 → 제한된 환경에 배포 → 정상 동작 확인 → 이전 키 폐기 순으로 무중단 교체한다.
- 자동화 검증: 교체가 성공했는지뿐 아니라 애플리케이션이 새 값을 다시 읽는지, 롤백·경보·담당자 호출이 작동하는지 시험한다.
- 동적 신원 우선: 공급자가 워크로드 신원, OIDC, 짧은 수명 토큰을 지원하면 장기 정적 키를 줄일 수 있는지 검토한다.
예정된 로테이션과 유출 사고 대응은 다릅니다. 예정된 작업은 두 키의 짧은 중첩으로 가용성을 확인할 수 있지만, 실제 노출 사고에서는 공격자의 사용을 막기 위한 폐기가 우선입니다.
GitHub secret scanning과 push protection의 역할
GitHub secret scanning은 알려진 형식의 API 키·토큰 등 하드코딩된 자격 증명을 Git 기록과 여러 협업 영역에서 탐지합니다. 공개 저장소에서는 자동으로 무료 실행되며, 조직 소유 비공개·내부 저장소는 GitHub Team 또는 Enterprise Cloud에서 GitHub Secret Protection 활성화가 필요합니다. 기능 범위와 요금제는 변경될 수 있으므로 적용 전 현재 문서를 확인합니다. (GitHub secret scanning, 확인 2026-07-20)
Push protection은 지원되는 패턴을 푸시 시점에 검사해 저장소에 도달하기 전에 차단합니다. 다만 저장소 수준 기능은 기본적으로 꺼져 있을 수 있고, 권한 있는 사용자가 이유를 남겨 우회할 수 있으며, 모든 사내 형식이나 인코딩된 값을 탐지한다고 보장할 수 없습니다. 우회 이벤트의 감사 로그와 경보도 검토해야 합니다. (GitHub push protection, 확인 2026-07-20)
로컬 스캐너를 함께 두는 이유
푸시 전에 로컬 또는 CI에서 스캔하면 GitHub에 도달하기 전 피드백을 받을 수 있습니다. 예를 들어 gitleaks를 이미 승인·설치한 환경이라면 설치된 버전의 도움말과 조직 설정을 먼저 확인한 뒤 다음 형태로 검사할 수 있습니다.
# 정적 예시: 이 글의 작성 환경에서는 gitleaks가 없어 실행하지 않음
gitleaks git . --redact
이 명령은 도구 버전에 따라 옵션이 달라질 수 있습니다. 이 글 작성 환경에는 gitleaks가 없었고 요청 범위에 따라 새로 설치하지 않았으므로, 위 출력이나 탐지 성능은 검증하지 않았습니다. 배포 전 담당 환경에서 gitleaks --version과 gitleaks git --help를 확인하고, 승인된 테스트 저장소에서 조직 규칙·예외·실패 코드를 검증해야 합니다.
실제 공급자 키처럼 보이는 가짜 키로 스캐너를 시험하지 마십시오. 파트너 알림이나 자동 폐기 절차를 불필요하게 유발할 수 있습니다. 조직 전용 테스트 규칙을 쓸 때도 SAFE_DEMO_SECRET_처럼 실키와 혼동되지 않는 형식을 별도 샌드박스에서 사용합니다.
키가 이미 노출됐다면: 사고 대응 순서
1. 노출된 키를 즉시 폐기한다
발급자 콘솔이나 승인된 관리 API에서 해당 키를 비활성화·폐기합니다. 저장소를 비공개로 바꾸거나 마지막 커밋을 삭제하는 것만으로는 이미 복제·캐시·열람된 키를 회수할 수 없습니다. 폐기 시각, 실행자, 키 ID를 기록하되 키 값은 기록하지 않습니다.
2. 대체 키를 발급한다
이전 키보다 넓은 권한을 주지 않습니다. 가능하면 영향받은 환경만을 위한 별도 키를 만들고, 소유자·목적·만료 또는 다음 검토일을 등록합니다. 공급자가 키를 하나만 허용한다면 예상 중단과 복구 절차를 먼저 공유하되, 확인된 노출을 방치하지 않습니다.
3. 영향받은 환경만 갱신하고 배포한다
운영 키 노출이라면 운영 시크릿 경로만 갱신합니다. 워크로드를 재시작하거나 캐시를 무효화해 새 값이 반영되도록 하고, 키 값을 출력하지 않는 상태 확인으로 복구를 검증합니다. 개발·검증 키까지 같은 값이었다면 모두 별도 키로 분리합니다.
4. 로그와 영향 범위를 검토한다
발급자 사용 로그, 결제·쿼터 변화, 소스 저장소 감사 로그, CI/CD 실행, 시크릿 저장소 조회·변경 기록을 사고 시간대 전후로 확인합니다. 예상하지 않은 IP·리전·API·리소스·주체가 있었는지 조사하고, 필요하면 관련 계정·세션·토큰도 폐기합니다. 로그가 없다고 오용이 없었다고 단정하지 않습니다.
5. 노출 사본을 제거한다
코드와 현재 브랜치뿐 아니라 Git 기록, 포크·클론, PR·이슈, CI 로그, 빌드 산출물, 컨테이너 이미지 계층, 티켓, 메신저, 화면 캡처, 백업을 확인합니다. 기록 재작성은 협업자와 조율하고 GitHub 공식 절차를 따릅니다. 이미 폐기된 키의 사본을 지우는 작업은 재사용과 오탐을 줄이지만, 폐기를 대신하지 않습니다.
6. 재발 방지 통제를 추가한다
커밋 전 검사, push protection, 최소권한, 환경 분리, 로그 마스킹, 키 소유자와 만료 메타데이터, 사고 훈련을 보강합니다. 단일 담당자의 주의력에만 의존하지 않습니다.
실패 조건·비용·권한 한계
- 비용: AWS Secrets Manager는 저장한 시크릿 수와 API 호출을 기준으로 과금합니다. 복제, KMS 고객 관리형 키, 로그 보관·분석, 네트워크 구성에는 별도 비용이 생길 수 있습니다. 최신 단가와 리전 조건은 AWS Secrets Manager 요금에서 계산하십시오. 이 글에서는 유료 API를 호출하지 않았습니다.
- 권한: 시크릿 읽기 권한만 있어도 애플리케이션이나 사용자는 값을 복사할 수 있습니다. 관리자, 배포자, 시크릿 읽기 주체, 로그 관리자의 역할을 분리하고 정기적으로 권한을 재검토합니다.
- 탐지 한계: secret scanning과 gitleaks는 지원 패턴·설정·범위 밖의 키, 분할·인코딩된 값, 이미지 속 텍스트를 놓칠 수 있습니다. 탐지 성공은 키가 안전하다는 보장이 아닙니다.
- 가용성: 잘못된 IAM/KMS 정책, 네트워크 제한, 로테이션 함수 실패, 캐시 만료는 배포 장애를 만들 수 있습니다. 검증 환경에서 교체·롤백·비상 접근을 시험합니다.
- 브라우저: 프런트엔드 번들에 포함되는 값은 사용자가 볼 수 있습니다. 브라우저에서 비밀로 유지해야 하는 공급자 키를 직접 호출에 쓰지 말고 서버 측 중계와 사용자 인증, 호출 한도, 도메인 제한을 설계합니다.
- 로그: 디버그 출력, 예외 객체, HTTP 헤더, CI의
set -x, 프로세스 환경 덤프가 키를 노출할 수 있습니다. 마스킹 규칙과 샘플 로그를 배포 전에 검사합니다. - 로테이션: 공급자가 복수 활성 키나 자동 교체를 지원하지 않으면 중단 위험이 있습니다. 반대로 너무 긴 수명은 노출된 키의 사용 가능 기간을 늘릴 수 있습니다. 위험과 가용성을 함께 결정합니다.
발행 전·운영 체크리스트
저장과 배포
- 저장소와 이미지, 문서, 티켓에 실제 키가 없다.
-
.env,.env.*가 무시되며.env.example에는 변수 이름과 자리표시자만 있다. - 개발·검증·운영이 서로 다른 키와 시크릿 경로를 쓴다.
- 워크로드 신원이 필요한 시크릿 하나만 읽을 수 있다.
- CI/CD 로그, 애플리케이션 로그, 오류 추적 도구가 값을 마스킹한다.
- 브라우저 번들에 서버 전용 키가 포함되지 않는다.
탐지와 감사
- 공개·비공개 저장소의 GitHub 기능 제공 범위와 요금제를 확인했다.
- Secret scanning 경보 담당자와 처리 기한을 정했다.
- Push protection 우회 권한과 우회 경보를 검토했다.
- 로컬·CI 스캐너의 버전, 규칙, 예외, 실패 코드를 샌드박스에서 검증했다.
- 시크릿 조회·변경·거부·관리 이벤트가 값 없이 감사 로그에 남는다.
- 로그 변조 방지, 접근권한, 보존 기간, 경보가 정해져 있다.
로테이션과 사고 대응
- 키별 소유자, 목적, 환경, 권한, 발급일, 다음 검토일이 있다.
- 즉시 폐기 조건과 담당자 연락 경로가 문서화돼 있다.
- 예정된 교체의 발급→배포→검증→이전 키 폐기 절차를 시험했다.
- 노출 시 폐기→재발급→배포→로그 검토→사본 제거 순서를 지킨다.
- 시크릿 저장소 장애와 break-glass 절차를 값 노출 없이 훈련했다.
자주 묻는 질문
.env를 비공개 저장소에 커밋해도 되나요?
권장하지 않습니다. 저장소 공개 여부는 변경될 수 있고, 협업자·CI·백업·앱 권한을 통해 값이 복사될 수 있습니다. .env.example에는 변수 이름만 공유하고 실제 값은 승인된 시크릿 저장소나 배포 플랫폼의 비밀 변수에 둡니다.
저장소를 비공개로 바꾸면 노출된 키가 다시 안전해지나요?
아닙니다. 누가 이미 복제하거나 읽었는지 알 수 없으므로 키를 폐기하고 재발급해야 합니다. 비공개 전환과 기록 정리는 추가 확산을 줄이는 보조 조치입니다.
API 키는 몇 일마다 바꿔야 하나요?
일률적인 날짜를 적용하지 않습니다. 공급자의 만료·복수 키 기능, 키 권한, 결제 영향, 규정, 탐지 능력, 배포 중단 위험을 평가합니다. 노출이 의심되면 정기 일정과 무관하게 즉시 폐기합니다.
시크릿 매니저를 쓰면 환경변수는 안전한가요?
런타임에 환경변수로 주입하면 코드 하드코딩을 피할 수 있지만, 같은 사용자 권한의 프로세스, 진단 도구, 환경 덤프, 로그가 값을 읽을 수 있습니다. 위협 모델에 따라 메모리 파일, 사이드카, 직접 SDK 조회 등 방식을 비교하고 프로세스·디버그 권한을 제한해야 합니다.
관련 글
- LLM API 토큰과 사용자별 비용 한도 설계 — 키가 오용됐을 때 청구 영향을 줄이는 쿼터 설계
- n8n 워크플로 백업 저장소의 PAT 보호 — 백업 자동화 자격 증명의 저장·복구 범위
- AWS 서버 운영과 IAM 점검 — 운영 계정의 역할 분리와 최소권한 확장
공식 참고 문서
모든 링크는 2026-07-20에 확인했습니다.
'🤖 1인 에이전트 구축기' 카테고리의 다른 글
| AI 에이전트 성능 유지, 회귀 테스트 구축 시 절대 하지 말아야 할 실수 3 (1) | 2026.06.15 |
|---|---|
| 정책을 지키는 웹 수집기: robots.txt·429·재시도·증분 수집 (0) | 2026.06.15 |
| n8n 워크플로우 삭제 사고 방지, 깃허브 자동 백업 1분 세팅법 (0) | 2026.06.14 |
| AWS 서버 운영 시 절대 하지 말아야 할 실수 3 (0) | 2026.06.13 |
| 생성형 AI 검색 시대, 살아남는 정보 밀도 전략 (0) | 2026.06.13 |