ARTICLE

实验室服务器 SSH 入门:从 Remote SSH 到 GitHub SSH Key

刚进实验室时,老师把服务器的用户名、IP 和初始密码发给了我,让我之后在服务器上跑代码,顺便建议我配一下 SSH Key。

登录服务器这一步倒没有卡很久。b站上搜索“VS Code 连接远程服务器”,基本上都推荐 Remote - SSH 插件。安装插件,在 VS Code 中按 Ctrl+Shift+P,选择:

Remote-SSH: Add New SSH Host

然后输入:

ssh 用户名@服务器IP

例如:

ssh yuhan@203.0.113.10

第一次连接会询问是否信任服务器,并让我输入老师给的密码。输入完成后,VS Code 会自动在服务器上安装 VS Code Server,然后就可以像打开本地文件夹一样打开服务器目录。

所以真正让我开始研究 SSH Key 的原因,并不是“不知道怎么登录服务器”,而是每次重新连接都要输入一遍密码。老师说配好 Key 以后会方便很多,我才开始看它到底是什么。

SSH 连接先在服务器门口用密码或 Key 验证身份,成功后命令与终端输出才在加密隧道中双向传输
一次 SSH 连接包含两件事:ssh key 负责在会话开始时证明你是谁,加密通道负责保护之后传输的命令、文件和输出。

notes:文中的 IP、用户名和仓库地址都是示例,需要替换成自己的真实信息。

先把服务器登录改成 SSH Key

我一开始把 SSH Key 理解成“另一种登录密码”。实际使用时,它是一对文件:一个私钥和一个公钥。

在 Windows PowerShell 中可以这样生成一对专门用于实验室服务器的密钥:

ssh-keygen -t ed25519 -C "lab-server" -f "$env:USERPROFILE\.ssh\id_ed25519_lab"

生成后,Windows 用户目录下的 .ssh 文件夹里会出现:

id_ed25519_lab       # 私钥
id_ed25519_lab.pub   # 公钥

这里最需要注意的是:没有 .pub 后缀的是私钥,请妥善保存;有 .pub 后缀的是公钥,后续需要传到服务器上。

私钥在本地对当前 SSH 会话证明签名,服务器用 authorized_keys 中的公钥验签;网络上传输签名而不是私钥
私钥签名留在发起连接的一端;公钥验证交给负责验身份的服务器。

把公钥传到服务器

如果使用 Linux、macOS、WSL,或者环境里有 ssh-copy-id,上传公钥只需要一条命令:

ssh-copy-id -i ~/.ssh/id_ed25519_lab.pub yuhan@203.0.113.10

它会要求输入最后一次服务器密码,然后把公钥追加到服务器的:

~/.ssh/authorized_keys

Windows PowerShell 默认不一定有 ssh-copy-id。不想额外安装时,可以用下面这条等价命令:

Get-Content "$env:USERPROFILE\.ssh\id_ed25519_lab.pub" |
  ssh yuhan@203.0.113.10 "umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys"

GitHub SSH key

服务器能登录之后,我做的下一件事就是拉项目代码。和大多数刚开始使用 GitHub 的人一样,我直接复制仓库页面上的 HTTPS 地址:

git clone https://github.com/OWNER/REPO.git

如果是公开仓库,clonepull 通常不需要任何 GitHub SSH Key,所以这一步也没有让我意识到还有另一套认证。

真正麻烦的是后来需要把自己的修改推送到 GitHub。

git add 修改的文件
git commit -m "更新代码"
git push

每次 push 都要处理 GitHub 认证。终端看起来像是在要求用户名和密码,但 GitHub 的 Git 操作已经不再接受账号密码:走 HTTPS 时,需要使用 Personal Access Token,或者让 Git Credential Manager 代为保存凭据。

Token 很长,也不适合每次手动复制。我这才想起 GitHub 设置里还有一个 SSH and GPG keys,又联想到仓库页面的 Code 按钮下面一直有两个 clone 选项:HTTPS 和 SSH。以前我把它们理解成两种“复制下载地址的格式”,直到这时才发现,它们其实代表连接同一个仓库的两条路线。

  • HTTPS 地址形如 https://github.com/OWNER/REPO.git。Git 通过 HTTPS 传输数据,需要认证时使用 Token 或 Credential Manager 中保存的凭据。
  • SSH 地址形如 git@github.com:OWNER/REPO.git。Git 通过 SSH 传输数据,GitHub 使用账号中保存的公钥验证本机私钥的签名。

两种方式访问的是同一个仓库,提交记录、分支和文件没有任何区别;不同的是连接协议以及用什么证明身份。公开仓库允许匿名读取,所以用 HTTPS clone 时往往感觉不到认证。到了 push 需要写权限,这个区别才真正显现出来。

还有一个容易忽略的细节:git clone 不只是下载代码,它还会把当时使用的地址保存为本地仓库的 origin。以后执行 pullpush,Git 都会继续沿着这个地址所指定的路线连接 GitHub。配置了 SSH Key 并不会自动把已有的 HTTPS origin 改成 SSH。

本地 Git 仓库可以通过 HTTPS 或 SSH 连接同一个 GitHub 仓库;clone 时选择的地址会保存为 origin,之后 pull 和 push 继续使用对应的认证方式
HTTPS 和 SSH 指向同一个 GitHub 仓库,区别在连接协议和认证凭据。clone 时选择的 URL 会保存为 origin,之后的 pull 与 push 也会继续走这条路线。

服务器登录 Key 和 GitHub Key 是什么关系

从原理上说,它们都是 SSH 公钥认证,没有本质不同。区别只在于“这把公钥交给了谁”。

前面登录实验室服务器时:

我的电脑 ──SSH──> 实验室服务器

私钥留在我的电脑上,公钥放在实验室服务器的 authorized_keys 中。

现在从自己的电脑向 GitHub 推送时:

我的电脑 ──SSH──> GitHub

私钥仍然留在电脑上,但公钥需要添加到 GitHub 账号。

技术上可以把同一把公钥同时放到服务器和 GitHub,但我后来还是为 GitHub 单独生成了一把,文件名也写清楚用途。这样以后电脑丢失、密钥泄露或需要撤销权限时,不会牵连其他连接。

在 Windows 上配置 GitHub SSH Key

生成 GitHub 专用密钥:

ssh-keygen -t ed25519 -C "你的GitHub邮箱" -f "$env:USERPROFILE\.ssh\id_ed25519_github"

把公钥复制到剪贴板:

Get-Content "$env:USERPROFILE\.ssh\id_ed25519_github.pub" | Set-Clipboard

然后进入 GitHub:

头像 → Settings → SSH and GPG keys → New SSH key

给它起一个能认出设备的名字,例如 Yuhan Windows Laptop,选择 Authentication Key,粘贴公钥并保存。

因为我的 GitHub 私钥也不是默认文件名,所以在 Windows 的 ~/.ssh/config 中继续加入:

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_github
  IdentitiesOnly yes

然后测试:

ssh -T git@github.com

如果认证成功,GitHub 会显示类似“successfully authenticated”的提示,同时说明它不提供 shell access。这是正常的,因为这里只是用 SSH 完成 Git 身份认证,并不是要进入 GitHub 的服务器终端。

配完 Key,为什么 push 还是要求认证

这是我实际卡住的一步。

我已经把 SSH Key 添加到 GitHub,测试也成功了,但回到原来的项目执行 git push,它仍然走 HTTPS 认证。

原因是添加 SSH Key 不会自动修改已有仓库的远程地址。可以先检查:

git remote -v

如果看到的是:

https://github.com/OWNER/REPO.git

就需要改为 SSH 地址:

git remote set-url origin git@github.com:OWNER/REPO.git

再次执行 git remote -v,确认变成:

git@github.com:OWNER/REPO.git

这时 push 才会真正使用刚配置的 GitHub SSH Key。

回头看,这个问题其实很简单,但第一次配置时很容易把“GitHub 已经认识我的公钥”和“这个仓库正在使用 SSH 地址”当成一回事。

服务器同步配置Github SSH key

先通过 Remote SSH 登录实验室服务器,然后在服务器终端生成一把新的密钥:

ssh-keygen -t ed25519 -C "lab-server-github" -f ~/.ssh/id_ed25519_github

查看服务器生成的公钥:

cat ~/.ssh/id_ed25519_github.pub

再把这行公钥添加到 GitHub。

最简单的方式是添加到个人账号的 SSH and GPG keys。如果服务器只需要访问某一个仓库,也可以在仓库的 Settings → Deploy keys 中添加,这样权限范围会更小。一般只需要拉代码时保留只读权限即可。

服务器的 ~/.ssh/config 可以写成:

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_github
  IdentitiesOnly yes

测试:

ssh -T git@github.com

之后再使用 SSH 地址:

git clone git@github.com:OWNER/REPO.git

总结

逻辑一直是同一套:

谁发起 SSH 连接,私钥就留在谁那里;
谁负责验证身份,公钥就交给谁。

对应到这次使用服务器的过程就是:

操作 私钥在哪里 公钥交给谁
Windows 登录实验室服务器 Windows 实验室服务器
Windows 向 GitHub push Windows GitHub 账号
实验室服务器访问 GitHub 实验室服务器 GitHub 账号或目标仓库
电脑用私钥 A 登录实验室服务器、用私钥 B 访问 GitHub;服务器如需访问 GitHub 则使用自己的私钥 C,三个目标分别保存对应公钥
谁发起连接,谁保存私钥;谁验证身份,谁保存对应公钥。三条箭头就是三套可独立撤销的身份。

参考资料