본문 바로가기

DevOps/Git & GitHub

[Git] git 보안 파일 히스토리 삭제

문제 발단

GitHub에 보안 파일이 실수로 올라간 적이 있었다.

불행하게도, 이 파일이 올라간 커밋 이후 다른 개발자의 pc에서 새로운 커밋들이 마구마구 됐었다.

실수로 올라간 보안 파일을 지우고, 다시 커밋해도 GitHub엔 History 버튼을 누르면 파일 안의 패스워드 등을 그대로 확인할 수 있다.

 

GitHub 리포지토리의 해당 파일을 클릭해 History 버튼을 누르면 변경된 파일, 파일의 내용 등을 그대로 확인할 수 있다.

 

포스팅 목적

이 포스팅은 보안 문제가 될 수 있는 패스워드, DB 주소 등이 실수로 리포지토리에 올라갔을 때

그 파일의 히스토리까지 완벽하게 삭제하고, 그 이후의 문제까지 다룬다.

 

해결 방법

전체적인 흐름은 터미널을 이용해 보안 파일에 해당하는 git log 파일을 삭제하고, 이 파일이 포함된 모든 커밋 기록을 변경한다.

삭제하려고 하는 파일은 `/Users/Github/my_repository/analysis/테스트분석.ipynb` 파일이다.

* 이 작업을 수행하기 전에, 해당 파일이 있는 브랜치로 check out 확인 후 수행한다.

 

사전 작업 1. 터미널에서 리포지토리의 최상위 디렉터리로 이동한다. (ex. `/Users/.../Github/my_repository`)

cd /Users/Github/my_repository

이렇게 하는 이유는 수행하려는 git 명령어 작업은 최상위 디렉터리에서만 가능하기 때문이다.

 

사전 작업 2. 작업하려는 브랜치로 전환한다.

git checkout [브랜치 이름]

 

Step 1. 로컬 저장소와 원격 저장소의 브랜치를 동기화한다.

git pull origin [브런치 이름]

 

Step 2. 삭제할 파일의 이름을 갖고 아래 명령어를 입력하자.

(파일 이름엔 "최상위 디렉터리로부터 삭제할 파일의 경로까지" 입력돼야 한다. 이 글에선 analysis 폴더 안에 있는 `테스트분석.ipynb` 파일을 제거하려고 한다.)

git filter-branch -f --index-filter 'rm --cached --ignore-unmatch analysis/테스트분석.ipynb' --prune-empty -- --all

또는

git filter-branch --tree-filter 'rm -f analysis/테스트분석.ipynb' --prune-empty HEAD

이 두 가지 명령어의 차이는 맨 아래에서 설명하겠다.

 

Step 3. 원격 브랜치를 강제로 업데이트한다.

git push --force origin [브랜치 이름]

이 작업 후, GitHub 웹의 리포지토리로 이동해서 해당 파일의 히스토리가 삭제됐는지 확인한다.

 

GitHub 리포지토리의 History를 확인해보자. 테스트분석.ipynb 파일에 대한 히스토리는 삭제됐다!

만약, 보안 파일 업로드 후, 다른 개발자들이 같은 브런치에 커밋을 했다면, 다른 개발자의 PC에서 힘들게 삭제한 파일의 히스토리가 있을 수 있다. 이 경우, 이 보안 파일의 히스토리(git log)가 스테이징 되어 본의 아니게 다른 커밋들과 함께 원격 저장소에 커밋되면 다시 원점으로 돌아가버린다.

이런 경우엔 Step 4를 시도한다.

 

Step 4. (다른 개발자 PC) 원격 저장소와 동기화 후 파일의 히스토리가 삭제됐는지 확인한다.

git log -- analysis/테스트분석.ipynb

만약, log가 있다면, 원격 저장소와 동기화가 제대로 되지 않은 것이므로 다시 동기화를 해준다.

git pull origin [브랜치 이름]

만약 위와 같은 동기화 문제에도 해결되지 않는다면, 원격 저장소를 지우고 다시 복제하는 과정을 거쳐야 한다.

git clone [리포지토리 url]
git checkout [해당 브랜치 이름]
git pull origin [해당 브랜치 이름]

 

git 명령어 설명

1) git filter-branch -f --index-filter 'rm --cached --ignore-unmatch analysis/테스트분석.ipynb' --prune-empty -- --all

2) git filter-branch --tree-filter 'rm -f analysis/테스트분석.ipynb' --prune-empty HEAD

  • `filter-branch`
    • Git 저장소의 커밋 기록을 변경하거나 필터링하는데 사용되는 Git 명령어. 이 명령어를 사용하면 Git의 커밋 기록에서 원치 않는 파일을 삭제하거나, 파일의 내용을 수정하거나, 커밋 메시지를 변경하는 등의 작업을 수행할 수 있음. 이미 다른 사용자들이 이용 중인 저장소에서 사용하지 않는 것이 좋다.
  • `--tree-filter`
    • 필터링 작업을 워킹 디렉토리를 기준으로 수행한다. 즉, 필터링 작업 중에 워킹 디렉토리에서 파일을 삭제하거나 추가하는 등의 변경 작업을 수행할 수 있다. 이 방식은 Git의 인덱스를 건드리지 않기 때문에 필터링 작업 중에 다른 파일들의 변경 내역이 무시되지 않도록 유의해야 한다.
  • `--index-filter`
    • 필터링 작업을 Git의 인덱스를 기준으로 수행한다. 즉, 필터링 작업 중에 Git의 인덱스에서 파일을 삭제하거나 추가하는 등의 변경 작업을 수행. 이 방식은 Git의 커밋 기록을 수정할 때 인덱스에 있는 내용을 기반으로 작업을 수행하기 때문에, 워킹 디렉토리의 변경 내역이 포함되지 않음.
  • `--tree-filter` vs `--index-filter`
    • 어떤 옵션을 선택할지는 작업의 목적과 필요에 따라 달라질 수 있다. 만약 작업 중에 워킹 디렉토리의 변경 내역을 포함해야 할 경우에는 --tree-filter를 선택하고, 작업 중에 워킹 디렉토리를 건드리지 않고 인덱스를 기준으로 작업을 수행해야 할 경우에는 --index-filter를 선택한다.
  • `'rm -f analysis/테스트분석.ipynb'`
    • 워킹 디렉토리에서 analysis 폴더 안의 '테스트분석.ipynb' 파일을 삭제하는 명령어
    • -f는 --force 옵션의 의미와 같이 Git 작업 중 발생하는 충돌을 무시하고 작업을 강제로 수행함을 의미한다.
  • `--cached`
    • Git의 인덱스나 워킹 디렉토리에서 삭제할 파일을 찾을 수 없을 경우에도 오류를 발생시키지 않고 넘어가도록 한다. 즉, 해당 파일이 없을 경우에는 그냥 무시하고 계속해서 작업을 수행.
  • `--ignore-unmatch`
    • Git의 인덱스에서 파일을 삭제할 때 사용. 즉, Git 저장소에서 파일을 삭제하는 것이 아니라, 해당 파일을 Git의 인덱스에서만 삭제. 이를 통해 워킹 디렉토리에서는 파일이 유지되면서, Git의 인덱스에서만 파일이 삭제되는 작업을 수행.
    • --index-filter와 함께 사용
  • `--prune-empty`
    • 빈 커밋을 제거하는 명령어
  • `--all`
    • Git 저장소의 모든 브랜치와 태그에 대해 작업을 수행
  • --index-filter 인수를 사용할 때, --all 앞의 `--`
    (git filter-branch -f --index-filter 'rm --cached --ignore-unmatch analysis/테스트분석.ipynb' --prune-empty -- --all)
    • git filter-branch 명령어에서 revision range를 나타내는 역할. 이 구분자 이후 오는 모든 인수는 filter-branch의 명령의 인수로 간주. 즉, `--prune-empty -- --all` 명령은 Git 저장소의 모든 브랜치와 태그를 대상으로 하되, 빈 커밋을 제거하는 `--prune-empty` 옵션을 함께 적용하도록 지시하는 것.
  • `HEAD`
    • 현재 브랜치의 모든 커밋에 대해 작업을 수행