The Missing Semester - 第五讲 学习笔记(二)

发布时间 2023-09-10 17:58:52作者: GrapefruitCat

第五讲(二) SSH入门

介绍完命令行环境后,这半节主要介绍的是ssh的有关入门知识。SSH是Secure Shell的简称。

课程视频地址:https://www.bilibili.com/video/BV1x7411H7wa

课程讲义地址:https://missing-semester-cn.github.io/2020/command-line/

本机学习使用平台:wsl1 && ubuntu20.04 + Windows10

通过如下命令,您可以使用 ssh 连接到其他服务器:

ssh [user name]@[target ip]

但是...目标服务器需要先安装ssh-server,才能进行正常的ssh交流!

配置虚拟机SSH

要想把一台机器作为服务器使用,就需要配置好相应的服务环境:

  1. 安装openssh-client:sudo apt-get install openssh-client
  2. 安装openssh-serversudo apt-get install openssh-server
  3. 启动前可能需要对ssh服务器配置进行修改,例如修改端口,在下文有单独章节说明;
  4. 启动ssh-server:sudo /etc/init.d/ssh restart (利用file命令可以看到这是一个一百多行的shell脚本文件,用来启动ssh服务)
  5. 连接后,在主机使用netstat 确认ssh-server工作正常。(看到ssh连接的相应条目表示工作正常)

要注意的是,在客户端第一次对服务器进行连接时,会弹出一个提示,让你验证服务器的ssh密钥指纹(当前为ECDSA密钥):

如图,该提示意味着主机首次连接到这台服务器,SSH无法确认服务器的身份。该指纹使用的是SHA256加密格式,如果服务器是你自己的机器,可以使用ssh-keygen工具查看该机器的指纹,指纹存放在/etc/ssh/ssh_host_ecdsa_key.pub,即验证的是服务器的ECDSA公钥指纹。命令如下:

ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key.pub

其中对命令参数的介绍如下:

    -f [filename]
             Specifies the filename of the key file.

	-l       Show fingerprint of specified public key file.  For RSA and
             DSA keys ssh-keygen tries to find the matching public key
             file and prints its fingerprint.  If combined with -v, a
             visual ASCII art representation of the key is supplied with
             the fingerprint.          

输入命令查看得到服务器指纹,对比发现确实是一模一样的,放心连接啦!选择yes后,这将把服务器的密钥添加到你的本地SSH密钥列表中,并在以后的连接中自动验证。(加入-v参数可以看到赛博指纹)

输入正确的密码进行连接:

连接成功后主机的情况,已经可以操作服务器了:

服务器端使用netstat检验连接是否正常,最后可以使用ctrl-D断开ssh连接。

修改服务器配置文件

如果要在服务器上开启SSH服务,我们应该修改sshd_config配置文件,而不是ssh_config配置文件。

  • ssh_config文件是SSH客户端的配置文件,用于配置SSH客户端的行为和选项。
  • sshd_config文件是SSH服务器的配置文件,用于配置SSH服务器的行为和选项。

但修改前要注意的是,里面已有的注释已经是默认选项,即你取不取消注释都没问题。

The commented out options in the sshd_config are the defaults.

大多数Linux发行版中,sshd_config文件位于/etc/ssh/目录下。对于Windows上的OpenSSH,sshd_config文件多数位于C:\ProgramData\ssh\目录下;可以在sshd_config这里配置免密认证、修改 ssh 端口、开启 X11 转发等等,也可以为每个用户单独指定配置。

对于Linux系统,可以使用以下命令重新加载SSH服务器配置:

  • sudo systemctl reload ssh(对于systemd系统)
  • sudo service ssh reload(对于非systemd系统)

基于密钥的认证机制

基于密钥的验证机制使用了密码学中的公钥,我们只需要向服务器证明客户端持有对应的私钥,而不需要公开其私钥。这样您就可以避免每次登录都输入密码的麻烦了。不过,私钥(通常是 ~/.ssh/id_rsa 或者 ~/.ssh/id_ed25519) 等效于您的密码,所以一定要好好保存它。

SSH采用的公私钥体系,简而言之,就是客户端给服务器提供公钥,服务器利用公钥认证登录的客户端(即证明客户端确实拥有登录私钥)。(此处有对ssh公私钥体系的知识讲解)

密钥有很多种,如ed25519 、EDCSA、RSA等等,这篇博客讲解了它们之间的区别,当前最好的还是ed25519

生成密钥的工具为ssh-keygen,工具文档在此

生成命令示例:

ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519

-o:指出私钥文件格式为 OpenSSH, OpenSSH 6.5版本引入。

-a:加密轮数。越高越难以BF破解。

-t:加密类型。 ed25519为其中一种加密方式,默认RSA加密。简略介绍见此

-f:输出文件名称。

生成的密钥会问你是否需要设置一个passphrase,这是为了防止别人能够轻易获取你的私钥。但是,这也会在你想要利用公钥认证登录时,需要再次输入保存该密钥的passphrase。那这还算什么免密登录呢???幸好,我们有ssh-agent帮我们解决这个麻烦,在这里先不提,先把流程走一遍先。

生成ed25519密钥后,可以利用管道输入ssh服务器配置文件authorized_keysauthorized_keys文件的格式在sshd手册中有介绍):

# windows作主机也可以进行这样的管道操作
cat .ssh/id_ed25519.pub | ssh user_name@remote 'cat  >>  ~/.ssh/authorized_keys'

或者可以用tee工具完成这个工作:

cat .ssh/id_ed25519.pub | ssh user_name@remote tee  ~/.ssh/authorized_keys

如果支持 ssh-copy-id 的话,可以使用下面这种更简单的解决方案:

ssh-copy-id -i .ssh/id_ed25519.pub user_name@remote

在这里比较推荐使用ssh-copy-id。因为单纯的管道操作不会给你验证你的输入文件到底是公钥还是私钥,而ssh-copy-id则给了这个防御性措施。(如果你错误地使用了私钥文件,还可能会暴露)

Windows没有ssh-copy-id,则可以编写相应功能的脚本来完成这一工作:

function ssh-copy-id([string]$userAtMachine, $args){   
    $publicKey = "$ENV:USERPROFILE" + "/.ssh/id_rsa.pub"
    if (!(Test-Path "$publicKey")){
        Write-Error "ERROR: failed to open ID file '$publicKey': No such file"            
    }
    else {
        & cat "$publicKey" | ssh $args $userAtMachine "umask 077; test -d .ssh || mkdir .ssh ; cat >> .ssh/authorized_keys || exit 1"      
    }
}

配置好客户端公钥后,即可进行“免密”的公钥认证登录,无需再次输入登录用户的密码(但仍需要passphrase)。

但是要注意的是,服务器端必须开启公钥认证!在/etc/ssh/sshd_config文件中进行修改,并且记得重启服务器端的sshd。

# 是否允许公钥身份验证,默认为yes
PubkeyAuthentication yes
# 认证公钥文件,默认情况就是我们之前所做的那样
AuthorizedKeysFile     .ssh/authorized_keys .ssh/authorized_keys2
#是否允许密码身份验证,默认为yes,当我们使用密钥可以成功登陆后,可以改为no
PasswordAuthentication no

而且要注意的是,公钥登录似乎对密钥文件权限要求特别严格..最好把服务器端的.ssh目录设为700,authorized_keys文件设为644。对于客户端:私钥必须为600权限或者更严格权限 (400), 一旦其他用户可读, 私钥就不起作用 (如640), 表现为系统认为不存在私钥; 对于服务器端:要求必须公钥其他用户不可写, 一旦其他用户可写 (如660), 就无法用key登录, 表现为:Permission denied (publickey).

参考的博文这样写:

  • ~/.ssh/authorized_keys must have 644 permission
  • Private key file i.e. ~/.ssh/id_rsa should be 600, the name of the key may be different as per user environment
  • Public key file i.e. ~/.ssh/id_rsa.pub should be 644, the name of the key may be different as per user environment
  • The ~/.ssh directory must not be world readable/writable so you can keep it with 700 permission

注意:如果你使用的是RSA加密的密钥,并且发现sshd_config文件没有RSAAuthentication这个配置选项,这是一个很有趣的坑,可能会让你无法进行密钥登录..详细原因见此博文。(所以还是用回其它加密方式的密钥吧)

如果你发现公钥认证登录失败,可以在登录时尝试利用登录参数-v打印debug信息。注意,v的个数上限为3个。

或者利用sshd的debug模式:先停掉ssh服务,然后在确保拥有755权限的/run/sshd文件夹的情况下,使用命令:sudo /usr/sbin/sshd -d开启debug模式的ssh服务器端。

我自己使用sshd启动debug模式,在公钥验证时出现过"postponed publickey" 的log,这里有一篇博文讲了这方面debug的方法:SSH fails with postponed publickey error,非常非常棒的博文!

关于添加公钥后,登录仍需密码?有关这类问题社区有提问:SSH Still Asks for Password with Public Key Setup——对这个问题做了一些以前出现过的问题的归纳。

我有一段时间一直搞不定windows10免密登录wsl1的ubuntu20.04,连wsl自己免密登录localhost也不行,一直是" Permission denied (publickey)"。但是在virtualbox上的Ubuntu18.04就能在ssh-copy-id后直接进行公钥登录……使用ssh-add也添加不上私钥认证,也找不出原因。

最后温度上来了把自己.ssh目录清空,将自己旧的ed25519密钥对删掉生成一个新的,然后从头来一遍,wsl登录localhost却可以了……而且win到wsl不行的原因更离谱。。竟然是采用管道输入的公钥在authorized_keys的格式不对(用cat命令输出,或者用vim打开都可以看到),前面多了一个/feff/符,可能是编码格式的问题。(Linux下文件开头的feff的问题

类似wsl的ssh登录失败问题:Passwordless SSH login into WSL2 failing

关于ssh密钥格式不对可能产生的问题:SSH Key Format Issues and Fixing the “invalid format” Error

ssh-agent和ssh-add

您可以为密钥设置密码(passphrase),防止有人持有您的私钥并使用它访问您的服务器。

“您可以使用 ssh-agentgpg-agent ,这样就不需要每次都输入该passphrase了。”

如教案中所言,ssh-agent存在的意义就是帮助你解决passphrase验证的过程,这需要ssh-add的帮助。

ssh-agent 是一个密钥管理器,用来管理一个多个密钥,并为其他需要使用 ssh 密钥的程序提供代理(即帮忙验证passphrase,不用每次都要自己输入)。在使用ssh-add前,先要保证ssh-agent正在运行。

利用echo $SSH_AGENT_PID来检查是否有ssh-agent进程在运行。如果没有,则需要启动。

不同操作系统中的ssh-agent的启动方式是不同的。在 Linux中: ssh-agentX会话 或 **登录会话 **之初就已经启动;在Windows中: 计算机管理 -> 服务 -> OpenSSH Authentication Agent 设置为自动启动。

也可以手动启动ssh-agent

ssh-agent $SHELL

另一种命令:eval shell-agent , 在windows中为 eval $(ssh-agent) : 它并不会启动一个子shell,而是直接启动一个 ssh-agent 进程;此时当我们退出当前 bash 后,ssh-agent 进程并不会自动关闭。我们可以在当前bash退出之前,使用 ssh-agent -k ,或者在当前 bash 退出之后,使用 kill 命令,关闭对应的 ssh-agent 进程。

如果在Windows中手动启动失败,遇到error:1058问题,这里有一个关于windows10 ssh-agent启动服务失败的问题:Starting ssh-agent on Windows 10 fails: "unable to start ssh-agent service, error :1058"

ssh-agent启动成功后,就可以开始着手passphrase的代理验证添加了:

在默认情况下,ssh-agent使用的是用户目录下.ssh目录里面保存的密钥。ssh-add在缺省情况下,会将~/.ssh/id_rsa, .ssh/id_dsa, ~/.ssh/id_ecdsa, ~/.ssh/id_ed25519, 和 ~/.ssh/identity都添加到ssh-agent里面。通过ssh-add添加私钥认证后,ssh-add -l可以看到当前ssh-agent可以访问的私钥列表。

ssh-add -l显示"The agent has no identities."的情况:ssh-add command does not add my identity to ssh-agent——大概率也是密钥文件权限问题导致的。

这一节参考博文:ssh agent详解

通过 SSH 复制文件

使用 ssh 复制文件,即在主机与远程机之间传输文件有很多方法:

  • ssh+tee, 最简单的方法是执行 ssh 命令,然后通过这样的方法利用标准输入实现 cat localfile | ssh remote_server tee serverfile。回忆一下,tee 命令会将标准输出写入到一个文件;
  • scp :当需要拷贝大量的文件或目录时,使用scp 命令则更加方便,因为它可以方便的遍历相关路径。语法如下:scp path/to/local_file remote_host:path/to/remote_file
  • rsyncscp 进行了改进,它可以检测本地和远端的文件以防止重复拷贝。它还可以提供一些诸如符号连接、权限管理等精心打磨的功能。甚至还可以基于 --partial标记实现断点续传(课上展示了-avP参数)。rsync 的语法和scp类似。

ssh的dotfile——config文件

我们每次进行ssh登录,都需要输入一长串的string……太麻烦了!

教案里面给出了一个很简单粗暴的方法:为它们创建一个alias

alias my_server="ssh -i ~/.id_ed25519 --port 2222 -L 9999:localhost:8888 user_name@remote_server"

不过,更好的方法是使用dotfile。我们可以使用config文件,它的默认位置为~/.ssh/config,和.vimrc.bashrc一样,它是属于ssh的dotfile

# 这样登录只需要输入 ssh vm 就可以了
Host vm
    User foobar
    HostName 172.16.174.141
    Port 2222
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 9999 localhost:8888

# 在配置文件中也可以使用通配符
Host *.mit.edu
    User foobaz

这么做的好处是,使用 ~/.ssh/config 文件来创建别名,类似 scprsyncmosh的这些命令都可以读取这个配置并将设置转换为对应的命令行选项。

教案的tips:

注意,~/.ssh/config 文件也可以被当作配置文件,而且一般情况下也是可以被导入其他配置文件的。不过,如果您将其公开到互联网上,那么其他人都将会看到您的服务器地址、用户名、开放端口等等。这些信息可能会帮助到那些企图攻击您系统的黑客,所以请务必三思。

端口转发

该节为教案内容,课程中并没有提及。

很多情况下我们都会遇到软件需要监听特定设备的端口。如果是在您的本机,可以使用 localhost:PORT127.0.0.1:PORT。但是如果需要监听远程服务器的端口该如何操作呢?这种情况下远端的端口并不会直接通过网络暴露给您。

此时就需要进行 端口转发。端口转发有两种,一种是本地端口转发和远程端口转发(参见下图,该图片引用自这篇StackOverflow 文章)中的图片。

本地端口转发

远程端口转发

常见的情景是使用本地端口转发,即远端设备上的服务监听一个端口,而您希望在本地设备上的一个端口建立连接并转发到远程端口上。例如,我们在远端服务器上运行 Jupyter notebook 并监听 8888 端口。 然后,建立从本地端口 9999 的转发,使用 ssh -L 9999:localhost:8888 user_name@remote_server 。这样只需要访问本地的 localhost:9999 即可。

杂项

连接远程服务器的一个常见痛点是遇到由关机、休眠或网络环境变化导致的掉线。如果连接的延迟很高也很让人讨厌。Mosh(即 mobile shell )对 ssh 进行了改进,它允许连接漫游、间歇连接及智能本地回显。

有时将一个远端文件夹挂载到本地会比较方便, sshfs 可以将远端服务器上的一个文件夹挂载到本地,然后您就可以使用本地的编辑器了。(不过现在sshfs已经是一个orphaned的项目了……)