SSH(Secure Shell 的缩写)是一种网络协议,用于加密两台计算机之间的通信。OpenSSH 是其一个开源实现。
SSH 的软件架构是服务器-客户端模式(Server - Client)。在这个架构中,SSH 软件分成两个部分:向服务器发出请求的部分,称为客户端(client),OpenSSH 的实现为 ssh;接收客户端发出的请求的部分,称为服务器(server),OpenSSH 的实现为 sshd。
OpenSSH 同时提供了辅助工具,比如文件传输相关的 scp 和 sftp,密钥管理相关的 ssh-keygen 等。
基本用法
登陆服务器
服务器上运行有 SSH 服务软件时,客户端可以进行登陆。
1
2
3
4
5
6
7
8
# 登陆 host,之后输入密码(默认采用与当前客户端相同的用户名进行登陆)
$ ssh host
# 指定用户名登陆 host
$ ssh user@host
# 指定用户名登陆 host
$ ssh -l user host
# 指定端口登陆 host
$ ssh -p 22 host
连接过程
首次登陆服务器时,命令行会提示服务器的指纹是陌生的,让用户进行选择是否继续进行连接,用户输入 yes 后服务器指纹会存入 ~/.ssh/known_hosts 文件,之后再连接该服务器就不会再提示。
1
2
3
4
5
6
7
8
9
10
# 第一次连接陌生指纹提示
The authenticity of host 'xxx' can't be established.
ECDSA key fingerprint is SHA256:xxx.
Are you sure you want to continue connecting (yes/no)?
# 命令行查看公钥指纹
$ ssh-keygen -l -f ~/.ssh/xxx.pub
# 查看已保存的指纹
$ cat ~/.ssh/know_hosts
当服务器的密钥因重装或者其他原因更改了密钥时,连接服务器会提示指纹不符,这时需要将本地已存储的指纹,然后再进行连接。
1
2
3
4
5
6
7
8
9
# 指纹不符提示
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
...
# 删除指纹
$ ssh-keygen -R hostname
执行远程命令
SSH 登录成功后,用户就进入了远程主机的命令行环境。如果仅仅想执行一条远程指令也可以将命令直接写在 ssh
命令之后。
1
2
3
4
$ ssh username@hostname command
# 下面是在远程服务器上执行 cat 命令,并在本地主机显示结果
$ ssh foo@server.example.com cat /etc/hosts
采用上边的语法执行命令时,ssh 客户端不会提供互动式的 Shell 环境,而是直接远程命令的执行结果输出在命令行。但是,有些命令需要互动式的 Shell 环境,这时就要使用 -t
参数。
1
2
3
4
5
6
# 报错
$ ssh remote.server.com emacs
emacs: standard input is not a tty
# 不报错
$ ssh -t server.example.com emacs
主机配置
用户的配置文件 ~/.ssh/config
,可以按照不同服务器,列出各自的连接参数,从而不必每一次登录都输入重复的参数。
1
2
3
4
5
6
7
Host *
Port 2222
Host remoteserver
HostName remote.example.com
User neo
Port 2112
上面配置中,Host *
表示对所有主机生效,后面的 Port 2222
表示所有主机的默认连接端口都是 2222,这样就不用在登录时特别指定端口了。这里的缩进并不是必需的,只是为了视觉上,易于识别针对不同主机的设置。
后面的 Host remoteserver
表示,设置只对主机 remoteserver
生效。remoteserver
只是一个别名,具体的主机由 HostName
命令指定,User
和 Port
这两项分别表示用户名和端口。这里的 Port
会覆盖上面 Host *
部分的 Port
设置。
以后,登录 remote.example.com
时,只要执行 ssh remoteserver
命令,就会自动套用 config 文件里面指定的参数。
1
2
3
$ ssh remoteserver
# 等同于
$ ssh -p 2112 neo@remote.example.com
密钥登陆
利用非对称加密,ssh 可以实现方便的密钥登陆,而不用每次登陆都输入用户名和密码。登陆过程分为以下步骤。
- 客户端通过
ssh-keygen
生成自己的公钥和私钥。 - 手动将客户端的公钥放入远程服务器的指定位置。
- 客户端向服务器发起 SSH 登录的请求。
- 服务器收到用户 SSH 登录的请求,发送一些随机数据给用户,要求用户证明自己的身份。
- 客户端收到服务器发来的数据,使用私钥对数据进行签名,然后再发还给服务器。
- 服务器收到客户端发来的加密签名后,使用对应的公钥解密,然后跟原始数据比较。如果一致,就允许用户登录。
生成密钥的过程就是通过 ssh-kengen
工具进行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -t 指定加密算法,-C 添加额外的备注,以便区分多个密钥
$ ssh-keygen -t rsa -C comments
# 执行命令后,会要求指定密钥文件名和密码,没有特殊要求可以保持默认
Enter file in which to save the key (/home/username/.ssh/id_dsa): press ENTER
Enter passphrase (empty for no passphrase): ********
Enter same passphrase again: ********
Your identification has been saved in /home/username/.ssh/id_dsa.
Your public key has been saved in /home/username/.ssh/id_dsa.pub.
The key fingerprint is:
...
# 最后可以通过 cat 查看公钥
# 如果选择rsa算法,生成的密钥文件默认就会是~/.ssh/id_rsa(私钥)和~/.ssh/id_rsa.pub(公钥)。
上传公钥可以手动上传,也可以通过 ssh-copy-id
工具实现上传。
OpenSSH 规定,用户公钥保存在服务器的 ~/.ssh/authorized_keys
文件。你要以哪个用户的身份登录到服务器,密钥就必须保存在该用户主目录的 ~/.ssh/authorized_keys
文件。只要把公钥添加到这个文件之中,就相当于公钥上传到服务器了。每个公钥占据一行。如果该文件不存在,可以手动创建。
用户可以手动编辑该文件,把公钥粘贴进去,也可以在本机计算机上,执行下面的命令。
1
$ cat ~/.ssh/id_rsa.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
通过 ssh-copy-id
工具,用户在本地计算机执行下面的命令,也可以把本地的公钥拷贝到服务器。
1
$ ssh-copy-id -i key_file user@host
端口转发(SSH 隧道)
SSH 除了登录服务器,还有一大用途,就是作为加密通信的中介,充当两台服务器之间的通信加密跳板,使得原本不加密的通信变成加密通信。这个功能称为端口转发(port forwarding),又称 SSH 隧道(tunnel)。
端口转发有三种使用方法:动态转发(-D),本地转发(-L),远程转发(-R)。
动态转发
动态转发指的是,本机与 SSH 服务器之间创建了一个加密连接,然后本机内部针对某个端口的通信,都通过这个加密连接转发。它的一个使用场景就是,访问所有外部网站,都通过 SSH 转发,譬如科学上网。
1
2
3
4
5
# -N 表示只进行转发
$ ssh -D local-port tunnel-host -N
# 例如全部都通过本地端口 2121
$ ssh -D 2121 tunnel-host -N
动态转发采用了 SOCKS5 协议。访问外部网站时,需要把 HTTP 请求转成 SOCKS5 协议,才能把本地端口的请求转发出去。
下面是 SSH 隧道建立后的一个使用实例,或者也可以利用浏览器配置代理。
1
$ curl -x socks5://localhost:2121 http://www.example.com
本地转发
本地转发(local forwarding)指的是,SSH 服务器作为中介的跳板机,建立本地计算机与特定目标网站之间的加密连接。本地转发是在本地计算机的 SSH 客户端建立的转发规则。
它会指定一个本地端口(local-port),所有发向那个端口的请求,都会转发到 SSH 跳板机(tunnel-host),然后 SSH 跳板机作为中介,将收到的请求发到目标服务器(target-host)的目标端口(target-port)。
1
2
3
4
$ ssh -L local-port:target-host:target-port tunnel-host
# 访问本地 2121 端口,即可实现通过 tunnel-host 访问 example 的 80 端口
$ ssh -L 2121:www.example.com:80 tunnel-host -N
远程转发
远程端口指的是在远程 SSH 服务器建立的转发规则。
这种场景比较特殊,主要针对内网的情况。本地计算机在外网,SSH 跳板机和目标服务器都在内网,而且本地计算机无法访问内网之中的 SSH 跳板机,但是 SSH 跳板机可以访问本机计算机。
由于本机无法访问内网 SSH 跳板机,就无法从外网发起 SSH 隧道,建立端口转发。必须反过来,从 SSH 跳板机发起隧道,建立端口转发,这时就形成了远程端口转发。
1
2
3
4
5
6
7
8
# 注意此命令不在本地机器执行,而是在跳板机执行,本地机器对跳板机来说是远程主机
# 另外,本地机器需要安装 ssh 服务器,否则跳板机无法连接本地机器
$ ssh -R local-port:target-host:target-port -N local
# 跳板机绑定本地机器 2121 端口,实现对 example 的 80 端口访问
$ ssh -R 2121:www.example.com:80 local -N
# 从本机访问目标服务器
$ curl http://localhost:2121
文件传输
scp
、sftp
、rsync
可以用来在本地主机和远程服务器之间传输文件。
scp
用来在两台主机之间加密传送文件(即复制文件),用于以下三种操作。
- 本地复制到远程。
- 远程复制到本地。
- 两个远程系统之间的复制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 本地 -> 远程
$ scp SourceFile user@host:directory/TargetFile
# 示例
$ scp file.txt remote_username@10.10.0.2:/remote/directory
# 远程 -> 本地
$ scp user@host:directory/SourceFile TargetFile
# 示例
$ scp remote_username@10.10.0.2:/remote/file.txt /local/directory
# 远程 -> 远程
$ scp user@host1:directory/SourceFile user@host2:directory/SourceFile
# 示例
$ scp user1@host1.com:/files/file.txt user2@host2.com:/files
# 参数
-l 限制带宽(Kb/s)
-p 保留原始文件信息(修改、访问时间,文件状态)
-r 递归复制目录
-v 详细输出
sftp
相当于将 FTP 放入了 SSH。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 连接远程主机
$ sftp username@hostname
# 连接后,命令与 ftp 用法一致
ls [directory]:列出一个远程目录的内容。如果没有指定目标目录,则默认列出当前目录。
cd directory:从当前目录改到指定目录。
mkdir directory:创建一个远程目录。
rmdir path:删除一个远程目录。
put localfile [remotefile]:本地文件传输到远程主机。
get remotefile [localfile]:远程文件传输到本地。
help:显示帮助信息。
bye:退出 sftp。
quit:退出 sftp。
exit:退出 sftp。
rsync
rsync 不是 SSH 工具集的一部分,但也涉及到远程操作,可以用来同步文件,在本地计算机与远程计算机之间,或者两个本地目录之间同步文件。配合上 crontab 就可以定时同步。
1
2
3
4
5
6
7
8
9
10
11
$ rsync -av source destination
$ rsync -av source/ username@remote_host:destination
$ rsync -av username@remote_host:source/ destination
# 参数
-r 递归目录
-a 等价于 -r + 同步文件元信息,更为常用
-n dry-run,干跑看效果
-v 详细输出
--delete rsync 只确保源目录的所有内容(明确排除的文件除外)都复制到目标目录。它不会使两个目录保持相同,并且不会删除文件。如果要使得目标目录成为源目录的镜像副本,则必须使用--delete参数,这将删除只存在于目标目录、不存在于源目录的文件。
--exclude 排除文件