---
title: SSH 知识整理
date: 2024-02-14
modified: 2024-02-14
url: https://yuuu.org/post/ssh-basics
categories: ["学习"]
tags: ["学习"]
description: "这两天都在玩 linux，谈谈最近学习的 ssh。"
---
这并不是一篇指导性的文章，而是一篇初学者的笔记，可能会随时改动，其中也可能遍布错误。此外，我感觉这更像是一篇冗长啰嗦的唠叨。😪

以下代码都是在 Debian 系统上进行的操作。

## 基础

一般情况下 linux 默认安装了 ssh。linux 一般使用 ssh 连接。
没有安装的话可以按照以下命令安装

```bash
#安装
sudo apt-get install openssh-server  # 对于Debian/Ubuntu
sudo yum install openssh-server      # 对于RHEL/CentOS
#启动
sudo service ssh start    # 对于系统V（System V）初始化系统
sudo systemctl start ssh  # 对于使用systemd的系统
```

在`/etc/ssh/sshd_config`可以调整 ssh 配置，调整前先备份。

```bash
cp /etc/ssh/sshd_config /etc/ssh/backup.sshd_config # 备份好习惯
sudo vim /etc/ssh/sshd_config # 使用vim
sudo nano /etc/ssh/sshd_config # 使用nano
sshd -t #用于测试ssh服务器配置文件是否正确
sudo service ssh restart # 修改完之后重启
```

ssh 最常见的用途之一是远程登录到远程服务器或计算机。

```bash
ssh username@remote_host # 替换为你的username和remote_host
```

第一次连接服务器大概会出现以下情况。

```text
The authenticity of host '1.1.1.1 (1.1.1.1)' can't be established.
ED25519 key fingerprint is SHA256:rqEuH0ymaN0LbB1vPKzRpfgew+5hBusv1/ReDGJ6bDs.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?
# 翻译
无法确定主机 "1.1.1.1 (1.1.1.1) "的真实性。
ED25519 密钥指纹为 SHA256:rqEuH0ymaN0LbB1vPKzRpfgew+5hBusv1/ReDGJ6bDs。
此密钥没有其他名称
您确定要继续连接（是/否/[指纹]）？
```

这个消息通常是在你首次通过 ssh 连接到一个远程服务器时出现的。它是 ssh 的一种安全机制，目的是确保你连接的服务器确实是你想要连接的服务器，而不是中间人攻击（Man-in-the-Middle Attack）的一部分。

一般情况下只需按下"Enter"即可。
接下来就是输入密码，linux 默认输入密码的时候不会有任何提示或者显示。输入完密码再次回车即可登入服务器。  
以后将不会再次提醒无法验证该服务器的真实性，因为 ssh 客户端将保存该主机的公钥，并在将来的连接中使用它来验证主机的真实性。  
不过，当服务器修改了公钥或者对系统进行了重置，那么 ssh 客户端会在下一次连接中警告称：

```text
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256: [新的密钥指纹]
Please contact your system administrator.
Add correct host key in /path/to/your/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /path/to/your/.ssh/known_hosts:1
remove with:
ssh-keygen -f "/path/to/your/.ssh/known_hosts" -R [服务器 IP 地址]
ECDSA host key for [服务器 IP 地址] has changed and you have requested strict checking.
Host key verification failed.
# 翻译
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

@ 警告：远程主机标识已更改！ @

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
有可能有人在做坏事！
可能有人正在窃听你（中间人攻击）！
也有可能是主机密钥刚刚被更改。
远程主机发送的 ECDSA 密钥的指纹是
SHA256: [新的密钥指纹]
请联系系统管理员。
在 /path/to/your/.ssh/known_hosts 中添加正确的主机密钥，以消除此信息。
在 /path/to/your/.ssh/known_hosts:1 中的违规 ECDSA 密钥
删除：
ssh-keygen -f "/path/to/your/.ssh/known_hosts" -R [服务器 IP 地址]
[服务器 IP 地址] 的 ECDSA 主机密钥已更改，您要求进行严格检查。
主机密钥验证失败。
```

## ssh 无密码登录

我们会发现，每一次连接的时候，服务器都要求我们输入密码。  
如果我们每一次都使用诸如`ssh username@remote_host`+`password`的方式，可能会感到十分的厌烦，这时候我们就可以使用 ssh 密钥对来连接到远程服务器。如果私钥没有设置密码，就不需要输入密码。从而达到无密码登录。  
`ssh-keygen`可以生成密钥对，接下来连续三次回车即可生成密钥对。

```txt
ssh-keygen -t rsa -b 2048
# 生成一个 2048 位的 RSA 密钥对，可以选择更长的
❯ ssh-keygen -t rsa -b 2048
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/admin/.ssh/id_rsa):# 存储位置（回车）
Enter passphrase (empty for no passphrase): # 可以选择输入密码（回车）
Enter same passphrase again: # 再次输入密码（回车）
Your identification has been saved in /c/Users/admin/.ssh/id_rsa
Your public key has been saved in /c/Users/admin/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:a9G5vG/6Qz6Aptj/cFQsPZtt+DjiAMIBVI9fW/OIENM admin@PC
The key's randomart image is:
+---[RSA 2048]----+
| .o.. o.         |
|   . o oE  o     |
|    o o . + =    |
|   . o o = B *   |
|    o o S.= = o  |
|     . .o=...+   |
|     o o= =+o .  |
|    . o. = o=.   |
|       ...==oo   |
+----[SHA256]-----+
```

此时我们就生成好了一个 2048 位的 RSA 密钥对，此时它存储在`c/Users/admin/.ssh/id_rsa`这个位置，可以从自己的终端看到文件所在位置。
接下来我们可以使用`ssh-copy-id`将它复制到远程服务器上。

```bash
ssh-copy-id username@remote-server
```

执行这个命令后，系统会要求您输入远程服务器的密码，然后会将您本地计算机上的 `~/.ssh/id_rsa.pub`（或其他公钥文件）的内容追加到远程服务器上的 `~/.ssh/authorized_keys` 文件中。此时，就完成了公钥的复制。  
此时就已经完成了无密码登录的步骤，以后使用 ssh 连接都只需要输入以下代码即可

```bash
ssh username@remote-server
```

如果不想用 ssh-copy-id，可以使用`scp`指令，将公钥发送到服务器。

```bash
scp /Users/admin/.ssh/id_rsa.pub username@remote_host:~/.ssh
```

多讲'亿'句。其中，`scp`（Secure Copy Protocol）是一个用于在本地计算机和远程服务器之间安全地复制文件的命令行工具。  
基础指令如下。

1. 可以将本地的文件复制到服务器。

   ```bash
   scp local_file.txt user@remote_server_ip:/path/to/destination/
   ```

   - local_file.txt: 本地文件的路径。
   - user: 远程服务器上的用户名。
   - remote_server_ip: 远程服务器的 IP 地址。
   - /path/to/destination/: 目标目录在远程服务器上的路径。

2. 反过来可以将服务器的文件复制回来。

   ```bash
   scp user@remote_server_ip:/path/to/remote_file.txt /local/destination/
   ```

   - user: 远程服务器上的用户名。
   - remote_server_ip: 远程服务器的 IP 地址。
   - /path/to/remote_file.txt: 远程服务器上要复制的文件路径。
   - /local/destination/: 本地目录，用于保存复制的文件。

3. 复制整个目录（递归）到远程服务器

   ```bash
   scp -r local_directory user@remote_server_ip:/path/to/destination/
   ```

   - -r: 表示递归复制，用于复制整个目录。
   - local_directory: 本地目录的路径
   - user: 远程服务器上的用户名。
   - remote_server_ip: 远程服务器的 IP 地址。
   - /path/to/destination/: 目标目录在远程服务器上的路径，指定复制到远程服务器的位置。

4. 复制整个目录（递归）到本机

   ```bash
   scp -r user@remote_server_ip:/path/to/remote_directory /local/destination/
   ```

   - -r: 表示递归复制，用于复制整个目录。
   - user: 远程服务器上的用户名。
   - remote_server_ip: 远程服务器的 IP 地址。
   - /path/to/remote_directory: 远程服务器上要复制的目录路径。
   - /local/destination/: 本地目录，用于保存复制的目录。

### 更简便一点

经过了刚才~~繁琐冗赘~~的说明。此时，我们完成了无密码登录，以后使用 ssh 连接都只需要输入我们想登录的服务器和用户即可登录。
那么，我们还可以再简便一点吗？

当然，这样做还可以更懒一点，比如建立一个 sh 文件。

```sh
#!/bin/bash

# 显示菜单
echo "1, 1.1.1.1"
echo "2, 2.2.2.2"
echo "3, 3.3.3.3"
echo "4, 退出"

# 获取用户输入
read -p "请选择需要连接的服务器（输入相应的数字）: " choice

# 使用 case 语句进行选择
case $choice in
    1)
        echo "1.1.1.1"
        ssh username@1.1.1.1
        ;;
    2)
        echo "连接2.2.2.2"
        ssh username@2.2.2.2
        ;;
    3)
        echo "连接3.3.3.3"
        ssh username@3.3.3.3
        ;;
    4)
        echo "退出脚本"
        exit 0
        ;;
    *)
        echo "无效的选择，请输入1-4之间的数字"
        ;;
esac
```

这下每次只需运行脚本，输入数字即可连接。

或者说，你的目前在用移动端类 unix\linux 系统（比如 termux，一个适用于 Android 操作系统的开源终端模拟器应用），你打开它的唯一目的就是远程连接服务器，那么我们还可以玩的更极端点，直接使用`alias`来起一个别名。  
而`alias`是一个用于创建命令别名的 shell 内建命令。别名允许用户为常用的命令或一系列命令定义短而易记的替代名称。
`alias`基本语法为`alias name='command'`。  
别名只在当前会话中有效，如果您想要使别名在每个新的 shell 会话中都可用，可以将别名定义添加到 shell 配置文件（如 ~/.bashrc 或 ~/.bash_profile）中。  

```bash
echo 'alias link=ssh username@remote_host' >> ~/.bashrc # 使用bash
echo 'alias link=ssh username@remote_host' >> ~/.zshrc # 使用zsh
```

此时在终端输入`link`即可直接连接到`username@remote_host`，`alias`只做替换功能，我们完全可以继续使用其他参数诸如`-p`和`-t`。

> 注：termux 好像没有 bashrc，需要自己新建，以上代码可以直接新建。

## ssh 别名

想必看到这里，你已经基础了解了 ssh 是如何使用的了。  
尽管刚刚，我们已经会使用 alias 来起一个别名，但是就我而言，我其实并不喜欢为 ssh 专门新建一个 alias。  
那么，难道我们每一次都要完整的输入一次`ssh username@remote_host`吗？其实 ssh 也有一个专门的配置文件来帮助你为不同的 SSH 连接设置简短易记的别名，从而简化连接命令。  
SSH 别名可以通过配置文件 `.ssh/config` 实现，这个文件位于用户的主目录下`~/.ssh/config`。如果文件不存在，你可以手动创建它。  

基本的配置如下所示：

```bash
Host myserver
    HostName remote_host
    User  username
    Port  22
    IdentityFile ~/.ssh/id_rsa
```

- Host: 自定义别名。在连接时使用这个名称，而不需要输入完整的主机名。
- HostName: 实际的主机名或 IP 地址。
- User: 登录所使用的用户名。
- Port: 服务器的 SSH 端口。如果不是默认的 22 端口，则需要指定。
- IdentityFile: 私钥文件路径。如果你有多个私钥文件，可以指定特定的文件。

配置好别名后，你只需要使用 ssh 命令加上别名即可：

```bash
ssh myserver
#同等于ssh -i  ~/.ssh/id_rsa username@remote_host -p 22
```

还有一件事。使用 SSH 别名后，**仍然需要密钥进行无密码登录**，但 SSH 别名可以让连接过程更加简便。

### ssh 别名的其他选项

一般情况下，以上的配置就已经够用了。不过还有一些选项有时候也是会用上的。

#### ForwardAgent

ForwardAgent，它允许将本地 SSH 代理转发到远程服务器，从而使你可以在连接到远程服务器后，无需重新输入密码或私钥，即可在远程服务器上发起其他 SSH 连接。  
说的再详细一点就是：SSH Agent 的功能是，你只需要在本地机器上解锁一次私钥，后续的 SSH 连接会使用这个已经解锁的密钥，而不需要你再次输入密码。那么，ForwardAgent 是用来将本机的 SSH 代理转发到远程机器上，使得远程机器可以利用本机的 SSH 代理进行进一步的 SSH 连接。  
通过 ForwardAgent，在第一台机器上启动了 SSH 代理，之后连接到第二台机器时，这个代理会被转发到第二台机器上。然后第二台机器就可以使用在第一台机器上的密钥来进行连接，从而实现无密码登录，而不用将密钥拷贝到中间机器上。  

```bash
Host server
    ForwardAgent yes
```

#### ServerAliveInterval 和 ServerAliveCountMax

```bash
Host server
    ServerAliveInterval 60
    ServerAliveCountMax 3
```

ServerAliveInterval: 通过设置一个间隔来发送 keep-alive 消息，防止连接断开。  
详细点说就是：定义 SSH 客户端在多长时间内（以秒为单位）没有收到服务器响应时，会主动发送 keep-alive 消息以检查连接是否仍然有效。这个选项有助于防止长时间闲置的 SSH 连接因超时而断开。  
ServerAliveCountMax：设置在没有收到服务器响应的情况下，客户端可以发送多少次保持活动信号。

## 其他的指令

既然都讲到这里了，不如再多讲讲有关 ssh 的知识。  
现在说说`-t`选项。`-t`是 ssh 命令的一个选项，用于在远程服务器上分配一个伪终端，在某些情况很有用。  

例子：

```bash
ssh -t username@remote_host 'sudo apt update && sudo apt upgrade'
Hit:1 http://mirrors.cloud.aliyuncs.com/debian bullseye InRelease
......
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Connection to remote_host closed.
 ~/
```

可以看到，此时参数`-t`表示要分配一个伪终端（pseudo-tty）。  
在执行完`sudo apt update && sudo apt upgrade`后，伪终端则是会立即关闭。这种行为是由于指令执行完毕后没有额外的交互需要，所以终端会自动关闭。可以增加一个`&&bash`来启动一个新的`bash`会话，能够在保持 ssh 连接的情况下继续与服务器互动。  
但是我暂时还不知道这个能用来干什么。🤔

看了一下文档，发现 ssh 还有用作代理的方法，使用 `-D 端口号`参数，即可指定动态端口转发，创建一个 SOCKS 代理服务器，该代理服务器将在本地计算机的指定端口上监听。

```bash
ssh -D 1080 username@remote_host
```

以上代码建立了一个 ssh，它将在本地计算机的 1080 端口上监听，并且转发流量。

```bash
ssh -fNCqTn -D 1080 username@remote_host
# 加这么多参数好像有点重复，实际使用上可以看需删减参数
```

如果我们使用 ssh 作为代理的话，可以用以下的参数

- -f：将 ssh 连接放到后台运行，使其在后台运行，以便您可以继续使用终端。
- -N：告诉 ssh 不要执行任何远程命令，它只会建立连接而不执行任何额外的命令。
- -C: 启用数据压缩。这将通过 zlib 库对传输的数据进行压缩，以减小传输的数据量。
- -q: 静默模式。该选项用于减少输出，使 ssh 在执行时更为静默，不输出不必要的信息。
- -T: 禁用伪终端分配。通常，ssh 会为远程连接分配伪终端，但使用 -T 选项会禁用这个分配，适用于不需要交互的连接。
- -n: 防止远程命令执行时，ssh 从标准输入读取数据。

而`-L` 也是大差不差。在使用`-L`选项时，可本地主机上的一个端口映射到远程主机的指定端口。

```bash
ssh -L 9999:example.org:80 -N -T username@remote_host
```

此时，访问 local:9999 即为访问 example.org:80  
这个 ssh 命令的目的是通过 ssh 隧道将本地端口（localhost:9999）转发到远程主机（example.org）的端口 80。

`-J`，这个 ssh 参数是用于通过跳板主机（jumphost）连接到远程服务器的。跳板主机充当一个中间站，帮助我们间接地连接到目标服务器。

```bash
ssh -J username@jump_host username@remote_host
```

`-J`: 指定跳板主机，表示要通过跳板主机连接到目标服务器。

可以用来连接当作外网连接内网的跳板机。  
而在一些安全要求较高的环境中，跳板主机可以作为一个堡垒机（Bastion Host），所有外部连接都必须先通过堡垒机，再通过内部网络连接到其他服务器。  
还有的时候需要通过多个跳板主机才能达到目标服务器，这就形成了一个跳板链。通过 ssh 的 -J 选项，可以指定多个跳板主机，以建立跳板链。比如`ssh -J user1@jump_host1,user2@jump_host2 user@remote_host`

累了，不想写了。over.😇

参考：

> [ssh | tldr InBrowser.App](https://tldr.inbrowser.app/pages/common/ssh)
> [ssh 命令的三种代理功能（-L/-R/-D）](https://zhuanlan.zhihu.com/p/57630633)

## 记录

- 2024-02-02 初稿完成。
- 2024-02-10 订正初稿，发布。
- 2024-05-10 更新杂七杂八的其他指令。
- 2024-10-20 更新 ssh 别名，和 ServerAliveInterval、ForwardAgent、ProxyJump 讲解。
- 2025-03-01 订正文章格式错误。

无关紧要的念念碎：怎么这玩意越写越多了，而且的 ssh 内容真的好多啊，再写下去还不如去看官方文档了。
