개요
Git repository에 민감한 정보(API 키, 비밀번호, 개인정보 등)가 커밋되면 단순히 파일을 삭제하거나 수정하는 것만으로는 충분하지 않다. Git의 히스토리는 모든 변경사항을 추적하기 때문에 과거 커밋에 남아있는 민감한 데이터는 여전히 접근 가능하다. 본 글에서는 git filter-repo와 git filter-branch를 활용하여 Git 히스토리 전체에서 민감한 파일과 문자열을 완전히 제거하는 방법을 다룬다.
설명
Git History 정리의 필요성
Git repository의 히스토리에 민감한 정보가 포함되면 다음과 같은 보안 위험이 발생한다:
- 영구적 노출: 삭제된 파일도 과거 커밋에서 복원 가능
- Public repository의 경우: 전 세계 누구나 히스토리를 통해 접근 가능
- Fork 및 Clone: 이미 복제된 repository에도 민감한 정보가 존재
git filter-repo vs git filter-branch
git filter-repo는 Python으로 작성된 도구로, Git 공식 문서에서도 git filter-branch의 대안으로 권장한다. 주요 차이점은 다음과 같다:
| 항목 | git filter-repo | git filter-branch |
|---|---|---|
| 성능 | 10-100배 빠름 | 느림 |
| 안전성 | 높음 (실수 방지 기능) | 낮음 |
| 사용 편의성 | 직관적 | 복잡한 스크립팅 필요 |
| Git 공식 권장 | ✓ | ✗ (deprecated) |
특징
git filter-repo의 주요 기능
- 파일 제거: 특정 경로의 파일을 히스토리에서 완전히 삭제
- 문자열 치환: 민감한 문자열을 안전한 값으로 대체
- 경로 재구성: 디렉토리 구조 변경
- 메일박스 재작성: 커미터 정보 수정
- 성능 최적화: 대용량 repository도 빠르게 처리
작동 원리
git filter-repo는 Git의 fast-import/fast-export 메커니즘을 활용하여 전체 히스토리를 재작성한다. 각 커밋을 순회하면서 지정된 규칙에 따라 내용을 필터링하고, 새로운 커밋 객체를 생성한다. 이 과정에서 SHA-1 해시값이 변경되므로 히스토리가 완전히 새로워진다.
예제
사전 준비
# git filter-repo 설치 (pip 사용)
pip install git-filter-repo
# 또는 Homebrew (macOS)
brew install git-filter-repo
# 작업 전 백업 (선택사항이지만 강력히 권장)
git clone --mirror <repository-url> backup-repo
예제 1: 특정 파일 완전 제거
# secrets.txt 파일을 모든 히스토리에서 제거
git filter-repo --path secrets.txt --invert-paths
# 여러 파일 동시 제거
git filter-repo --path config/database.yml --path .env --invert-paths
# 특정 디렉토리 전체 제거
git filter-repo --path credentials/ --invert-paths
예제 2: 민감한 문자열 치환
# replacements.txt 파일 생성
echo "API_KEY=sk-1234567890abcdef==>API_KEY=REDACTED" > replacements.txt
echo "password123==>***REMOVED***" >> replacements.txt
echo "admin@secret.com==>user@example.com" >> replacements.txt
# 문자열 치환 실행
git filter-repo --replace-text replacements.txt
예제 3: 파일 제거와 문자열 치환 동시 적용
# 복합 작업: secrets.txt 제거 + API 키 치환
echo "sk-proj-[a-zA-Z0-9]+==>API_KEY_REDACTED" > replacements.txt
git filter-repo \
--path secrets.txt \
--path .env.local \
--invert-paths \
--replace-text replacements.txt
예제 4: git filter-branch 사용 (레거시)
# 특정 파일 제거 (filter-branch 방식)
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch secrets.txt' \
--prune-empty --tag-name-filter cat -- --all
# 문자열 치환 (filter-branch 방식)
git filter-branch --force --tree-filter \
'find . -type f -exec sed -i "s/API_KEY=12345/API_KEY=REDACTED/g" {} +' \
HEAD
예제 5: Remote Repository 업데이트
# 기존 remote 확인
git remote -v
# filter-repo 사용 후 remote가 제거되므로 다시 추가
git remote add origin https://github.com/username/repository.git
# 강제 푸시 (--force 또는 --force-with-lease 사용)
git push origin --force --all
git push origin --force --tags
# 또는 안전한 강제 푸시
git push origin --force-with-lease --all
예제 6: GitHub Actions에서 민감 정보 관리
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup environment
env:
API_KEY: ${{ secrets.API_KEY }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: |
echo "API_KEY=${API_KEY}" >> .env
echo "DB_PASSWORD=${DB_PASSWORD}" >> .env
예제 7: Pre-commit Hook으로 예방
# .git/hooks/pre-commit 파일 생성
#!/bin/bash
# 민감한 패턴 검사
if git diff --cached | grep -E "API_KEY|password|secret"; then
echo "경고: 민감한 정보가 포함되어 있습니다!"
echo "커밋을 중단합니다."
exit 1
fi
exit 0
완전한 워크플로우 예제
# 1. 로컬 작업 디렉토리 준비
cd /path/to/repository
git clone --mirror https://github.com/username/repo.git temp-repo
cd temp-repo
# 2. 민감 정보 제거
git filter-repo \
--path config/secrets.yml \
--path .env.production \
--invert-paths \
--replace-text <(echo "literal:sk-1234567890==>REDACTED")
# 3. Remote 재설정 및 푸시
git remote add origin https://github.com/username/repo.git
git push origin --force --all
git push origin --force --tags
# 4. 로컬 repository 정리
cd ..
rm -rf temp-repo
git clone https://github.com/username/repo.git
# 5. 팀원 공지
echo "모든 팀원은 기존 클론을 삭제하고 새로 클론해주세요."
결론
Git repository에서 민감한 정보를 제거하는 작업은 단순한 파일 삭제가 아닌 히스토리 재작성을 필요로 한다. git filter-repo는 성능과 안전성 면에서 git filter-branch보다 우수하며, Git 공식 문서에서도 권장하는 도구이다.
그러나 히스토리 재작성은 다음과 같은 주의사항을 수반한다:
- 불가역적 작업: 히스토리가 완전히 변경되므로 신중하게 진행해야 함
- 협업 영향: 모든 팀원이 repository를 새로 클론해야 함
- Fork 문제: Public repository의 경우 이미 생성된 fork에는 여전히 민감 정보가 남아있을 수 있음
따라서 가장 중요한 것은 예방이다. .gitignore 설정, 환경 변수 사용, GitHub Secrets 활용, pre-commit hook 도입 등을 통해 민감한 정보가 애초에 커밋되지 않도록 해야 한다. 이미 노출된 키는 반드시 폐기하고 재발급받아야 하며, GitHub Secret Scanning 같은 자동화 도구를 활용하여 지속적으로 보안을 유지해야 한다.
참고문헌
- Git 공식 문서 - git-filter-repo: https://git-scm.com/docs/git-filter-repo
- GitHub 공식 가이드 - Removing sensitive data: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository
- git-filter-repo GitHub repository: https://github.com/newren/git-filter-repo
- Git 공식 문서 - git-filter-branch: https://git-scm.com/docs/git-filter-branch
- GitHub Secret Scanning 문서: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
- OWASP - Credential Management Cheat Sheet: https://cheatsheetsorg.owasp.org/cheatsheets/Credential_Management_Cheat_Sheet.html
'Git' 카테고리의 다른 글
| Git 커밋 메시지 변경시 현재 시간으로 바뀌는 현상 해결방법 (0) | 2025.02.13 |
|---|