-
A1. 附錄 A: Git 在其他環境
- A1.1 圖形介面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小結
-
A2. 附錄 B: 在應用程式中嵌入 Git
-
A3. 附錄 C: Git 命令
7.14 Git 工具 - 憑證儲存
憑證儲存
如果你使用 SSH 傳輸方式連線遠端倉庫,可以擁有一個沒有密碼短語的金鑰,這允許你無需輸入使用者名稱和密碼即可安全地傳輸資料。然而,對於 HTTP 協議來說,這不可能實現——每次連線都需要使用者名稱和密碼。對於啟用雙因素認證的系統來說,這變得更加困難,因為用作密碼的令牌是隨機生成且無法發音的。
幸運的是,Git 有一個憑證系統可以幫助解決這個問題。Git 內建了一些選項:
-
預設情況下不進行任何快取。每次連線都會提示你輸入使用者名稱和密碼。
-
“cache”模式會將憑證在記憶體中儲存一段時間。密碼絕不會儲存在磁碟上,並在15分鐘後從快取中清除。
-
“store”模式將憑證儲存到磁碟上的一個純文字檔案中,並且永不過期。這意味著除非你更改 Git 主機的密碼,否則你將無需再次輸入憑證。這種方法的缺點是,你的密碼以明文形式儲存在主目錄中的一個普通檔案中。
-
如果你使用的是 macOS,Git 提供了一個“osxkeychain”模式,它會將憑證快取到附加到你的系統賬戶的安全鑰匙串中。這種方法將憑證儲存在磁碟上,並且永不過期,但它們使用與儲存 HTTPS 證書和 Safari 自動填充相同的系統進行加密。
-
如果你使用的是 Windows,在安裝 Git for Windows 時可以啟用 Git 憑證管理器 功能,或者單獨安裝 最新版 GCM 作為獨立服務。這類似於上面描述的“osxkeychain”助手,但它使用 Windows 憑證儲存來控制敏感資訊。它還可以為 WSL1 或 WSL2 提供憑證。請參閱 GCM 安裝說明 以獲取更多資訊。
你可以透過設定一個 Git 配置值來選擇其中一種方法:
$ git config --global credential.helper cache
其中一些助手帶有選項。“store”助手可以接受一個 --file <path>
引數,用於自定義純文字檔案的儲存位置(預設是 ~/.git-credentials
)。“cache”助手接受 --timeout <seconds>
選項,它會改變其守護程序保持執行的時間(預設是“900”,即15分鐘)。下面是一個如何使用自定義檔名配置“store”助手的示例:
$ git config --global credential.helper 'store --file ~/.my-credentials'
Git 甚至允許你配置多個助手。當查詢特定主機的憑證時,Git 會按順序查詢它們,並在提供第一個答案後停止。當儲存憑證時,Git 會將使用者名稱和密碼傳送給所有列表中的助手,它們可以選擇如何處理這些資訊。如果你的憑證檔案在一個 U 盤上,但又想在 U 盤未插入時使用記憶體快取來減少輸入,那麼 .gitconfig
檔案會是這樣:
[credential]
helper = store --file /mnt/thumbdrive/.git-credentials
helper = cache --timeout 30000
內部機制
這都如何工作呢?Git 憑證助手系統的根命令是 git credential
,它接受一個命令作為引數,然後透過標準輸入 (stdin) 獲取更多輸入。
透過一個示例可能更容易理解。假設憑證助手已配置,並且助手已為 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
-
這是啟動互動的命令列。
-
git-credential
隨後在標準輸入 (stdin) 上等待輸入。我們向它提供我們已知的資訊:協議和主機名。 -
一個空行表示輸入完成,憑證系統應該用它所知道的資訊來回答。
-
git-credential
隨後接管,並將其找到的資訊寫入標準輸出 (stdout)。 -
如果未找到憑證,Git 會要求使用者輸入使用者名稱和密碼,並將它們提供回撥用方的標準輸出(這裡它們連線到同一個控制檯)。
憑證系統實際上是呼叫一個獨立於 Git 本身的程式;呼叫哪個程式以及如何呼叫取決於 credential.helper
配置值。它有幾種形式:
配置值 | 行為 |
---|---|
|
執行 |
|
執行 |
|
執行 |
|
! 後的程式碼在 shell 中評估 |
所以上面描述的助手實際上被命名為 git-credential-cache
、git-credential-store
等等,我們可以配置它們接受命令列引數。其一般形式是“git-credential-foo [引數] <動作>。”標準輸入/輸出 (stdin/stdout) 協議與 git-credential
相同,但它們使用一套略有不同的動作:
-
get
是請求使用者名稱/密碼對。 -
store
是請求在此助手的記憶體中儲存一組憑證。 -
erase
從此助手的記憶體中清除給定屬性的憑證。
對於 store
和 erase
動作,不需要響應(Git 反正會忽略它)。然而,對於 get
動作,Git 對助手要說的話非常感興趣。如果助手不知道任何有用的資訊,它可以簡單地不輸出而退出,但如果它知道,它應該用它儲存的資訊來補充提供的資訊。輸出被視為一系列賦值語句;任何提供的資訊都將替換 Git 已知的資訊。
這是上面相同的示例,但跳過 git-credential
,直接使用 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
-
這裡我們告訴
git-credential-store
儲存一些憑證:當訪問 https://mygithost 時,使用使用者名稱“bob”和密碼“s3cre7”。 -
現在我們來檢索這些憑證。我們提供已知連線部分(https://mygithost),以及一個空行。
-
git-credential-store
回覆了我們上面儲存的使用者名稱和密碼。
~/git.store
檔案看起來像這樣:
https://bob:s3cre7@mygithost
它只是一系列行,每行都包含一個帶有憑證裝飾的 URL。osxkeychain
和 wincred
助手使用其後端儲存的原生格式,而 cache
使用其自己的記憶體格式(其他程序無法讀取)。
自定義憑證快取
鑑於 git-credential-store
和類似的程式都獨立於 Git,不難理解任何程式都可以成為 Git 憑證助手。Git 提供的助手涵蓋了許多常見用例,但並非所有。例如,假設你的團隊有一些與整個團隊共享的憑證,可能用於部署。這些憑證儲存在一個共享目錄中,但你不想將它們複製到你自己的憑證儲存中,因為它們經常更改。現有助手都無法滿足這種情況;讓我們看看編寫自己的助手需要什麼。這個程式需要具備幾個關鍵特性:
-
我們唯一需要關注的動作是
get
;store
和erase
是寫入操作,所以當它們被接收時,我們只需乾淨地退出。 -
共享憑證檔案的格式與
git-credential-store
使用的格式相同。 -
該檔案的位置相當標準,但我們應該允許使用者傳遞自定義路徑,以防萬一。
再次強調,我們將用 Ruby 編寫這個擴充套件,但只要 Git 可以執行最終產品,任何語言都可以。這是我們新憑證助手的完整原始碼:
#!/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.exist? 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
-
這裡我們解析命令列選項,允許使用者指定輸入檔案。預設是
~/.git-credentials
。 -
這個程式只在動作是
get
且後端儲存檔案存在時才響應。 -
這個迴圈從標準輸入 (stdin) 讀取,直到遇到第一個空行。輸入被儲存在
known
雜湊中供以後參考。 -
這個迴圈讀取儲存檔案的內容,查詢匹配項。如果
known
中的協議、主機和使用者名稱與此行匹配,程式將結果列印到標準輸出 (stdout) 並退出。
我們將助手儲存為 git-credential-read-only
,將其放在我們的 PATH
中的某個位置並標記為可執行。這是一個互動式會話的示例:
$ git credential-read-only --file=/mnt/shared/creds get
protocol=https
host=mygithost
username=bob
protocol=https
host=mygithost
username=bob
password=s3cre7
因為它的名稱以“git-”開頭,我們可以使用配置值的簡單語法:
$ git config --global credential.helper 'read-only --file /mnt/shared/creds'
如你所見,擴充套件這個系統非常直接,並且可以為你和你的團隊解決一些常見問題。