7.14 凭证存储

最后更新于:2022-04-02 01:34:52

## 凭证存储 如果你使用的是 SSH 方式连接远端,并且设置了一个没有口令的密钥,这样就可以在不输入用户名和密码的情况下安全地传输数据。 然而,这对 HTTP 协议来说是不可能的 —— 每一个连接都是需要用户名和密码的。 这在使用双重认证的情况下会更麻烦,因为你需要输入一个随机生成并且毫无规律的 token 作为密码。 幸运的是,Git 拥有一个凭证系统来处理这个事情。 下面有一些 Git 的选项: * 默认所有都不缓存。 每一次连接都会询问你的用户名和密码。 * “cache” 模式会将凭证存放在内存中一段时间。 密码永远不会被存储在磁盘中,并且在15分钟后从内存中清除。 * “store” 模式会将凭证用明文的形式存放在磁盘中,并且永不过期。 这意味着除非你修改了你在 Git 服务器上的密码,否则你永远不需要再次输入你的凭证信息。 这种方式的缺点是你的密码是用明文的方式存放在你的 home 目录下。 * 如果你使用的是 Mac,Git 还有一种 “osxkeychain” 模式,它会将凭证缓存到你系统用户的钥匙串中。 这种方式将凭证存放在磁盘中,并且永不过期,但是是被加密的,这种加密方式与存放 HTTPS 凭证以及 Safari 的自动填写是相同的。 * 如果你使用的是 Windows,你可以安装一个叫做 “winstore” 的辅助工具。 这和上面说的 “osxkeychain” 十分类似,但是是使用 Windows Credential Store 来控制敏感信息。 可以在 [*https://gitcredentialstore.codeplex.com*](https://gitcredentialstore.codeplex.com/) 下载。 你可以设置 Git 的配置来选择上述的一种方式 ~~~ $ git config --global credential.helper cache ~~~ 部分辅助工具有一些选项。 “store” 模式可以接受一个 `--file ` 参数,可以自定义存放密码的文件路径(默认是`~/.git-credentials`)。 “cache” 模式有 `--timeout ` 参数,可以设置后台进程的存活时间(默认是 “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`,这个命令接收一个参数,并通过标准输入获取更多的参数。 举一个例子更容易理解。 我们假设已经配置好一个凭证辅助工具,这个辅助工具保存了 `mygithost`的凭证信息。 下面是一个使用 “fill” 命令的会话,当 Git 尝试寻找一个服务器的凭证时就会被调用。 ~~~ $ git credential fill protocol=https host=mygithost protocol=https host=mygithost username=bob password=s3cre7 $ git credential fill protocol=https host=unknownhost Username for 'https://unknownhost': bob Password for 'https://bob@unknownhost': protocol=https host=unknownhost username=bob password=s3cre7 ~~~ [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb9927824.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-1)这是开始交互的命令。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb9933920.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-2)Git-credential 接下来会等待标准输入。 我们提供我们所知道的信息:协议和主机名。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb993ede8.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-3)一个空行代表输入已经完成,凭证系统应该输出它所知道的信息。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb994b297.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-4)接下来由 Git-credential 接管,并且将找到的信息打印到标准输出。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb98cc43d.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO2-5)如果没有找到对应的凭证,Git 会询问用户的用户名和密码,我们将这些信息输入到在标准输出的地方(这个例子中是同一个控制台)。 凭证系统实际调用的程序和 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-cache`、`git-credential-store` 之类,我们可以配置它们来接受命令行参数。 通常的格式是 “git-credential-foo [args] .” 标准输入/输出协议和 git-credential 一样,但它们使用的是一套稍微不太一样的行为: * `get` 是请求输入一对用户名和密码。 * `store` 是请求保存一个凭证到辅助工具的内存。 * `erase` 会将给定的证书从辅助工具内存中清除。 对于 `store` 和 `erase` 两个行为是不需要返回数据的(Git 也会忽略掉)。 然而对于 `get`,Git 对辅助工具的返回信息十分感兴趣。 如果辅助工具没有任何有用的信息,它可以直接退出而不需要输出任何东西,但如果它有这些信息,它在提供的信息后面增加它所拥有的信息。 这些输出会被视为一系列的赋值语句;每一个提供的数据都会将 Git 已有的数据替换掉。 这有一个和上面一样的例子,但是跳过了 git-credential 这一步,直接到 git-credential-store: ~~~ $ git credential-store --file ~/git.store store protocol=https host=mygithost username=bob password=s3cre7 $ git credential-store --file ~/git.store get protocol=https host=mygithost username=bob password=s3cre7 ~~~ [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb9927824.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO3-1)我们告诉 `git-credential-store` 去保存凭证:当访问 `https://mygithost` 时使用用户名 “bob”,密码是 “s3cre7”。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb9933920.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO3-2)现在我们取出这个凭证。 我们提供连接这部分的信息(`https://mygithost`)以及一个空行。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb993ede8.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO3-3)`git-credential-store` 输出我们之前保存的用户名和密码。 `~/git.store` 文件的内容类似: ~~~ https://bob:s3cre7@mygithost ~~~ 仅仅是一系列包含凭证信息URL组成的行。 `osxkeychain` 和 `winstore` 辅助工具使用它们后端存储的原生格式,而 `cache` 使用它的内存格式(其他进程无法读取)。 ## 自定义凭证缓存 已经知道 `git-credential-store` 之类的是和 Git 是相互独立的程序,就不难理解 Git 凭证辅助工具可以是 *任意* 程序。 虽然 Git 提供的辅助工具覆盖了大多数常见的使用场景,但并不能满足所有情况。 比如,假设你的整个团队共享一些凭证,也许是在部署时使用。 这些凭证是保存在一个共享目录里,由于这些凭证经常变更,所以你不想把它们复制到你自己的凭证仓库中。 现有的辅助工具无法满足这种情况;来看看我们如何自己实现一个。 这个程序应该拥有几个核心功能: 1. 我们唯一需要关注的行为是 `get`;`store` 和 `erase` 是写操作,所以当接受到这两个请求时我们直接退出即可。 2. 共享的凭证文件格式和 `git-credential-store` 使用的格式相同。 3. 凭证文件的路径一般是固定的,但我们应该允许用户传入一个自定义路径以防万一。 我们再一次使用 Ruby 来编写这个扩展,但只要 Git 能够执行最终的程序,任何语言都是可以的。 这是我们的凭证辅助工具的完整代码: ~~~ #!/usr/bin/env ruby require 'optparse' path = File.expand_path '~/.git-credentials' OptionParser.new do |opts| opts.banner = 'USAGE: git-credential-read-only [options] ' 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' exit(0) unless File.exists? path known = {} while line = STDIN.gets break if line.strip == '' k,v = line.strip.split '=', 2 known[k] = v end File.readlines(path).each do |fileline| prot,user,pass,host = fileline.scan(/^(.*?):\/\/(.*?):(.*?)@(.*)$/).first if prot == known['protocol'] and host == known['host'] then puts "protocol=#{prot}" puts "host=#{host}" puts "username=#{user}" puts "password=#{pass}" exit(0) end end ~~~ [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb9927824.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO4-1)我们在这里解析命令行参数,允许用户指定输入文件,默认是 `~/.git-credentials`. [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb9933920.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO4-2)这个程序只有在接受到 `get` 行为的请求并且后端存储的文件存在时才会有输出。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb993ede8.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO4-3)这个循环从标准输入读取数据,直到读取到第一个空行。 输入的数据被保存到 `known` 哈希表中,之后需要用到。 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-12_561bcb994b297.png)](http://git-scm.com/book/zh/v2/ch00/co_git____CO4-4)这个循环读取存储文件中的内容,寻找匹配的行。 如果 `known` 中的协议和主机名与该行相匹配,这个程序输出结果并退出。 我们把这个辅助工具保存为 `git-credential-read-only`,放到我们的 `PATH` 路径下并且给予执行权限。 一个交互式会话类似: ~~~ $ 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 ~~~ 正如你看到的,扩展这个系统是相当简单的,并且可以为你和你的团队解决一些常见问题。
';