首先,我想指出这与Git无关(除了Git可以使用ssh),而与ssh以及GitHub如何使用ssh来确定“你”是谁有关。
ssh和ssh-agent的细节(您在自己的答案中使用)非常复杂,但原则足够简单。 ssh的工作方式是使用公共/私人密钥对:
- 私钥只有您自己知道。
- 公钥为所有人所知。
拥有公钥的任何人都可以加密消息并将其发送给您。 然后,您可以解密消息并查看其内容。 在GitHub的情况下,您会向他们提供公钥,并告诉他们这个公钥是“您”的公钥。
稍后,您需要让您的计算机 - 无论使用哪个 - 调用GitHub并向他们提供相同的公钥。他们查找此公钥。现在,任何人都可以将此公钥发送给他们,因此他们还不确定这是否真的是您,但他们暂时相信,因为您给了他们那个特定的公钥,您声称自己是您自己。
(要设置或添加声称是您的公钥,请参见
this GitHub文档页面。请注意,GitHub会查询您的浏览器以猜测您正在使用哪个操作系统,然后再向您发送此页面的内容。如果您倾向于使用Mac进行浏览和Linux进行编程工作,则默认情况下会收到错误的指令集。页面上有可点击的项目来更改发送的指令集:请确保选择正确的指令集。)
现在,仅仅因为一个声称自己是你的人给GitHub打电话,并不意味着他们确实是你。因此,在回答了ssh风格的互联网“电话”并看到这个公钥后,Git将会探测你的ssh连接以确定那是否真的是你。他们生成一个“秘密”(随机字节)消息,使用公钥加密它,并将其发送给你。如果你有私钥,你的电脑可以解密刚生成的“秘密消息”并将原始字节传递回他们。完成这一步骤后,他们现在相信你确实是你。
所以这就是底层机制:你有一个秘密密钥和一个公钥。你公开公钥。任何拥有它的人都可以声称自己是你。但你保持秘密的那个秘密,并且如果有人声称是你,那个不确定这是否真的是你的实体——在这种情况下是GitHub——会加密一些东西并向你发起挑战来解密它。如果你可以,那么他们就相信你确实是你。
附:你为什么相信你所呼叫的计算机是真正的GitHub?如果有人偷偷接管了他们的计算机,邪恶帝国现在声称自己是GitHub,那该怎么办?(答案不在本问答中,这只是一个思考问题。)
生成密钥对
使用 ssh-keygen
。目前,它至少在Linux、macOS和Windows上都很常见。如何找到并分发公钥,已经生成了密钥对,但它们都使用 ssh-keygen
来制作密钥对。
你把秘密存储在哪里?
为了使上述方法起作用,您需要让您的计算机——您的笔记本电脑或其他设备——存储一些秘密。但是,如果它被存储了,那么它还有多少秘密呢?
ssh系统可以加密您的秘密数据,并且只有在输入密码短语时才能解密使用。因此,保护您的秘密的一种方法是将其隐藏在另一个秘密之下。但是,如何存储和保护那个秘密呢?这就像乌龟一样无穷无尽。(xkcd版本)
为了避免每次都要输入密码短语,您可以启动一个代理。代理的工作是暂时记住秘密密钥,并以授权的方式使用它。谁被授权做什么?好吧,这很棘手,我现在不会详细介绍,但我们会回到这个问题。
如果您有多个秘密怎么办?
GitHub确实允许您存储多个公钥。因此,您可以为每台计算机生成一个公钥并将其存储在那里,每台计算机都可以拥有自己的秘密密钥。
如果有可能会丢失计算机,并且您希望能够撤销该计算机的访问权限,则为每台计算机分配单独的公钥/私钥对是一个不错的方案。因此,请考虑这样做。但也可以只使用一个密钥,在多台计算机上使用它。
要在多台计算机上共享公钥/私钥对,通常需要将至少公钥复制到多台计算机上(以便它们都使用相同的公钥)。根据您是否以及如何使用代理,您可以保留私钥私有。
每种方法都有其优缺点。
关于代理的更多信息
如果您使用ssh代理,可以并且应该执行以下操作之一:
- 在您的计算机上每个运行会话只有一个(这需要定义一个会话);
- 如果可能,避免将私钥存储在此计算机上;
- 如果您有多个密钥,则使用代理程序解锁一些但不是全部密钥; 和/或
- 使用您的ssh配置文件(通常在`$ HOME/.ssh/config`中)指定向谁发送哪个公钥。
这很复杂和混乱,各种操作系统的一些细节显示在这里。首先,让我们定义一个会话。
抽象地说,会话背后的想法是,它就像是你在键盘前、电脑上的一样。打开多少个窗口都没关系,它们都是“你”。如果你在笔记本电脑L上,并从笔记本电脑远程登录到A和B计算机,A和B计算机通常需要出于计算机方面的原因启动新的“会话”。理想情况下,它们不需要这样做,因此ssh具有一种系统,代理(例如A和B计算机上的代理)可以与其他代理进行通信。这样,您可以在L笔记本电脑上启动一个代理,然后让A和B上的代理与L上的代理交流,以获得临时访问正确秘钥的权限。
这真的可能会变得非常混乱。如果您只有一个秘密密钥,那至少可以降低复杂度水平,但是您可能希望有一个密钥对来识别“在家中的您而不是在工作中的您”,以及另一个密钥对来识别“在工作中的您而不是在家中的您”。或者,您可能希望每个主机都有一个(或更多!)密钥对,以便L、A和B都有不同的密钥对,以防其中一台机器被盗或受到威胁。我不能为您选择正确的方法。没有什么比考虑自己的情况并自行决定更好的替代方案。
无论如何,这里的重点是,如果您在单台计算机上打开多个窗口,则
可能需要告诉不同的命令解释器(Linux上的shell、bash实例)
共享会话。Macs(通过macOS)拥有一个非常好的系统,当您第一次登录时自动为您设置所有这些内容;Linux和Windows通常不会这样做(尽管Linux窗口管理器可以像macOS一样聪明——我只是没有使用过像这样的窗口管理器;我使用的Linux系统通常是独立的机器,我必须通过ssh进行连接)。
环境变量,或者说ssh-agent如何告知其存在
注意:我不使用Windows,因此这里没有Windows指导。
在Linux系统上,当您首次登录时,会获得一个shell——bash、dash、fish、sh、tcsh、zsh等任何您喜欢的。如果您在笔记本电脑上运行Linux,它可能有一个窗口管理器,像上面提到的macOS一样做一些花哨的事情。接下来的内容假定它没有。
Unix类系统中的进程严格按层级划分。每个进程都有我们称之为环境的东西,由具有值的环境变量组成。在我们使用的shell中,我们使用类似这样的结构来表示它们:
HOME=/home/username
USER=username
TERM=xterm-256color
传统上,变量名称都是全大写的,根据你的shell,它们也必须是有效的shell变量名称。2任何进程中的环境变量集取决于该进程:一旦该进程已启动和运行,它可以更改它们、添加到它们、删除其中一些或全部,等等。此时,其他进程不能更改它们:只有原始进程才能做到这一点。但是,现在这个原始进程正在运行,它可以生成(fork
-and-exec
)一个新的进程,并提供该新进程启动进程喜欢的任何环境。
对于使用其中一个shell(命令行解释器)的用户而言,这意味着您可以通过某些环境变量向可以运行程序的程序提供初始环境设置:
$ FOO=bar command arg1 arg2
(假设使用
sh
样式的shell)以给定命令和两个参数运行该命令,但首先还设置名为
FOO
的环境变量为值
bar
。 因此,运行
command
的进程及其运行的任何进程都继承了此
FOO=bar
设置。
更具体地说,在使用Git时,我们可以运行:
$ GIT_TRACE2=1 git status
想要获取与git前端运行status子命令所执行的操作以及各种操作所需时间相关的一些信息。
这个特定的表单为单个命令设置一个环境变量。要将其设置为所有未来的命令,在shell本身退出(或值被更改)之前,我们使用shell的变量设置语法:
$ FOO=bar
但这只是设置了一个普通的shell变量,所以我们再添加一个命令:
$ export FOO
这告诉shell将该变量放入导出的环境变量集合中,以便传递给每个命令。在大多数shell中,您可以将它们结合使用:
$ export FOO=bar
这段文字讲述了如何在一条命令中同时设置变量并导出它,以及任何命令都可以继承这些设置但不能更改shell的设置。子命令所做的任何更改都可以在子命令和子命令本身运行的命令中保持,但是一旦进程退出,其所有环境设置都将被丢弃。换句话说,没有命令可以直接设置shell的环境,只有shell可以设置自己的环境。但是,命令可以打印shell命令(或将它们写入文件等),然后我们可以要求shell运行那些命令。如果我们打印命令git status,并要求shell运行我们打印的命令,shell将运行git status。
$ eval `echo git status`
On branch master
(在我的Shell窗口中查看Git存储库)。如果我们要求Shell设置一些环境变量怎么办?
eval `echo FOO=bar; export FOO`
这是一种相当愚蠢的设置和导出
FOO=bar
的方式。但如果我们将这些命令放入某个程序的输出中,并
eval
它,我们就可以让shell设置和导出环境变量,这些变量会在同一个shell实例中运行的未来命令中保留下来。
这就是
ssh-agent
的工作原理。它会向标准输出打印命令。因此,当您第一次在某台计算机上启动代理时——例如,在登录并已经有一个单独的命令行窗口正在运行带有shell提示符的情况下:
$ eval `ssh-agent -s`
例如,
ssh-agent
的作用是:
- 启动代理程序;
- 打印以下文本(来自手动运行
ssh-agent -s
的输出):
SSH_AUTH_SOCK=/tmp/ssh-ExiC7A6qilWW/agent.11761; export SSH_AUTH_SOCK;
SSH_AGENT_PID=11762; export SSH_AGENT_PID;
echo Agent pid 11762;
通过对上述文本进行
eval
评估,我们的shell设置了两个环境变量,这些变量将一直存在,直到窗口本身被关闭。
1现代Linux允许进程被“重新父化”,这使得它不那么严格了,但继承链仍然是自上而下的。我知道这个陈述大多是术语,但我还没有想出一个真正能表达我的意思的好方法,除了坚持这篇脚注所指的文本。
2一个有趣的技巧,目前我不知道它有什么真正的价值,就是从你可以做到这一点的代码中导出一些错误格式的变量值:要么不是var=value
的形式,要么使用一个shell不支持的变量名。设置好这个“不可能”的环境后,启动一个shell,看看它会做什么。一些shell可能会删除这些环境变量,一些可能会保持它们不变,还有一些可能会做一些奇怪的事情,比如试图对它们进行消毒。
这个技巧的缺陷
这里有一个明显的缺陷。代理程序在那个窗口中启动,并打印使该窗口中的shell保存SSH_AUTH_SOCK
路径名和SSH_AGENT_PID
进程ID的内容。但是当窗口完成时,当您关闭它或退出shell时,这些保存的值会消失。如果您设置了shell以使用ssh-agent -k
来终止代理程序,则代理程序本身也会消失。现在就没有代理程序了。
如果您希望在其他窗口中使用代理程序,当然可以启动一个新窗口,它会启动一个新的shell,然后再次使用eval `ssh-agent -s`
。这不是很好,但它可以工作:每个窗口都需要输入自己的密码来解锁访问秘密密钥,这甚至更不好。
macOS的技巧是在你登录到Mac之前启动代理,然后再启动任何终端窗口。然后每个终端窗口都会继承登录级别的ssh设置。我们可以在Linux上模拟这个过程(有点混乱),方法是检测某个现有窗口中是否正在运行代理,或者如果您的Linux系统配置了这样的方式,则通过在启动X服务器之前启动代理来模拟它(许多系统没有这样的配置),或者从xdm启动它。
代理转发
还有最后一个相对较大的障碍,与ssh 代理转发有关。如果您想将所有秘钥放在一台计算机上,例如笔记本电脑L,但在从L到A或B运行ssh之后能够在计算机A和B上使用它们,则可以通过代理转发以不错的方式实现此目的。
要启用代理转发,请使用
ssh -A
或在您的ssh配置中设置
ForwardAgent yes
。这将使机器A上的ssh代理(当您从笔记本电脑L
ssh -A machine-A
时)访问机器L上的ssh代理。因此,现在,与机器A上的代理联系的进程会通过传递到机器L(您的笔记本电脑)。这样,即使机器A仅有您的
公钥,机器A也可以作为您来验证GitHub。当GitHub回到机器A并说
证明它:解密这个字节字符串时,由于机器A声称是您,因此机器A的ssh代理请求机器L的ssh代理来解密字节,获取解密后的字节,并将其交给GitHub,现在GitHub相信机器A实际上是您。
~/.ssh/github_key
和~/.ssh/github_key.pub
中的ssh密钥对(私钥和公钥)。我按照Github帮助中的说明将公钥添加到了Github上。当通过~/.ssh/config
文件的条目连接到Github时,该特定密钥文件被提供给ssh-agent:https://pastebin.com/1gV3c99U - banskt