Redis 如果不設密碼就放上公網,大概不用多久就會被掃到,接著可能被拿去挖礦或塞奇怪的東西。所以密碼驗證(AUTH)這層不能省。
另外,為了方便運維查看現在有誰連著,還得加個連線管理(像是 CLIENT LIST, CLIENT KILL)。
今天就來幫這個專案補上基本的安全防線。
password),則除了 AUTH 命令之外,其他所有接收到的命令都應被拒絕執行,並回傳 (error) NOAUTH Authentication required. 錯誤。AUTH <password>。伺服器比對正確後,將該連線的上下文 Client.Authenticated 標記為 true,隨後客戶端才能正常操作。我把這個攔截放在 code/command/dispatcher.go 的核心分發邏輯裡:
實作的時候我踩了個坑,一開始把驗證邏輯寫在指令處理之後,結果發現密碼錯了指令還是跑完了,趕緊把它搬到最前面去攔截。
func (d *Dispatcher) Dispatch(client *Client, val resp.Value) resp.Value {
// ... 解析指令名與引數 ...
// 1. 密碼驗證校驗 (AUTH)
if cmdName == "AUTH" {
if d.requirePass == "" {
return resp.NewError("ERR Client sent AUTH, but no password is set")
}
if len(args) != 1 {
return resp.NewError("ERR wrong number of arguments for 'auth' command")
}
// 比對密碼
if string(args[0]) == d.requirePass {
client.Authenticated = true // 標記驗證成功
return resp.NewSimpleString("OK")
}
return resp.NewError("ERR invalid password")
}
// 2. 若未通過驗證,拒絕其他所有指令
if d.requirePass != "" && !client.Authenticated {
return resp.NewError("NOAUTH Authentication required.")
}
// ... 正常執行其他命令 ...
}
連線管理命令主要是讓我們看得到目前有哪些 client 還連著:
CLIENT LIST:回傳當前所有活躍客戶端連線的詳細資訊(如 IP、埠口、訂閱頻道數、驗證狀態等)。CLIENT KILL <ip:port>:主動中斷指定的客戶端連線,釋放資源。這類指令會碰到 server 連線狀態,所以我透過全域 ActiveServer 取得目前活躍連線,再做查詢或中斷:
// 於 commands.go 或 server 中實作客戶端連線查詢:
func clientListCommand(dbEngine *db.DB, client *Client, args [][]byte) resp.Value {
// 利用 ActiveServer.conns 遍歷連線
// 將每個連線的 RemoteAddr() 等metadata格式化為字串 Bulk 回傳
}
這種功能平常不一定會用到,但 debug 連線問題時會很方便。
來看看我們的程式在壓測時,到底是哪邊在拖慢速度:
# 1. 開啟 pprof 收集,執行壓測
$ go test -cpuprofile cpu.prof -bench .
# 2. 進入 pprof 互動模式
$ go tool pprof cpu.prof
> top 10
# 預期回覆:
# Showing nodes accounting for 1.20s, 85.10% of 1.41s total
# flat flat% sum% cum cum%
# 0.30s 21.28% 21.28% 0.35s 24.82% syscall.syscall
# 0.25s 17.73% 39.01% 0.40s 28.37% runtime.mallocgc
# 0.15s 10.64% 49.65% 0.15s 10.64% sync.(*RWMutex).Lock
# ...
如果鎖競爭和系統呼叫佔比很高,後面就可以從分段鎖、批次寫回這些方向下手。
今天補上 AUTH 和 CLIENT 指令,至少不再是完全裸奔的服務。
明天就是最後一天了,打算補一些系統監控指令,再用 benchmark 壓一下,看看這 30 天寫出來的東西到底能跑多快。