Git之深入解析凭证存储

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Git之深入解析凭证存储相关的知识,希望对你有一定的参考价值。

  • 了解了管理或者维护 Git 仓库、实现代码控制所需的大多数日常命令和工作流程,尝试跟了踪和提交文件的基本操作,并且掌握了暂存区和轻量级地分支及合并的威力。如果想进一步对 Git 深入学习,可以学习一些 Git 更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。
  • 如果还不清楚 Git 的基础使用流程、分支的管理、托管服务器的技术以及分布式工作流程等相关的技术和能力,请参考博客:
  • 如果使用的是 SSH 方式连接远端,并且设置了一个没有口令的密钥,这样就可以在不输入用户名和密码的情况下安全地传输数据。然而,这对 HTTP 协议来说是不可能的,每一个连接都是需要用户名和密码的,这在使用双重认证的情况下会更麻烦,因为需要输入一个随机生成并且毫无规律的 token 作为密码。
  • 幸运的是,Git 拥有一个凭证系统来处理这个事情,如下所示:
    • 默认所有都不缓存,每一次连接都会询问你的用户名和密码;
    • “cache” 模式会将凭证存放在内存中一段时间,密码永远不会被存储在磁盘中,并且在15分钟后从内存中清除;
    • “store” 模式会将凭证用明文的形式存放在磁盘中,并且永不过期,这意味着除非修改了在 Git 服务器上的密码,否则永远不需要再次输入凭证信息,这种方式的缺点是密码是用明文的方式存放在 home 目录下;
    • 如果使用的是 Mac,Git 还有一种 “osxkeychain” 模式,它会将凭证缓存到系统用户的钥匙串中,这种方式将凭证存放在磁盘中,并且永不过期,但是是被加密的,这种加密方式与存放 HTTPS 凭证以及 Safari 的自动填写是相同的;
    • 如果使用的是 Windows,可以安装一个叫做 “Git Credential Manager for Windows” 的辅助工具,这和上面说的 “osxkeychain” 十分类似,但是是使用 Windows Credential Store 来控制敏感信息,可以在 Git-Credential-Manager-for-Windows下载。
  • 可以设置 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 (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 会询问用户的用户名和密码,将这些信息输入到在标准输出的地方(这个例子中是同一个控制台)。
  • 凭证系统实际调用的程序和 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 已有的数据替换掉。
  • 这有一个和上面一样的例子,但是跳过了 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 组成的行。 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.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) 在这里解析命令行参数,允许用户指定输入文件,默认是 ~/.git-credentials;
    • (2) 这个程序只有在接受到 get 行为的请求并且后端存储的文件存在时才会有输出;
    • (3) 这个循环从标准输入读取数据,直到读取到第一个空行,输入的数据被保存到 known 哈希表中,之后需要用到;
    • (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'
  • 正如看到的,扩展这个系统是相当简单的,并且可以为团队解决一些常见问题。

以上是关于Git之深入解析凭证存储的主要内容,如果未能解决你的问题,请参考以下文章

Git之深入解析48个经典操作场景的分析和处理,专治不会合并代码

Git之深入解析高级合并

Git之深入解析如何重写提交历史

Git之深入解析如何选择修订的版本

Git之深入解析如何交互式暂存

Git之深入解析如何替换数据库中的Git对象