通过 SSH 访问的远程机器执行 git push 时出现“Permission denied (publickey)”错误。

5
为了解释我的情况,假设我有PC1和PC2。
我在PC2中有一个git仓库,并设置了ssh密钥,这样在执行git push时就不需要输入凭据。无论如何,当我从PC2进行git push时,它都可以正常工作。
现在,如果我从PC1通过ssh连接到PC2,我可以编辑文件并且可以完美地进行git add和commit操作。但是,奇怪的是,如果我使用git push命令,就会出现错误消息:
Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.
如果我回到PC2并直接git push,则git push又能正常工作了。
是否有一种方法可以通过PC1上的ssh连接来执行git push呢?
编辑:截至2021年01月05日,我仍然无法使用ssh连接到GitHub使其工作,但对我有效的解决方法是改用https连接:也就是将远程仓库的url从ssh更改为https,如https://docs.github.com/en/free-pro-team@latest/github/using-git/changing-a-remotes-url所述,然后我就可以从ssh会话中进行git push。但是,最好还是能够直接使用ssh连接到GitHub。

我遇到了相同的问题。在我的情况下,我使用保存在~/.ssh/github_key~/.ssh/github_key.pub中的ssh密钥对(私钥和公钥)。我按照Github帮助中的说明将公钥添加到了Github上。当通过~/.ssh/config文件的条目连接到Github时,该特定密钥文件被提供给ssh-agent:https://pastebin.com/1gV3c99U - banskt
如果上述方法对您有用,我可以将其扩展为完整的答案。 - banskt
3个回答

1
首先,我想指出这与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实际上是您。

感谢您的详细解释!但是,我仍然无法从中提取出解决方案。假设我从Mac笔记本电脑L ssh到Linux桌面A。直接从A进行git push非常完美,但在从L到A的ssh会话中却不行。对我来说,最简单的解决方案似乎是代理转发,因此我创建了一个ssh密钥,并按照https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent中的说明将其添加到L中的代理中。然后,我使用“ssh -A”登录到A。但是,我仍然无法从那里进行git push。 - BK736
一旦您在A上,您需要确保它向Github发送正确的ssh密钥。使用ssh-add -l(在A上打开的窗口中)列出当前密钥集;如果缺少所需的密钥,请使用ssh-add添加它,以便可以发送它。我不确定您需要多久才能ssh-add:A代理和L代理之间的交互对我来说有点神秘。 - torek
在我的设置中,我倾向于拥有个人(“我在家里”)和工作(“我在<公司>”)密钥。我将每个公共密钥放在机器A(工作机器)上,然后使用ssh-add在那里启用公共密钥。为了使自己的存储库可访问,我还放置了我的个人公钥,并在需要时使用ssh中的Identity功能。如果您只有一个密钥,我不知道您是否需要这么多花哨的东西。 - torek
我明白了。谢谢! - BK736

0
也许您无法访问远程机器时设置的原始密钥。当您ssh进入PC2时,您可以生成另一个密钥并将其注册到您要推送到的服务器上吗?
您需要在ssh会话中运行ssh-keygen,然后将该密钥添加到git服务器上的帐户中。

谢谢您的回复!您能给我更详细的指导来完成您建议的操作吗?我对ssh密钥不是很熟悉... - BK736
你第一次是如何生成并注册密钥的? - Charlie Elverson
我记不得我遵循的确切指示,但我相信它沿着 https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/connecting-to-github-with-ssh 建议的路线。 - BK736
如果您正在使用Github,那么您发送的链接可能已经包含了添加新密钥到您的账户所需的所有细节。生成密钥很容易,只需运行ssh-keygen命令即可。 - Charlie Elverson
谢谢!我会发布对我有用的详细说明。 - BK736
正如我上面提到的,这个问题在新的ssh会话中仍然存在。你有什么想法来解决这个问题吗? - BK736

0

编辑:下面的解决方案仅在我执行它的ssh会话中有效。在新的ssh会话中,我仍然遇到了同样的问题。我将保留这篇帖子,以便其他人可以参与并找出如何永久解决这个问题。

这是一个对我有效的解决方案。

cd ~/.ssh

ssh-keygen

对于提示“输入要保存密钥的文件”,您可以按回车键选择默认文件名,或输入您想要的文件名。然后按照提示输入密码短语。
cat [name of the key, such as "id_rsa"].pub

复制cat命令的输出,包括“ssh-rsa”但不包括末尾的计算机名称。这是您的ssh密钥。

然后,前往https://github.com/settings/ssh并在那里添加已复制的密钥。

最后,

eval "$(ssh-agent -s)"

ssh-add ~/.ssh/[name of the key without .pub, such as "id_rsa"]

你可以通过验证来确认你是否正确地完成了这项任务

ssh -T git@github.com

你应该会收到类似的消息

Hi [your GitHub username]! You've successfully authenticated, but GitHub does not provide shell access.

现在,你可以进行 git push 操作了!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接