feat(backend): 挂载 /v1 API + 实现 nodes/connect 端到端
- 新增 internal/dpcred 包,统一 DeriveHy2Password + DefaultFlow agentd 与 HTTP connect handler 共享同一实现 - 新增迁移 000011:nodes 表拆分 reality_prk 私钥 / reality_pbk 公钥 reality_short_id;修正 handler_grpc.go 使用私钥字段 - 新增迁移 000012:connect_credentials 持久化凭证 实现 CredentialsForNode 修复 agent 重连 resync 原先返回空的桩 - 扩展 NodeStore 接口:ListUp / EntitlementForUser / PersistCredential / DeleteCredential;同步 grpc_test.go mock - 新增 httpapi/nodes.go:GET /nodes、POST /nodes/id/connect Hub.Push + PersistCredential + 渲染完整 sing-box client 配置 JSON POST /nodes/id/disconnect - 新增 httpapi/account.go:GET /me、GET /plans、GET /notices - 新增 httpapi/clientconfig.go:BuildClientConfig 服务端渲染 - 重写 cmd/server/main.go:手写 chi public/protected 分组 nodes.Service/Hub 在 main 构造并共享;SMTPMailer/LogMailer go build ./... && go vet ./... && go test ./... 全部通过 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ const (
|
||||
DefaultSingboxCfg = "/etc/sing-box/config.json"
|
||||
|
||||
// DefaultFlow is the REALITY VLESS flow (doc/02 §3.1).
|
||||
// Kept here as an alias; canonical definition is in internal/dpcred.
|
||||
DefaultFlow = "xtls-rprx-vision"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,34 +1,11 @@
|
||||
package agentd
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
)
|
||||
import "github.com/wangjia/pangolin/server/internal/dpcred"
|
||||
|
||||
// DeriveHy2Password derives a node-agnostic Hysteria2 password from the opaque
|
||||
// data-plane credential id (dp_uuid). The REALITY inbound uses dp_uuid directly as
|
||||
// the VLESS user uuid; the Hy2 inbound cannot reuse a UUID as a password verbatim
|
||||
// (it must look like an opaque secret), so it is derived from the SAME source —
|
||||
// "password = dp_uuid 同源派生" — via a keyed HMAC.
|
||||
//
|
||||
// The derivation is deterministic given (dp_uuid, key): the control plane runs the
|
||||
// exact same function when it builds the client's connect config (doc/02 §3.1), so
|
||||
// both sides agree without the password ever crossing the agent contract.
|
||||
//
|
||||
// Risk note (carried from task #5): the derivation key is shared material. If a
|
||||
// node is seized the attacker still only sees dp_uuids and this node's key, which
|
||||
// lets them recompute Hy2 passwords for dp_uuids THEY ALREADY HOLD — it does not
|
||||
// reveal other subscribers' credentials or any account identity. Rotating the key
|
||||
// rotates every Hy2 password. When key == "" the password falls back to the raw
|
||||
// dp_uuid (acceptable for dev; production cloud-init always injects a key).
|
||||
// DeriveHy2Password is an alias for dpcred.DeriveHy2Password kept here so
|
||||
// existing callers within the agentd package compile without change.
|
||||
// The canonical implementation lives in internal/dpcred so that both the node
|
||||
// agent and the HTTP connect handler share the exact same derivation logic.
|
||||
func DeriveHy2Password(dpUUID, key string) string {
|
||||
if key == "" {
|
||||
return dpUUID
|
||||
}
|
||||
mac := hmac.New(sha256.New, []byte(key))
|
||||
mac.Write([]byte(dpUUID))
|
||||
sum := mac.Sum(nil)
|
||||
// base64url without padding → URL/JSON-safe, 43 chars.
|
||||
return base64.RawURLEncoding.EncodeToString(sum)
|
||||
return dpcred.DeriveHy2Password(dpUUID, key)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user