SSH 是每一台电脑的标准配置,Linux 就没必要说了,连 windows 也从 2018 年开始自带 OpenSSH 了。
它主要的用途是登录到远程电脑中执行命令,在云开发的时代,它是每一个程序天天都要用到的工具。本文将简单介绍一下它的原理,
基本用法以及端口转发和动态转发等高阶用法。mysql
SSH 叫安全外壳协议(Secure Shell),是一种加密的网络传输协议,可在不安全的网络中网络服务提供安全的传输环境。它经过在网络中建立安全隧道来实现 SSH 客户端和服务器之间的链接。最先的时候,互联网通讯都是明文通讯,一旦被截获,内容就会被暴露。1995年,芬兰学者 Tatu Ylonen 设计了 SSH 协议,将登陆信息所有加密,成为互联网安全的一个基本解决方案,迅速在全世界得到推广,目前已经成为全部操做系统的标准配置。linux
SSH 是一种协议, 存在多种实现,既有商业实现,也有开源实现(OSSH,OpenSSH)。本文使用的自有软件 OpenSSH, 毕竟它是目前最流行的 SSH 实现,并且是全部操做系统的默认组件。git
TIPS: OpenSSH发展史
1995 年 7 月, Tatu Ylonen 以避免费软件的形式将一套保护信息传输的程序(也就是 SSH )发布出去。程序很快流行,到年末已经有两万用户,遍及五十国家。因此在年末时,他创立了 SSH 通讯安全公司来继续开发和销售 SSH, 因此它变成了专有软件。在 1999 年,瑞典程序员基于 SSH 最后一个开源的版本 1.2.12 开发了 OSSH,以后 OpenBSD 开发者在 OSSH 的基础上进行大量修改,造成了 OpenSSH。它是目前惟一一种最流行的 SSH 实现,成为了全部操做系统的默认组件。
SSH 之因此一经提出,就获得了快速发展,是由于数据的安全性对任何人都很是重要。这里咱们对其保护数据安全的原理进行探究。程序员
在聊加密前先介绍一下几个密码学的基本概念:github
plaintext
指传送方(通常指客户端)想要接受方(通常指服务端)得到的可读信息ciphertext
指明文通过加密后所产生的信息key
指用来完成加密、解密、完整性验证等密码学应用的密码信息,是明文转换为密文或密文转换为明文的算法须要的参数对称加密就是加密或解密使用的是同一个秘钥。比较经常使用的对称加密算法有 AES,DES等。其具体的时序图以下:算法
对称加密的优势是加解密效率高,速度快。对于服务端而言,它和每一个客户端都要有一个秘钥,庞大的客户端数目致使秘钥数目多,并且一旦机器被登陆,全部的秘钥都泄露,因此缺点是秘钥的管理和分发比较困难,不安全。sql
非对称加密须要一对秘钥来进行加密和解密,公开的秘钥叫公钥,私有的秘钥叫私钥。注意公钥加密的信息只有私钥才能解开(加密过程),私钥加密的信息只有公钥才能解开(验签过程)。比较经常使用的非对称加密算法有 RSA。其具体的时序图以下:windows
非对称加密的优势是安全性更高,秘钥管理比较方便,每一个服务器只要维护一对公私钥便可。缺点是加解密耗时长,速度慢。不过对于如今的计算机而言,这点成本能够忽略不计。api
中间人攻击的英文全称是 Man-in-the-middle attack,缩写为 MITM。在密码学和计算机安全领域中是指攻击者与通信的两端分别建立独立的联系,并交换其所收到的数据,使通信的两端认为他们正在经过一个私密的链接与对方直接对话,但事实上整个会话都被攻击者彻底控制。在中间人攻击中,攻击者能够拦截通信双方的通话并插入新的内容。在许多状况下这是很简单的(例如,在一个未加密的 Wi-Fi 无线接入点的接受范围内的中间人攻击者,能够将本身做为一个中间人插入这个网络)。其具体的时序图以下:安全
受到中间人攻击的关键缘由是客户端不知道服务端的公钥真假,服务端也不知道客户端的公钥真假。因此破解这个问题的关键是如何相互认证,也就是要像黄宏《开锁》小品里同样证实我就是我,你就是你。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ 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:sYNNR1L6T5cSAG4BndqtdDhJEI0eB9LamBTkuIue3+0. Please contact your system administrator. Add correct host key in /Users/xx/.ssh/known_hosts to get rid of this message. Offending ECDSA key in /Users/xx/.ssh/known_hosts:40 ECDSA host key for [xx.com] has changed and you have requested strict checking. Host key verification failed.
ssh-keygen 是安全外壳( SSH )协议套件的标准组件,用于生成,管理和转换身份验证密钥。
$HOME/.ssh
目录下生产私钥 filename 和公钥 filename.pub# 生成公私钥,默认文件为 ~/.ssh/id_rsa ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
ssh-agent 和 ssh-add 是安全外壳(SSH)协议套件的标准组件,用于管理私钥。通常状况下咱们使用不带密码的 id_rsa 做为咱们的默认私钥,此时是不必启动 ssh-agent 的。当咱们碰到如下两种状况则须要它:
# 启动代理 eval `ssh-agent` # 关闭代理 ssh-agent -k # 在 ~/.bashrc 中加入如下来实现登录自动启动 ssh-agent,退出自动 kill 掉程序 eval $(ssh-agent -s) > /dev/null trap 'test -n "$SSH_AGENT_PID" && eval `/usr/bin/ssh-agent -k` > /dev/null' 0 # 查看代理中的私钥 ssh-add -l # 查看代理中私钥对应的公钥 ssh-add -L # 移除指定的私钥 ssh-add -d /path/of/key/key_name # 移除全部的私钥 ssh-add -D
ssh-copy-id 是一个用来将公钥放到服务器上的脚本。它经过 SSH 密码登录远程服务器,并将指定的公钥放到远程服务器 $HOME/.ssh/authorized_keys
中。这个操做也能够先登录到服务器中,而后经过 vi 等文本编辑命令向 $HOME/.ssh/authorized_keys
中加入容许登录的公钥。不过对于云服务器能够在启动服务器时在页面上操做绑定公钥,这样更安全些(阿里云和腾讯云默认关闭秘钥登录 PasswordAuthentication no
)。特别注意的是,千万别在公共的网络中经过密码登录远程服务器,而秘钥登录没有问题。
# 发送公钥的两种方式(等价) ssh-copy-id -i ~/.ssh/id_rsa.pub user@host ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub
SSH 登录服务器须要知道服务器的主机地址(主机名或主机 IP 地址),用户名和密码,有时还要指定端口号(默认 22 )。主机名还好,可是主机IP 地址就比较难记的,特别是当你可能要登陆十几台服务器时。通常咱们使用的登录命令以下:
# 登录目标服务器( 172.17.132.120 ) ssh -p 58422 user@172.17.132.120 # 经过跳板机登录目标服务器( 172.17.132.120 ) ssh -p 58422 user@jumper.example.com ssh user@172.17.132.120 # 端口映射 ssh -p 58422 user@jumper.example.com -fNL 5433:172.17.132.120:5432 -N
经过配置 $HOME/.ssh/config
可使用如下命令来登陆。
# 登录目标服务器( 172.17.132.120 ) ssh target # 经过跳板机登录目标服务器( 172.17.132.120 ) ssh jump_target # 端口映射 ## 登录时经过 LocalForward 配置 ssh jump_target ## 使用-L来实现本地端口映射 ssh -C -N -g -L 5433:127.0.0.1:5432 jump_target
# 通用配置,全部配置都使用 Host * AddKeysToAgent yes # 将私钥添加到ssh-agent中 UseKeychain yes # 保存密码到agent中 ServerAliveInterval 10 # 链接心跳间隔10s ServerAliveCountMax 3 # 重连次数为3 # target配置 Host target HostName 172.17.132.120 User user Port 58422 IdentityFile ~/.ssh/id_rsa # 跳板机配置 Host jumper HostName jumper.example.com User user Port 58422 IdentityFile ~/.ssh/id_rsa Host jump_target HostName 172.17.132.120 User user Port 22 IdentityFile ~/.ssh/id_rsa ProxyCommand ssh user@jumper -W %h:%p 2>/dev/null LocalForward 5433 localhost:5432 # 本地5433映射到jump_target的5432
TIPS:
VS Code 的 Remote 插件会读取本地的配置文件$HOME/.ssh/config
,以便像本地同样进行远程开发。
通常在 $HOME/.ssh
目录下除了公私钥文件,config 配置文件,authorized_keys 认证文件外,还有一个 known_hosts 文件。
这个文件记录了远程主机 ip 和远程主机对应的公钥指纹。咱们在第一次登录(密码或秘钥登录)服务器时,会有以下的提示界面:
### SSH 首次登录的提示 The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established. ECDSA key fingerprint is SHA256:HosOqhcUmbB7QG81yCuDPkvxTgot+vpple+czXPrEug. ECDSA key fingerprint is MD5:fd:d7:e1:2c:42:4e:b4:2d:a3:21:4d:d1:c4:74:64:2d. Are you sure you want to continue connecting (yes/no)?
此时 known_hosts 并无 127.0.0.1 这台机器的指纹信息,因此显示这个提示来让咱们确认这个指纹是不是目标机器的 ECDSA 算法的指纹。
当咱们输入 yes 确认后,在下次登陆的时候,远程主机发送过来的公钥指纹,直接和 known_hosts 文件中对应 ip 的公钥指纹比较便可。
# 本机查看服务器 172.17.132.120 的全部公钥(要与服务器上 /etc/ssh 下面的公钥 *.pub 一致) ssh-keyscan -p 22 172.17.132.120 # 查看服务器公钥 ecdsa 的指纹 -E md5/sha256 指纹 hash 算法 ssh-keygen -E md5 -lf /etc/ssh/ssh_host_ecdsa_key.pub ## 256 MD5:84:3d:9c:6e:75:f2:6b:b2:0b:40:aa:d6:29:2f:b4:40 no comment (ECDSA) ## 256 SHA256:ZoGnph63gnKLC9wQYrHYVU8ROTf6+K9LKAjn+jrXB2o no comment (ECDSA) # 从客户端查看服务器公钥 ecdsa 的指纹(初次登录时要验证的指纹) ssh-keyscan -t ecdsa -p 22 172.17.132.120 |ssh-keygen -lf - # 公钥转换成特定指纹 hash 算法的指纹 awk '{print $2}' /etc/ssh/ssh_host_ecdsa_key.pub | base64 -d|openssl sha256 -binary |base64
TIPS: known_hosts的重要性
known_hosts 这个文件是客户端验证服务端身份的重要依据。每次客户端向服务端发起链接请求时,不只服务端要验证客户端的合法性,客户端也须要验证服务端的身份。客户端就是经过 known_hosts 中的公钥指纹来验证服务器是否发生了变化。它在必定程度上能避免中间人攻击,除了第一次登录,由于那时 known_hosts 中尚未服务器的身份信息,因此对于首次提示的登录指纹信息仍是须要和服务器比对的。最安全保险的作法是第一次登录就使用秘钥登录。
密码登录的认证流程以下:
秘钥登录的认证流程以下:
$HOME/.ssh/authorized_keys
中找对应的公钥,若是没有找到,发送失败消息给客户端,若是找到,比较客户发送过来的这个公钥和找到的公钥,若是内容相同,服务端生成一个随机的字符串,简称“质询”,而后使用找到的公钥加密这个质询,而后使用会话密钥再次加密。咱们常说使用秘钥登录比密码登录更方便更安全,为何这么说呢?方即是由于不用记密码,安全是一方面敏感关键的密码没有在传输,另外一方面是由于质询的存在使得在一次对话中同时验证了客户端和服务端。
scp/rsync/sftp 均可以基于 SSH 来进行免密安全传输,常见命令以下:
# 从本地同步 src.tar.gz 文件到远程服务器 jump_target 的目录 /path/to/des/ scp src.tar.gz jump_target:/path/to/des/ rsync -avz src.tar.gz jump_target:/path/to/des/ # 从远程服务器 jump_target 的文件 /path/to/src.tar.gz 到本地 scp jump_target:/path/to/src.tar.gz . rsync -avz jump_target:/path/to/src.tar.gz .
SSH 不只仅可以自动加密和解密 SSH 客户端与服务端之间的网络数据,同时,SSH 还可以提供了一个很是有用的功能,那就是端口转发,即将 TCP 端口的网络数据,转发到指定的主机某个端口上,在转发的同时会对数据进行相应的加密及解密。若是工做环境中的防火墙限制了一些网络端口的使用,可是容许 SSH 的链接,那么也是可以经过使用 SSH 转发后的端口进行通讯。转发主要分为本地转发与远程转发两种类型。
由本地网络服务器的端口 A,转发到远程服务器端口 B。说白了就是,将发送到本地端口 A 的请求,转发到目标端口 B。格式以下
ssh -L 本地网卡地址:本地端口:目标地址:目标端口 用户@目标地址
常见的应用场景见下图:
对应的命令以下:
# jump_target 服务器上的 3306 端口服务映射到本地 33306 `mysql -u root -p root -H localhost -P 33306` ## 1 是 2,3,5 路线中的加密通道,将本地 33306 的网络数据转发到 jump_target 的 3306 端口 ssh -C -N -g -L 33306:localhost:3306 jump_target ## 在 2,3 中搞了个加密通道,而后在跳板机上将本地 33306 的网络数据转发到 172.17.132.120 的 3306 端口 ssh -C -N -g -L 33306:172.17.132.120:3306 jumper
由远程服务器的某个端口,转发到本地网络的服务器某个端口。说白了,就是将发送到远程端口的请求,转发到目标端口。格式以下:
ssh -R 远程网卡地址:远程端口:目标地址:目标端口 用户@目标地址
常见的应用场景有个专用术语叫内网穿透,结构以下图:
# 将公网上的服务器 jump_target 的端口 33333 映射到本地的 22,这样就能够经过在 jump_target 上经过 SSH 来访问本地机器 ssh -f -N -g -R 33333:127.0.0.1:22 jump_target
TIPS:
公网上的服务器 jump_target 要设置GatewayPorts yes
,默认为 no。此外要映射的端口 33333 要能够访问。
动态转发就是创建一个SSH加密的SOCKS 4/5代理通道。任何支持 SOCKS 4/5 协议的程序均可以使用这个加密的通道进行访问。格式以下:ssh -D [本地地址:]本地端口号 远程用户@远程地址
# 将访问本地 55558 端口的请求都转发给 jump_target ,并让它去执行 ssh -C -N -g -T -D 127.0.0.1:55558 jump_target
# 跳板机的配置 Host jump HostName jumper.example.com Port 58422 User haojunyu IdentityFile ~/.ssh/dg_rsa AddKeysToAgent yes # 将私钥添加到 agent 中 UseKeychain yes # 保存密码到 agent 中 # 目标机的配置 Host ws HostName 172.17.132.120 Port 22 User haojunyu IdentityFile ~/.ssh/dg_rsa ProxyCommand ssh haojunyu@jump -W %h:%p 2>/dev/null ServerAliveInterval 10 ServerAliveCountMax 3
平常工做中常常会启不少服务在内网机器上,而后经过打洞(本地端口转发)来将本地的端口映射到内网机器上服务端口。
这样有个问题就是一个服务就得维持一个打洞命令 ssh -C -N -g -L 33306:172.17.132.120:3306 jumper
。
对应这样的问题,最好的解决方案是使用动态转发 ssh -C -N -g -T -D 127.0.0.1:55557 hb_jumper
,
本地经过 SwitchyOmega 或 proxifier 工具来将内网 IP 段 172.17.* 的请求转发到本地的 55557 端口。
一般在服务器上执行 git push
时会报以下错误
具体报错信息:
Permission denied (publickey).
fatal: Could not read from remote repository.Please make sure you have the correct access rights and the repository exists.
报错的缘由是当前机器上没有服务告诉 git 要使用哪一个私钥来进行 git 的操做。
对应的解决方法也比较多,推荐解法一和二:
解法一:经过 ~/.ssh/config
指定(适用我的机器)
Host github.com HostName github.com User haojunyu IdentityFile ~/.ssh/id_rsa
解法二:配置仓库或全局的 core.sshCommand(指定仓库适用共享机器,全局适用我的机器.git版本高于2.3.0)
git config core.sshCommand "ssh -i ~/.ssh/id_rsa -F /dev/null"
解法三:ssh-agent 临时受权(适用共享机器)
eval `ssh-agent` ssh-add ~/.ssh/id_rsa
这个状况是但愿开机时就把端口转发开通,而且一直保持着。这就得介绍 linux 中经常使用的两种服务化的工具:Supervisor 和 Systemd。
前者是须要安装 Supervisor, 但工具比较轻量,使用也比较简单,后者虽然比较重,可是基本全部系统都自带。下面提供二者的配置方法:
[program:ssh-wifi_ol] command=ssh -C -N -g -L 9789:127.0.0.1:9789 jump stdout_logfile=/Users/haojunyu/.supervisord_log/ssh-wifi_ol.log autostart=true autorestart=true startsecs=5 priority=1 stopasgroup=true killasgroup=true
# gfw service [Unit] Description=gfw After=network.target [Service] Type=simple User=hjy ExecStart=ssh -C -N -g -T -D 127.0.0.1:55558 gfw Restart=on-failure [Install] WantedBy=multi-user.target
TIPS:
把一些常常用的服务经过端口转发服务化,而一些临时性的服务经过命令来进行端口转发,也可使用同事编写的端口转发的 Python 程序来进行。
若是该文章对您产生了帮助,或者您对技术文章感兴趣,能够关注微信公众号: 技术茶话会, 可以第一时间收到相关的技术文章,谢谢!
本篇文章由一文多发平台ArtiPub自动发布