Git笔记(38) 凭证存储


1. 凭证存储

如果使用的是 SSH 方式连接远端,并且设置了一个没有口令的密钥
就可以在不输入用户名和密码的情况下安全地传输数据
然而,这对 HTTP 协议来说是不可能的 —— 每一个连接都是需要用户名和密码的
这在使用双重认证的情况下会更麻烦
因为你需要输入一个随机生成并且毫无规律的 token 作为密码

幸运的是,Git 拥有一个凭证系统来处理这个事情

下面有一些 Git 的选项:

  1. 默认所有都不缓存,每一次连接都会询问你的用户名和密码

  2. cache” 模式会将凭证存放在内存中一段时间,密码永远不会被存储在磁盘中,并且在15分钟后从内存中清除

  3. store” 模式会将凭证用明文的形式存放在磁盘中,并且永不过期

  4. 如果你使用的是 Mac,还有一种 “osxkeychain” 模式,它会将凭证缓存到你系统用户的钥匙串中
    这种方式将凭证存放在磁盘中,并且永不过期
    但是是被加密的,这种加密方式与存放 HTTPS 凭证以及 Safari 的自动填写是相同的

  5. 如果使用的是 Win,可以安装一个叫做 “Git Credential Manager for Windows” 的辅助工具
    这和上面说的 “osxkeychain” 十分类似,但是是使用 Windows Credential Store 来控制敏感信息
    可以在 https://github.com/Microsoft/Git-Credential-Manager-for-Windows 下载

你可以设置 Git 的配置来选择上述的一种方式

$ git config --global credential.helper cache

部分辅助工具有一些选项:

  1. store” 模式可以接受一个 --file <path> 参数,可以自定义存放密码的文件路径(默认是 ~/.git-credentials )
  2. 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

2. 底层实现

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-cachegit-credential-store 之类
可以配置它们来接受命令行参数

通常的格式是 “git-credential-foo [args] <action>” 标准输入/输出协议和 git-credential 一样
但它们使用的是一套稍微不太一样的行为:

  1. get 是请求输入一对用户名和密码
  2. store 是请求保存一个凭证到辅助工具的内存
  3. 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 使用它的内存格式(其他进程无法读取)


3. 自定义凭证缓存

已经知道 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' (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

我们在这里解析命令行参数,允许用户指定输入文件,默认是 ~/.git-credentials.
这个程序只有在接受到 get 行为的请求并且后端存储的文件存在时才会有输出

这个循环从标准输入读取数据,直到读取到第一个空行
输入的数据被保存到 known 哈希表中,之后需要用到

这个循环读取存储文件中的内容,寻找匹配的行
如果 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官网介绍删减、添加和修改组成


相关推荐:

Git笔记(37) 替换
Git笔记(36) 打包
Git笔记(35) 子模块
Git笔记(34) 调试
Git笔记(33) Rerere


谢谢

发布了265 篇原创文章 · 获赞 358 · 访问量 337万+

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie

分享到微信朋友圈

×

扫一扫,手机浏览