本文详细介绍
SSH(Secure Shell Protocol)相关知识,如如何在一台服务器中,生成 ssh-key,并为不同地址配置不同 ssh key,该方法适合为不同 git client 配置不同 key。
介绍
- SSH(Secure Shell Protocol) 是一个传输安全协议、一个身份验证协议和一系列应用协议。最典型的应用层协议是远程 shell
- SSH 协议是一个标准化的协议,由 IETF 制定,主要的 RFC 有:
- RFC 4250: The Secure Shell (SSH) Protocol Assigned Numbers
- 4.9.3. Connection Protocol Channel Request Names
- pty-req
- x11-req
- env
- shell
- exec
- subsystem
- window-change
- xon-xoff
- signal
- exit-status
- exit-signal
- 4.9.3. Connection Protocol Channel Request Names
- RFC 4251: The Secure Shell (SSH) Protocol Architecture
- RFC 4252: The Secure Shell (SSH) Authentication Protocol 用户身份认证算法协议
- RFC 4253: The Secure Shell (SSH) Transport Layer Protocol 传输层协议,简单流程
- 建立底层连接(4.1 Use over TCP/IP)
- Client 请求建立 TCP 连接
- Server Accept 完成 TCP 连接建立
- 协议版本交换(4.2 Protocol Version Exchange)
- Client 发送字符串,格式
SSH-protoversion-softwareversion SP comments CR LF,如SSH-2.0-Go\r\n - Server 发送字符串,格式要求和 Client 一致。如
SSH-2.0-dropbear_2022.83\r\n
- Client 发送字符串,格式
- Key 交换算法协商(7.1. Algorithm Negotiation)
- 原因:非对称加密算法性能太差,SSH 在这里交互对称加密算法和密钥,之后采用对称加密通信,类似于 https 交互过程
- Key 交换算法执行(8. Diffie-Hellman Key Exchange)
- 9. Key Re-Exchange
- 即 Key 会多次交换
- 建立底层连接(4.1 Use over TCP/IP)
- RFC 4254: The Secure Shell (SSH) Connection Protocol 连接协议,包括:交互式登录会话、TCP/IP 端口转发、X11 Forwarding
- 在 SSH 协议中,
Channel实现对底层连接的多路复用(虚拟连接)- 通过一个数字来进行标识和区分这些 Channel
- 实现流控(窗口)
- Channel 实现
- 交互式会话(Session)表示远程执行一个程序
- TCP/IP 端口转发
- 本地转发(
direct-tcpip):将 client 监听的 tcp 端口连接转发到 server 上 远端转发(forwarded-tcpip):将 server 监听的 tcp 端口连接转发到 client 上
- 本地转发(
- 在 SSH 协议中,
- RFC 4250: The Secure Shell (SSH) Protocol Assigned Numbers
- 架构
high level
+-------------------------+---------------------+
| Authentication Protocol | Connection Protocol |
+-------------------------+---------------------+
| Transport Layer Protocol |
+-----------------------------------------------+
| Underlying Connection |
+-----------------------------------------------+
low level部署 SSH
服务端
SSH 服务端是一个守护讲程 (daemon):sshd,用来响应来自客户端的连接请求和响应,一般包括公共密钥认证、密钥交换、对称密钥加密和非安全连接等
# 安装服务端
$ apt install openssh-server
$ dpkg -S `which sshd`
openssh-server: /usr/sbin/sshd
$ dpkg -L openssh-server
# 配置文件
$ tree /etc/ssh/
/etc/ssh/
├── moduli
├── ssh_config
├── ssh_config.d
├── sshd_config # 配置文件
├── sshd_config.d
│ └── 50-cloud-init.conf
├── sshd_config.ucf-dist
├── ssh_host_dsa_key # 下面 key 和 pub 是在启动时生成的,在第一次启动服务时自动生成
├── ssh_host_dsa_key.pub
├── ssh_host_ecdsa_key
├── ssh_host_ecdsa_key.pub
├── ssh_host_ed25519_key
├── ssh_host_ed25519_key.pub
├── ssh_host_rsa_key
├── ssh_host_rsa_key.pub
└── ssh_import_id
3 directories, 14 files启动服务端
systemctl status ssh.service
# 启动前,可以使用以下命令检测配置文件是否合法
sshd -t /etc/ssh/sshd_config客户端
SSH 客户端包括:ssh远程登陆,软连接sloginscp远程拷贝sftp(Secure File Transfer Protocol, 安全文件传送协议)等应用程序
# 安装客户端
$ apt install openssh-client
$ dpkg -S `which ssh`
openssh-client: /usr/bin/ssh
$ dpkg -L openssh-client | grep bin
/usr/bin
/usr/bin/scp
/usr/bin/sftp # 安全FTP文件传输
/usr/bin/ssh
/usr/bin/ssh-add
/usr/bin/ssh-agent
/usr/bin/ssh-argv0
/usr/bin/ssh-copy-id
/usr/bin/ssh-keygen
/usr/bin/ssh-keyscan
/usr/bin/slogin
$ ls -lh /usr/bin/slogin
lrwxrwxrwx 1 root root 3 Feb 15 10:13 /usr/bin/slogin -> sshssh-keygen 生成 ssh key
通常,要为多个邮箱账号分别生成公钥,公私钥都是放到~/.ssh 下面,生成步骤如下:
$ ssh-keygen -t ed25519 -b 4096 -C "me@xiexianbin.cn" # 推荐,比 rsa 短,安全性高
$ ssh-keygen -t rsa -C "me@xiexianbin.cn" # 把这个文件命名为id_rsa_github,然后一路回车
$ ssh-keygen -t rsa -C "10972072@qq.com" # 把这个文件命名为id_rsa_aliyun,然后一路回车
$ ssh-keygen -t rsa -b 4096 -C "me@xiexianbin.cn"将 SSH 私钥增加到 ssh-agent:
ssh-add ~/.ssh/id_rsa查看已经 add 的 SSH KEY:
ssh-add -l若报错:Could not open a connection to your authentication agent.,执行:
ssh-agent bashPS:将 ~/.ssh/id_rsa.pub 添加到目标及其的 .ssh/authorized_keys 可以实现 ssh 证书登录。
修改 key 密码
ssh-keygen -f ~/.ssh/id_rsa -pssh-copy-id 分发公钥
ssh-copy-id $IP #$IP为本虚机地址,按照提示输入yes 和密码,然后可以无密码登录服务器上述过程,是将 id_rsa.pub 添加到对应主机的 ~/.ssh/authorized_keys 目录下。
多个服务器分别配置 key
此时在~/.ssh下面生成了两对公私钥,把 id_rsa_aliyun.pub 的内容贴到 aliyun 的 git 服务的 ssh keys 中,把 id_rsa_github.pub 的内容贴到 github 的 ssh keys 中。然后 touch 一个配置文件:
touch ~/.ssh/config
chmod 600 ~/.ssh/*最后在~/.ssh/config 中添加如下内容即可:
host code.aliyun.com
# user xiexianbin
hostname code.aliyun.com
port 22
identityfile ~/.ssh/id_rsa_aliyun
host github.com
User git
hostname github.com
port 22
identityfile ~/.ssh/id_rsa_githubHost 10.0.0.* 10.0.1.*
port 22
IdentityFile ~/.ssh/id_rsa
User root- 快捷登录
host vm1
User root
hostname 10.0.0.2
port 22
identityfile ~/.ssh/id_rsa可以直接 ssh vm1 登录该机器。
测试
然后用 ssh 命令分别测试:
ssh -T git@github.com调试
如果到这里你没有成功的话,可以 –debug,比如测试 github:
ssh -vT git@github.com-v 是输出编译信息,然后根据编译信息自己去解决问题吧。
如果卡在 debug1: pledge: network 阶段,检查 /etc/hosts 是否有 hostname 的解析,并重启 systemctl restart systemd-logind
配置免确认
修改 ~/.ssh/config,内容如下:
UserKnownHostsFile=/dev/null
StrictHostKeyChecking=no
LogLevel=ERROR修改权限:
chmod 400 ~/.ssh/configssh 免 hostkey check 登录:
sudo ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i .ssh/ssh_key.key ubuntu@10.0.0.1自动登陆文件
cat 10.0.0.1
#!/bin/sh
user="root"
host=`basename $0`
ssh -i ~/.ssh/id_rsa $user@$hostTCP port or Unix socket 转发
man ssh 查看
本地端口转发
- 目标:将远程主机服务监听的端口转发到本地
- 参数
-
使用场景
- 远程机器监听在
127.0.0.1:3306的 MySQL 想在本地访问 - 远程机器(到本地近通 22 端口)能访问的某个服务端口想在本地访问
- 远程机器监听在
-
示例
# 远程机器启动本地服务
$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
# 本地转发,监听在本地的 5000 端口
ssh -L 127.0.0.1:5000:127.0.0.1:8000 root@<server-ip>
# ssh -v -CNg -L 127.0.0.1:5000:127.0.0.1:8000 root@<server-ip> -p 22
# 本地查看 ssh 监听在 5000 端口
$ ss -lpn | grep 5000
tcp LISTEN 0 128 127.0.0.1:5000 0.0.0.0:* users:(("ssh",pid=133,fd=4))
# 访问本地的 5000 端口(等价于访问远程的 8000 端口)
$ curl -i -v 127.0.0.1:5000
* Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000
...
< HTTP/1.0 200 OK远程端口转发
- 目标:将本地主机(或局域网)服务监听的端口转发到远程服务器
- 参数
-R [bind_address:]port:host:hostport
-R [bind_address:]port:local_socket
-R remote_socket:host:hostport
-R remote_socket:local_socket
-R [bind_address:]port需要配合修改 /etc/ssh/sshd_config 中 GatewayPorts yes
-
使用场景
- 远程服务器想访问本地监听在
127.0.0.1:8000上的服务
- 远程服务器想访问本地监听在
-
示例
# 本地启动本地服务
$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
# 远程端口转发,监听在远程的 5000 端口
ssh -R 127.0.0.1:5000:127.0.0.1:8000 root@<server-ip>
# 远程查看 sshd 监听在 5000 端口
$ ss -lpn | grep 5000
tcp LISTEN 0 128 127.0.0.1:5000 0.0.0.0:* users:(("sshd",pid=31006,fd=6))
# 远程访问自己的 5000 端口(等价于访问客户端的 8000 端口)
$ curl -i -v 127.0.0.1:5000
* Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000
...
< HTTP/1.0 200 OK- 扩展,也可利用
yum install autossh实现
动态转发
- 目标:将本地主机(或局域网)服务监听的端口转发到远程服务器
-D [bind_address:]port
Specifies a local “dynamic” application-level port forwarding. This works by allocating a socket to
listen to port on the local side, optionally bound to the specified bind_address. Whenever a con‐
nection is made to this port, the connection is forwarded over the secure channel, and the applica‐
tion protocol is then used to determine where to connect to from the remote machine. Currently the
SOCKS4 and SOCKS5 protocols are supported, and ssh will act as a SOCKS server. Only root can for‐
ward privileged ports. Dynamic port forwardings can also be specified in the configuration file.
IPv6 addresses can be specified by enclosing the address in square brackets. Only the superuser can
forward privileged ports. By default, the local port is bound in accordance with the GatewayPorts
setting. However, an explicit bind_address may be used to bind the connection to a specific ad‐
dress. The bind_address of “localhost” indicates that the listening port be bound for local use
only, while an empty address or ‘*’ indicates that the port should be available from all interfaces.- 示例
# 本地创建 SOCKS 代理,且监听在 5000 端口
$ ssh -D 5000 root@192.168.179.159
# 本地监听 5000 端口
$ ss -lpn | grep 5000
tcp LISTEN 0 128 127.0.0.1:5000 0.0.0.0:* users:(("ssh",pid=279,fd=4))
# 本地指定代理,可以访问远程服务器可以访问的任意服务?
HTTP_PROXY=127.0.0.1:50000 curl 192.168.179.159:8000高级用法
执行多个命令
ssh -CT -o BatchMode=yes c1F&Q
ssh 链接慢
- 修改:
/etc/ssh/sshd_config
UseDNS no
# GSSAPI options, 使用 ssh -v xiexianbin@git.xiexianbin.cn 调试发现连接gssapi-with-mic消耗时间较长
GSSAPIAuthentication no
GSSAPICleanupCredentials now命令查看当前 ssh 远程连接数,是否达到最大连接数
vim /etc/ssh/sshd_config
LoginGraceTime 2m # 参数表示登录验证时间
MaxAuthTries 6 # 最大验证重试次数
MaxSessions 10 # 最大远程连接数last命令查看最近登录日志/var/log/secure系统安全日志/etc/hosts.all和/etc/hosts.deny查看限定的ip
ssh 连接超时中断
超时时,ssh console 的错误日志
client_loop: send disconnect: Broken pipe- 修复方式:客户端配置
- ssh 指定超时时长
ssh -o ServerAliveInterval=600 xxx - 配置文件指定
echo "ServerAliveInterval 600" >>> ~/.ssh/config - 或在
~/.ssh/config中配置
Host *
ServerAliveInterval 600- 修复方式:服务器配置
服务器端,修改/etc/ssh/sshd_config,重启 sshd 后生效:
# 单位:秒,SSH 客户端不活动时间间隔
ClientAliveInterval 200
# SSH 服务器向客户端尝试发送活动消息的次数,超过该次数后将中断 SSH 连接
ClientAliveCountMax 3禁用密码登录
编辑/etc/ssh/sshd_config,将PasswordAuthentication参数值修改为no:
sed 's/#PasswordAuthentication yes/PasswordAuthentication no/g' -i /etc/ssh/sshd_config
systemctl restart ssh.service # sshd.service禁止提示 Warning: Permanently added ’’ (ED25519) to the list of known hosts.
Host *.mydomain.com
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
User foo
LogLevel QUIET
# 或
ssh -o LogLevel=ERROR xxx
ssh -o LogLevel=quiet xxx