Git
Chapters ▾ 2nd Edition

7.14 Git 도구 - Credential 저장소

Credential 저장소

SSH 프로토콜을 사용하여 리모트 저장소에 접근할 때 Passphase 없이 생성한 SSH Key를 사용하면 사용자이름과 암호를 입력하지 않고도 안전하게 데이터를 주고받을 수 있다. 반면 HTTP 프로토콜을 사용하는 경우는 매번 사용자이름과 암호를 입력해야 한다.

다행히도 Git은 이렇게 매번 인증정보(Credential)를 입력하는 경우 인증정보를 저장해두고 자동으로 입력해주는 시스템을 제공한다. Git Credential 기능이 제공하는 옵션은 아래와 같다.

  • 일단 기본적으로 아무런 설정도 하지 않으면 어떤 암호도 저장하지 않는다. 이 경우 인증이 필요한 때 매번 사용자이름과 암호를 입력해야 한다.

  • “cache” 모드로 설정하면 일정 시간 동안 메모리에 사용자이름과 암호 같은 인증정보를 기억한다. 이 정보를 Disk에 저장하지는 않으며 메모리에서도 15분 까지만 유지한다.

  • “store” 모드로 설정하면 인증정보를 Disk의 텍스트 파일로 저장하며 계속 유지한다. 계속 유지한다는 말은 리모트의 인증정보를 변경하지 않는 한 다시 인증정보를 입력하지 않아도 접근할 수 있다는 말이다. “store” 모드를 사용할 때 주의할 점은 인증정보가 사용자 홈 디렉토리 아래에 일반 텍스트 파일로 저장된다는 점이다.

  • Mac에서 Git을 사용하는 경우 “osxkeychain” 모드를 사용하면 Mac에서 제공하는 Keychain 시스템에 사용자이름과 암호를 현재 로그인 계정에 속하게 저장한다. “store” 모드하면 인증정보를 Disk에 저장하고 인증정보가 만료되지 않는 점은 같지만, Safari 브라우저가 인증정보를 저장하는 것과 같은 수준으로 암호화해서 저장한다는 점이 다르다.

  • Windows 환경에서는 “Git Credential Manager for Windows.” 라는 Helper가 있다. “osxkeychain” Helper와 비슷하게 동작하며 Windows Credential Store를 사용하여 안전하게 인증정보를 저장한다. https://github.com/Microsoft/Git-Credential-Manager-for-Windows 에서 다운로드 받을 수 있다.

위에서 설명한 여러 모드 중 하나를 아래와 같이 설정할 수 있다.

$ git config --global credential.helper cache

추가 옵션을 지정할 수 있는 Helper도 있다. “store” Helper는 --file <path> 옵션을 사용하여 인증정보를 저장할 텍스트 파일의 위치를 지정한다. 기본 값은 ~/.git-credentials 이다. “cache” Helper는 --timeout <seconds> 옵션을 사용하여 언제까지 인증정보를 메모리에 유지할지 설정한다. 기본 값은 “900” 초로 15분이다. 기본 경로가 아닌 다른 경로를 지정해서 인증정보를 저장하려면 아래와 같이 실행한다.

$ git config --global credential.helper 'store --file ~/.my-credentials'

Helper를 여러개 섞어서 쓸 수도 있다. 인증이 필요한 리모트에 접근할 때 Git은 인증정보를 찾는데 Helper가 여러개 있으면 순서대로 찾는다. 반대로 인증정보를 저장할 때는 설정한 모든 모드에 저장한다. 아래 예제는 첫 번째 Path에 대해 인증정보를 읽거나 저장에 실패하면 두 번째 모드에 따라 메모리에서만 인증정보를 유지한다.

[credential]
    helper = store --file /mnt/thumbdrive/.git-credentials
    helper = cache --timeout 30000

뚜껑을 열어보면

실제로는 어떻게 동작하는지 살펴보자. Git의 Credential-Helper 시스템의 기본 명령은 git credential 이다. 이 명령이 하위 명령이나 옵션, 표준입력으로 필요한 정보를 입력받아 전달한다.

이 과정은 예제를 통해 이해하는 편이 쉽다. Credential Helper를 사용하도록 설정하고 mygithost 라는 호스트의 인증정보가 저장된 상태이다. 아래 예제는 “fill” 명령으로 Git이 특정 호스트에 대한 인증정보를 얻으려는 과정을 보여준다.

$ git credential fill (1)
protocol=https (2)
host=mygithost
(3)
protocol=https (4)
host=mygithost
username=bob
password=s3cre7
$ git credential fill (5)
protocol=https
host=unknownhost

Username for 'https://unknownhost': bob
Password for 'https://bob@unknownhost':
protocol=https
host=unknownhost
username=bob
password=s3cre7
  1. 이 명령으로 인증정보를 얻어오는 과정을 시작한다.

  2. 이제 Git-credential 명령은 표준 입력으로 사용자의 입력을 기다린다. 인증정보가 필요한 대상의 프로토콜과 호스트이름을 입력한다.

  3. 빈 라인을 하나 입력하면 입력이 끝났다는 것을 의미한다. 이제 입력한 내용에 해당하는 인증정보를 응답해야 한다.

  4. Git-credential 명령이 전달받은 내용으로 인증정보를 찾아보고 찾으면 표준출력으로 찾은 정보를 응답한다.

  5. 물론 요청에 대한 인증정보가 없을 수도 있다. 이렇게 되면 Git이 사용자이름과 암호를 사용자가 입력하도록 메시지를 띄우고 값도 입력받는다. 입력받은 값을 다시 표준출력으로 응답한다.

이 Credential 시스템은 사실 Git과 분리된 독립적인 프로그램을 실행시켜 동작한다. 어떤 프로그램을 실행시킬지는 credential.helper 설정 값에 따른다. 이 설정 값을 아래와 같이 설정한다.

설정 값 결과

foo

git-credential-foo 실행

foo -a --opt=bcd

git-credential-foo -a --opt=bcd 실행

/absolute/path/foo -xyz

/absolute/path/foo -xyz 실행

!f() { echo "password=s3cre7"; }; f

! 뒤의 코드를 쉘에서 실행

따라서 위에서 살펴본 여러 Helper는 사실 git-credential-cache, git-credential-store 같은 명령이다. 설정을 통해 이 명령들이 옵션이나 하위 명령을 받아서 실행하게 한다. 이 명령의 일반적인 형태는 “git-credential-foo [args] <action>” 이다. git-credential 명령과 마찬가지로 표준입력/표준출력을 프로토콜로 사용하지만 처리하는 액션(하위 명령)은 아래와 같이 다소 다르다.

  • get - 사용자이름과 암호를 요구하는 액션

  • store - Helper에서 인증정보를 저장하는 액션

  • erase - Helper에서 인증정보를 삭제하는 액션

storeerase 액션은 따로 결과를 출력할 필요가 없다(Git은 결과를 무시). get 액션의 결과는 Git이 주의 깊게 관찰해서 가져다 사용하므로 매우 중요하다. Helper는 전달받은 내용으로 인증정보를 찾고 저장된 인증정보가 없다면 아무런 결과도 출력하지 않고 종료하면 된다. 적당한 인증정보를 찾았을 때는 전달받은 내용에 찾은 인증정보를 추가하여 결과로 응답한다. 결과는 몇 라인의 할당 구문으로 구성하며, Git은 이 결과를 받아서 사용한다.

아래 예제는 위에서 살펴본 예제와 같은 내용으로 git-crediential 명령 대신 git-credential-store 명령을 직접 사용한다.

$ git credential-store --file ~/git.store store (1)
protocol=https
host=mygithost
username=bob
password=s3cre7
$ git credential-store --file ~/git.store get (2)
protocol=https
host=mygithost

username=bob (3)
password=s3cre7
  1. git-credential-store Helper에게 인증정보를 저장하도록 한다. 저장할 인증정보는 사용자이름이 “bob”, 암호가 “s3cre7” 이다. 프로토콜과 호스트가 https://mygithost 일 때 사용한다.

  2. 저장한 인증정보를 가져온다. 이미 아는 https://mygithost 리모트 주소를 호스트와 프로토콜로 나누어 표준입력으로 전달하고 한 라인을 비운다.

  3. git-credential-store 명령은 <1>에서 저장한 사용자이름과 암호를 표준출력으로 응답한다.

~/git.store 파일의 내용은 사실 아래와 같다.

https://bob:s3cre7@mygithost

단순한 텍스트파일로 인증정보가 포함된 URL 형태로 저장한다. osxkeychain 이나 wincred Helper를 사용하면 OS에서 제공하는 좀 더 안전한 저장소에 인증정보를 저장한다. cache Helper의 경우 나름의 형식으로 메모리에 인증정보를 저장하고 다른 프로세스에서는 (메모리의 내용을) 읽어갈 수 없다.

맞춤 Credential 캐시

git-credential-store 나 다른 명령도 독립된 프로그램이다. 아무 스크립트나 프로그램도 Git Credential Helper가 될 수 있다. 이미 Git이 제공하는 Helper로도 충분하지만 모든 경우를 커버하지 않는다. 예를 들어 어떤 인증정보는 팀 전체가 공유해야 한다. 배포에 사용하는 인증정보가 그렇다. 이 인증정보는 공유하는 디렉토리에 저장해두고 사용한다. 이 인증정보는 자주 변경되기 때문에 로컬 Credential 저장소에 저장하지 않고 사용하고자 한다. 이런 경우라면 Git이 제공하는 Helper로는 부족하며 자신만의 맞춤 Helper가 필요하다. 맞춤 Helper는 아래와 같은 기능을 제공해야 한다.

  1. 새 맞춤 Helper가 집중해야 할 액션은 get 뿐이다. storeerase 액션은 저장하는 기능이기 때문에 이 액션을 받으면 깔끔하게 바로 종료한다.

  2. 공유하는 Credential 파일은 git-credential-store 명령이 저장하는 형식과 같은 형식을 사용한다.

  3. Credential 파일의 위치는 기본 값을 사용해도 되지만 파일 경로를 넘길 수 있다.

예제로 보여주는 맞춤 Helper도 Ruby로 작성한다. 하지만, 다른 어떤 언어를 사용해도 Git이 실행할 수만 있다면 상관없다. 작성한 저장소 Helper의 소스코드는 아래와 같다.

#!/usr/bin/env ruby

require 'optparse'

path = File.expand_path '~/.git-credentials' (1)
OptionParser.new do |opts|
    opts.banner = 'USAGE: git-credential-read-only [options] <action>'
    opts.on('-f', '--file PATH', 'Specify path for backing store') do |argpath|
        path = File.expand_path argpath
    end
end.parse!

exit(0) unless ARGV[0].downcase == 'get' (2)
exit(0) unless File.exists? path

known = {} (3)
while line = STDIN.gets
    break if line.strip == ''
    k,v = line.strip.split '=', 2
    known[k] = v
end

File.readlines(path).each do |fileline| (4)
    prot,user,pass,host = fileline.scan(/^(.*?):\/\/(.*?):(.*?)@(.*)$/).first
    if prot == known['protocol'] and host == known['host'] and user == known['username'] then
        puts "protocol=#{prot}"
        puts "host=#{host}"
        puts "username=#{user}"
        puts "password=#{pass}"
        exit(0)
    end
end
  1. 우선 명령 옵션을 처리한다. 옵션으로는 Credential 파일명이 들어온다. 기본 값은 ~/.git-credentials 이다.

  2. Helper 프로그램은 get 액션만 처리하며 Credential 파일이 존재하는 경우만 처리한다.

  3. 이후에는 빈 라인이 나타날 때까지 표준입력으로부터 한 줄 한 줄 읽는다. 각 라인을 파싱하여 known 해시에 저장하고 <4>의 응답에서 사용한다.

  4. 이 루프에서 Credential 파일을 읽어서 <3>의 해시에 해당하는 정보를 찾는다. known 해시에서 프로토콜과 호스트 정보가 일치하는 경우 사용자이름과 암호를 포함하여 결과를 출력한다.

이 파일을 git-credential-read-only 로 저장하고 PATH 에 등록된 디렉토리 중 하나에 위치시키고 실행 권한을 부여한다. Helper를 실행하면 아래와 같다.

$ git credential-read-only --file=/mnt/shared/creds get
protocol=https
host=mygithost

protocol=https
host=mygithost
username=bob
password=s3cre7

위에서 저장한 파일 이름이 “git-” 으로 시작하기 때문에 아래와 같이 간단한 이름으로 설정해서 사용할 수 있다.

$ git config --global credential.helper 'read-only --file /mnt/shared/creds'

이렇게 살펴본 대로 Credential 저장소를 필요에 따라 맞춤 프로그램을 작성해서 확장하는 것이 어렵지 않다. 스크립트를 만들어 사용자나 팀의 가려운 부분을 긁어줄 수 있다.