-
1. 起步
-
2. Git 基础
-
3. Git 分支
-
4. 服务器上的 Git
- 4.1 协议
- 4.2 在服务器上部署 Git
- 4.3 生成 SSH 公钥
- 4.4 架设服务器
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管服务
- 4.10 小结
-
5. 分布式 Git
-
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 传输协议连接远程仓库,可以使用没有密码短语(passphrase)的密钥,从而在无需输入用户名和密码的情况下安全地传输数据。然而,HTTP 协议无法做到这一点——每次连接都需要用户名和密码。对于采用双重验证的系统,这种需求变得更加困难,因为你用于密码的令牌是随机生成且难以拼读的。
幸运的是,Git 提供了一个凭证系统来辅助解决这个问题。Git 开箱即提供了一些选项:
-
默认情况下,它不会进行任何缓存。每次连接都会提示你输入用户名和密码。
-
“cache”模式会将凭证在内存中保留一段时间。密码永远不会存储在磁盘上,并且在 15 分钟后会从缓存中清除。
-
“store”模式将凭证以明文形式保存到磁盘文件,且永不过期。这意味着在修改 Git 主机的密码之前,你无需再次输入凭证。这种方法的缺点是密码以明文形式存储在主目录的一个普通文件中。
-
如果你使用的是 macOS,Git 自带“osxkeychain”模式,它会将凭证缓存到绑定在你系统账户上的安全钥匙串(keychain)中。这种方法会将凭证存储在磁盘上且永不过期,但它们会通过与存储 HTTPS 证书和 Safari 自动填充信息相同的系统进行加密。
-
如果你使用的是 Windows,可以在安装 Git for Windows 时启用 Git Credential Manager 功能,或者单独安装 最新版的 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 会要求用户输入用户名和密码,并将其回传给调用的 stdout(此处它们被挂载到同一个控制台)。
凭证系统实际上是在调用一个独立于 Git 本身的程序;具体调用哪个程序以及如何调用,取决于 credential.helper 的配置值。它可以有多种形式:
| 配置值 | 行为 |
|---|---|
|
运行 |
|
运行 |
|
运行 |
|
|
所以,上面描述的助手实际上被命名为 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'
正如你所见,扩展此系统非常简单,可以为你和你的团队解决一些常见问题。