forked from kevin/meshtastic_mqtt_server
安全加固:MQTT broker 新增可选连接认证(bcrypt 用户+匿名开关+按 IP 失败限速,配置明文密码首启自动转哈希,默认关闭零影响),py 迁移脚本数据库口令改环境变量(新增 db_config.example.py 模板),新增 doc/SECURITY_FIX_TODO.md 安全修复清单,后端 v1.3.0
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package mqttauth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
paho "github.com/eclipse/paho.mqtt.golang"
|
||||
mqtt "github.com/mochi-mqtt/server/v2"
|
||||
"github.com/mochi-mqtt/server/v2/listeners"
|
||||
)
|
||||
|
||||
// TestBrokerIntegration 用真实 TCP broker + paho 客户端验证认证全链路。
|
||||
func TestBrokerIntegration(t *testing.T) {
|
||||
hook := NewHook(Config{
|
||||
Enabled: true,
|
||||
Users: []User{{Username: "mesh", PasswordHash: hash(t, "secret")}},
|
||||
MaxFailures: 3,
|
||||
})
|
||||
t.Cleanup(func() { _ = hook.Stop() })
|
||||
|
||||
server := mqtt.New(&mqtt.Options{InlineClient: true})
|
||||
if err := server.AddHook(hook, nil); err != nil {
|
||||
t.Fatalf("add hook: %v", err)
|
||||
}
|
||||
addr := "127.0.0.1:18883"
|
||||
if err := server.AddListener(listeners.NewTCP(listeners.Config{ID: "t", Address: addr})); err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
if err := server.Serve(); err != nil {
|
||||
t.Fatalf("serve: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = server.Close() })
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
connect := func(user, pass string) bool {
|
||||
opts := paho.NewClientOptions().
|
||||
AddBroker("tcp://" + addr).
|
||||
SetClientID("smoke-" + user + time.Now().Format("150405.000")).
|
||||
SetUsername(user).SetPassword(pass).
|
||||
SetConnectTimeout(3 * time.Second)
|
||||
client := paho.NewClient(opts)
|
||||
token := client.Connect()
|
||||
token.WaitTimeout(5 * time.Second)
|
||||
ok := token.Error() == nil
|
||||
if ok {
|
||||
client.Disconnect(50)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
if !connect("mesh", "secret") {
|
||||
t.Error("valid credentials should connect")
|
||||
}
|
||||
if connect("mesh", "wrong") {
|
||||
t.Error("wrong password should be rejected")
|
||||
}
|
||||
if connect("", "") {
|
||||
t.Error("anonymous should be rejected when disabled")
|
||||
}
|
||||
|
||||
// 连续失败触发封禁后,正确凭据也应被拒绝(默认阈值 3,本例 MaxFailures=3)。
|
||||
// 前面已失败 2 次(mesh/wrong 与匿名),再失败 1 次触发封禁。
|
||||
if connect("mesh", "wrong") {
|
||||
t.Fatal("wrong password should be rejected")
|
||||
}
|
||||
if connect("mesh", "secret") {
|
||||
t.Error("blocked ip must reject even valid credentials")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user