↓ 快轉到主要內容
  1. 技術文章/

VPS 安全三件事:Email 驗證碼門禁、每日加密備份、SSH 只走 Tailscale

·2326 字·5 分鐘
作者
Loyu
記錄學習過程中的技術筆記,也分享自己做出來的作品。
目錄
自架 VPS 筆記 - 本文屬於一個選集。
§ 4: 本文

本文由 AI 協助撰寫,內容經作者確認。

我在 VPS 上放了網站和一個只給自己看的交易面板。基本防護(SSH 只用金鑰登入、防火牆、fail2ban)做好之後,還剩三個缺口:

缺口補法結果
私人網頁只靠一組帳號密碼Cloudflare Access 的 Email 驗證碼先過信箱驗證碼,才看得到帳號密碼畫面
資料只存在 VPS 上,主機壞了就沒了每天加密備份到 GitHub 私人倉庫主機毀損也能還原,備份外洩也打不開
SSH 對全世界開放改走 Tailscale 私人網路公開網路上連 SSH 的門都找不到

名詞說明(Tunnel、2FA、OTP、One-Time PIN)請看上一篇:Cloudflare Tunnel、2FA、OTP 與 One-Time PIN。

一、私人網頁加上 Email 驗證碼
#

做法
#

在 Cloudflare 後台(Cloudflare One / Zero Trust)建立兩個「自我裝載」應用程式:

應用程式網址範圍原則
交易面板整個子網域允許:只有我的 Email,登入方式只開 One-time PIN,工作階段 24 小時
憑證更新同一個子網域的 /.well-known/acme-challenge/略過:所有人

新版後台建議先在「Access 控制 → 原則」建好原則,再建立應用程式時直接選用,流程會比較順。

打開網頁時,Cloudflare 先要求輸入 Email,寄出 6 位數驗證碼,通過後才會把連線送到主機,接著才是網站原本的帳號密碼。兩道關卡分別是「你擁有的信箱」和「你知道的密碼」,合起來才算真正的雙因素驗證。

容易漏掉的兩件事
#

1. 憑證更新的路徑要放行。網站的 HTTPS 憑證(Let’s Encrypt)大約每 60 天自動更新一次,更新時憑證機構要能連到 /.well-known/acme-challenge/。這個路徑如果也被門禁擋住,平常完全不會發現,等到兩、三個月後憑證過期,網頁才突然打不開。所以要另外建一個「略過」的應用程式。

2. 主機端也要檢查。Cloudflare 的門禁只管「經過 Cloudflare」的連線。如果有人知道主機的真實 IP 並直接連線,就繞過門禁了。我的防火牆本來就只允許 Cloudflare 的 IP 連網頁,另外在網頁伺服器(Caddy)再加一道:請求裡沒有 Cloudflare Access 附上的 Cf-Access-Jwt-Assertion 標頭就直接拒絕。

@noaccess {
	not header Cf-Access-Jwt-Assertion *
	not path /.well-known/acme-challenge/*
}
route {
	respond @noaccess "Forbidden" 403
	# 之後才是原本的帳號密碼與網站設定
}

用 route 包起來,是為了讓這道檢查在帳號密碼之前執行。

二、每日加密備份到 GitHub
#

備份什麼、不備份什麼
#

備份不備份
資料庫(交易紀錄、市場資料)API 金鑰、App 簽章金鑰、SSH 金鑰
伺服器設定(網站、防火牆、SSH、排程)可以重新下載的大型研究資料
工作筆記

密鑰刻意不備份,原因是:如果備份裡有密鑰,「一把解密金鑰」就能打開所有密鑰,風險集中。所以每一種密鑰各自有復原方式:

  • App 簽章金鑰:存在密碼管理器,這是唯一無法重新產生的。
  • 其他金鑰(券商 API、Telegram Bot、Cloudflare、GitHub 部署金鑰):到各自的後台重新申請。

怎麼加密
#

用 age 這個加密工具,它的特點是加密和解密用不同的鑰匙:

  • 公鑰:只能把檔案鎖起來,放在 VPS 上,讓每天的備份可以自動加密。
  • 私鑰:才能把檔案打開,只存在我的密碼管理器(另外抄一份紙本),VPS 上已經刪除。

這樣就算 VPS 被入侵,入侵者手上只有公鑰,GitHub 上的備份對他來說只是一堆亂碼。代價是私鑰一旦弄丟,已經做好的備份就永遠打不開了,所以私鑰要存兩個地方。

流程
#

  1. 每天凌晨用排程(systemd timer)執行備份程式
  2. 資料庫用 SQLite 的線上備份功能複製,服務執行中也能得到一致的快照
  3. 打包、壓縮、用公鑰加密
  4. 推送到 GitHub 私人倉庫,只保留最近 14 份;每次只留一個 commit,避免倉庫越來越大
  5. 失敗時傳 Telegram 通知

上傳用的是只能存取這一個倉庫的部署金鑰(Deploy key),不是整個 GitHub 帳號的權限。

一定要做還原測試
#

備份做好後,我實際做了一次完整的還原:從 GitHub 下載、用私鑰解密、解開,再檢查資料庫的完整性和資料筆數。沒有還原過的備份,不能算是備份。確認可以還原之後,才把 VPS 上的私鑰刪掉。

三、SSH 只走 Tailscale
#

為什麼
#

SSH 只用金鑰登入已經很難被攻破,但只要 22 port 對外開放,全世界的掃描機器人就會不停來敲門。我看了一下紀錄,短短 15 分鐘就有十幾個不同的 IP 在嘗試登入。它們雖然進不來,但每一次嘗試都是風險,也會把紀錄塞滿。

Tailscale 會把自己的電腦、手機、VPS 組成一個私人網路,每台裝置會拿到一個 100.x.x.x 的私人位址。把 SSH 限制成只能從這個私人網路連進來,公開網路上就看不到 SSH 了。個人使用免費。

順序很重要:先確認新路通,再關舊門
#

  1. 電腦、手機安裝 Tailscale,用同一個帳號登入
  2. VPS 安裝 Tailscale,打開它給的登入連結,核准加入
  3. 關閉 VPS 的金鑰到期(Machines 頁面該機器的 ⋯ → Disable key expiry)。Tailscale 預設每 180 天要重新登入一次,伺服器到期後會掉出私人網路,SSH 又已經只走私人網路,就會把自己鎖在門外。
  4. 用新的私人位址實際 SSH 連一次,確認可以登入
  5. 確認可以之後,才修改防火牆:
sudo ufw allow in on tailscale0 to any port 22 proto tcp   # 只允許從 Tailscale 連 SSH
sudo ufw allow 41641/udp                                   # Tailscale 直連用,速度較快
sudo ufw delete allow 22/tcp                               # 刪掉原本對外開放的 SSH
  1. 再測一次:用私人位址可以連上,用公開 IP 則會連線逾時

改完之後
#

  • 平常 SSH 改用 ssh <帳號>@<Tailscale 私人位址>,連線前要確認裝置上的 Tailscale 顯示已連線。
  • 網站不受影響,因為走的是 80/443 port。
  • 萬一連不上,先檢查 Tailscale 有沒有開;真的進不去時,還有 VPS 業者網頁上的主控台可以用。

小結
#

三件事分別補上不同的缺口:

  • Email 驗證碼:管「誰能看到私人網頁」
  • 加密備份:管「主機壞了怎麼辦」
  • Tailscale:管「誰能敲 SSH 的門」

共同的原則是:

  • 先確認新的可以用,再關掉舊的:先測通私人位址再關 22 port、先做完還原測試再刪私鑰。
  • 鑰匙不要集中在一個地方:密鑰不進備份、解密私鑰不留在主機。
  • 最源頭的帳號要最安全:Cloudflare、Google、GitHub、Tailscale 帳號都是這些防線的鑰匙,一定要開兩步驟驗證。
自架 VPS 筆記 - 本文屬於一個選集。
§ 4: 本文

相關文章