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 會將使用者名稱和密碼傳送到清單中的所有輔助程式,它們可以選擇如何處理這些憑證。如果您的隨身碟上有一個憑證檔案,但又想在隨身碟未插入時使用記憶體內的快取來節省一些打字時間,以下是一個 .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
  1. 這是啟動互動的命令列。

  2. Git-credential 然後等待 stdin 的輸入。我們提供我們所知道的內容:協定和主機名稱。

  3. 一個空白行表示輸入已完成,憑證系統應該回應用它所知道的內容。

  4. Git-credential 然後接管,並將它找到的資訊寫入 stdout。

  5. 如果找不到憑證,Git 會要求使用者輸入使用者名稱和密碼,並將它們回傳到調用 stdout(這裡它們附加到同一個主控台)。

憑證系統實際上正在調用一個與 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

在 shell 中評估 ! 後面的程式碼

因此,上面描述的輔助程式實際上被命名為 git-credential-cachegit-credential-store 等等,我們可以設定它們以接受命令列參數。它的一般形式是「git-credential-foo [args] <action>」。stdin/stdout 協定與 git-credential 相同,但它們使用稍微不同的動作集。

  • get 是要求提供使用者名稱/密碼對。

  • store 是要求將一組憑證儲存在此輔助程式的記憶體中。

  • erase 從此輔助程式的記憶體中清除給定屬性的憑證。

對於 storeerase 動作,不需要回應(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
  1. 在這裡,我們告訴 git-credential-store 儲存一些憑證:當存取 https://mygithost 時,將使用使用者名稱「bob」和密碼「s3cre7」。

  2. 現在我們將擷取這些憑證。我們提供我們已知的連線部分(https://mygithost),以及一個空行。

  3. git-credential-store 回應我們上面儲存的使用者名稱和密碼。

以下是 ~/git.store 檔案的樣子

https://bob:s3cre7@mygithost

它只是一系列的行,每一行都包含一個憑證裝飾的 URL。osxkeychainwincred 輔助程式使用其後端儲存的原生格式,而 cache 使用其自己的記憶體格式(其他程序無法讀取)。

自訂憑證快取

鑑於 git-credential-store 和相關工具都是與 Git 分開的程式,不難發現任何程式都可以是 Git 憑證輔助程式。Git 提供的輔助程式涵蓋了許多常見的使用案例,但並非全部。例如,假設您的團隊有一些與整個團隊共用的憑證,可能用於部署。這些憑證儲存在共用目錄中,但您不想將它們複製到您自己的憑證儲存區,因為它們經常變更。現有的輔助程式都不涵蓋這種情況;讓我們看看編寫我們自己的輔助程式需要做什麼。這個程式需要具有幾個關鍵功能:

  1. 我們唯一需要注意的動作是 getstoreerase 是寫入操作,因此當它們被接收時,我們將乾淨地退出。

  2. 共用憑證檔案的檔案格式與 git-credential-store 使用的格式相同。

  3. 該檔案的位置相當標準,但為了以防萬一,我們應該允許使用者傳遞自訂路徑。

再一次,我們將用 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
  1. 在這裡,我們解析命令列選項,允許使用者指定輸入檔案。預設值是 ~/.git-credentials

  2. 只有當動作是 get 且後端儲存檔案存在時,此程式才會回應。

  3. 此迴圈從 stdin 讀取,直到達到第一個空白行。輸入會儲存在 known hash 中以供稍後參考。

  4. 此迴圈讀取儲存檔案的內容,尋找匹配項。如果 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'

如您所見,擴充此系統非常簡單,並且可以為您和您的團隊解決一些常見問題。

scroll-to-top