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自動發佈