chore: track version files, docs, web app split, scripts (missed commits from March work)
This commit is contained in:
+218
-14
@@ -215,6 +215,13 @@ func main() {
|
||||
fmt.Fprintf(w, `{"error":401,"message":"unauthorized"}`)
|
||||
return
|
||||
}
|
||||
// only tenant session/apikey can pass
|
||||
if ac.TenantID <= 0 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintf(w, `{"error":401,"message":"unauthorized"}`)
|
||||
return
|
||||
}
|
||||
r = r.WithContext(context.WithValue(r.Context(), server.ServerCtxKeyAccess{}, ac))
|
||||
next(w, r)
|
||||
}
|
||||
@@ -269,7 +276,14 @@ func main() {
|
||||
|
||||
// Serve Static Web Console
|
||||
webDir := "/root/.openclaw/workspace/inp2p/web"
|
||||
mux.Handle("/", http.FileServer(http.Dir(webDir)))
|
||||
fileServer := http.FileServer(http.Dir(webDir))
|
||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// 防止前端资源缓存导致“登录后瞬间白屏”(新旧 JS 混用)
|
||||
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
fileServer.ServeHTTP(w, r)
|
||||
}))
|
||||
|
||||
// Tenant APIs (API key auth inside handlers)
|
||||
mux.HandleFunc("/api/v1/admin/tenants", adminMiddleware(srv.HandleAdminCreateTenant))
|
||||
@@ -336,6 +350,7 @@ func main() {
|
||||
}
|
||||
u, err := srv.Store().VerifyUserPasswordGlobal(reqUser.Username, reqUser.Password)
|
||||
if err != nil || u == nil || u.Status != 1 {
|
||||
_ = srv.Store().AddAuditLog("auth", reqUser.Username, "auth_login_failed", "user", reqUser.Username, "invalid_credentials", r.RemoteAddr)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintf(w, `{"error":1,"message":"invalid credentials"}`)
|
||||
@@ -348,6 +363,7 @@ func main() {
|
||||
fmt.Fprintf(w, `{"error":1,"message":"create session failed"}`)
|
||||
return
|
||||
}
|
||||
_ = srv.Store().AddAuditLog("session", fmt.Sprintf("%d", u.ID), "auth_login_success", "tenant", fmt.Sprintf("%d", u.TenantID), fmt.Sprintf("role=%s", u.Role), r.RemoteAddr)
|
||||
ten, _ := srv.Store().GetTenantByID(u.TenantID)
|
||||
resp := struct {
|
||||
Error int `json:"error"`
|
||||
@@ -380,15 +396,108 @@ func main() {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
|
||||
if tenantID > 0 {
|
||||
nodes := srv.GetOnlineNodesByTenant(tenantID)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"nodes": listNodesOut(nodes, tenantID)})
|
||||
online := srv.GetOnlineNodesByTenant(tenantID)
|
||||
onlineOut := listNodesOut(online, tenantID)
|
||||
byName := map[string]map[string]any{}
|
||||
for _, it := range onlineOut {
|
||||
name, _ := it["name"].(string)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
it["online"] = true
|
||||
it["status"] = "online"
|
||||
byName[name] = it
|
||||
}
|
||||
if srv.Store() != nil {
|
||||
if creds, err := srv.Store().ListNodeCredentialsByTenant(tenantID); err == nil {
|
||||
for _, nc := range creds {
|
||||
if _, ok := byName[nc.NodeName]; ok {
|
||||
continue
|
||||
}
|
||||
item := map[string]any{
|
||||
"name": nc.NodeName,
|
||||
"displayName": nc.NodeName,
|
||||
"publicIP": "",
|
||||
"publicPort": 0,
|
||||
"natType": "unknown",
|
||||
"tenantId": nc.TenantID,
|
||||
"version": "",
|
||||
"relayEnabled": false,
|
||||
"superRelay": false,
|
||||
"loginTime": time.Time{},
|
||||
"lastHeartbeat": time.Time{},
|
||||
"nodeUUID": nc.NodeUUID,
|
||||
"alias": nc.Alias,
|
||||
"virtualIP": nc.VirtualIP,
|
||||
"online": false,
|
||||
"status": "offline",
|
||||
}
|
||||
if nc.Alias != "" {
|
||||
item["displayName"] = nc.Alias
|
||||
}
|
||||
byName[nc.NodeName] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
merged := make([]map[string]any, 0, len(byName))
|
||||
for _, v := range byName {
|
||||
merged = append(merged, v)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"nodes": merged})
|
||||
return
|
||||
}
|
||||
nodes := srv.GetOnlineNodes()
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"nodes": listNodesOut(nodes, 0)})
|
||||
|
||||
online := srv.GetOnlineNodes()
|
||||
onlineOut := listNodesOut(online, 0)
|
||||
byName := map[string]map[string]any{}
|
||||
for _, it := range onlineOut {
|
||||
name, _ := it["name"].(string)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
it["online"] = true
|
||||
it["status"] = "online"
|
||||
byName[name] = it
|
||||
}
|
||||
if srv.Store() != nil {
|
||||
if creds, err := srv.Store().ListNodeCredentialsAll(); err == nil {
|
||||
for _, nc := range creds {
|
||||
if _, ok := byName[nc.NodeName]; ok {
|
||||
continue
|
||||
}
|
||||
item := map[string]any{
|
||||
"name": nc.NodeName,
|
||||
"displayName": nc.NodeName,
|
||||
"publicIP": "",
|
||||
"publicPort": 0,
|
||||
"natType": "unknown",
|
||||
"tenantId": nc.TenantID,
|
||||
"version": "",
|
||||
"relayEnabled": false,
|
||||
"superRelay": false,
|
||||
"loginTime": time.Time{},
|
||||
"lastHeartbeat": time.Time{},
|
||||
"nodeUUID": nc.NodeUUID,
|
||||
"alias": nc.Alias,
|
||||
"virtualIP": nc.VirtualIP,
|
||||
"online": false,
|
||||
"status": "offline",
|
||||
}
|
||||
if nc.Alias != "" {
|
||||
item["displayName"] = nc.Alias
|
||||
}
|
||||
byName[nc.NodeName] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
merged := make([]map[string]any, 0, len(byName))
|
||||
for _, v := range byName {
|
||||
merged = append(merged, v)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"nodes": merged})
|
||||
}))
|
||||
|
||||
mux.HandleFunc("/api/v1/sdwans", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -443,11 +552,32 @@ func main() {
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 {
|
||||
ac := server.GetAccessContext(r)
|
||||
allowed, usedOverride := srv.TenantCanWithOverride(ac, "mutate_sdwan")
|
||||
if !allowed {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if usedOverride && ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("action=mutate_sdwan tenant=%d", tenantID)
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "rbac_override_used", "tenant", fmt.Sprintf("%d", tenantID), detail, r.RemoteAddr)
|
||||
}
|
||||
actorType, actorID := "", ""
|
||||
if ac != nil {
|
||||
actorType = ac.Kind
|
||||
actorID = fmt.Sprintf("%d", ac.UserID)
|
||||
}
|
||||
|
||||
// lock tenant gatewayCIDR to assigned subnet (tenant cannot modify)
|
||||
if srv.Store() != nil {
|
||||
ten, terr := srv.Store().GetTenantByID(tenantID)
|
||||
if terr == nil && ten != nil && ten.Subnet != "" {
|
||||
req.GatewayCIDR = ten.Subnet
|
||||
if len(req.Routes) == 0 {
|
||||
req.Routes = []string{ten.Subnet}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := srv.SetSDWANTenant(tenantID, req, actorType, actorID, r.RemoteAddr); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -478,19 +608,35 @@ func main() {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 {
|
||||
ac := server.GetAccessContext(r)
|
||||
allowed, usedOverride := srv.TenantCanWithOverride(ac, "mutate_node_control")
|
||||
if !allowed {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if usedOverride && ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("action=mutate_node_control tenant=%d", tenantID)
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "rbac_override_used", "tenant", fmt.Sprintf("%d", tenantID), detail, r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
node := srv.GetNode(req.Node)
|
||||
if node == nil {
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 && node.TenantID != tenantID {
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
// Push to client
|
||||
_ = node.Conn.Write(protocol.MsgPush, protocol.SubPushConfig, req.Apps)
|
||||
if ac := server.GetAccessContext(r); ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("node=%s apps=%d", req.Node, len(req.Apps))
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "node_apps_push", "node", req.Node, detail, r.RemoteAddr)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "config push sent"})
|
||||
}))
|
||||
@@ -508,18 +654,28 @@ func main() {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
ac := server.GetAccessContext(r)
|
||||
if tenantID > 0 {
|
||||
if !server.TenantCan(ac, "mutate_node_control") {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
node := srv.GetNode(req.Node)
|
||||
if node == nil {
|
||||
http.Error(w, "node not found or offline", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 && node.TenantID != tenantID {
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
node.Conn.Close()
|
||||
if ac != nil && srv.Store() != nil {
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "node_kick", "node", req.Node, "", r.RemoteAddr)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "node kicked"})
|
||||
}))
|
||||
@@ -541,14 +697,34 @@ func main() {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
ac := server.GetAccessContext(r)
|
||||
if tenantID > 0 {
|
||||
allowed, usedOverride := srv.TenantCanWithOverride(ac, "mutate_connect")
|
||||
if !allowed {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if usedOverride && ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("action=mutate_connect tenant=%d", tenantID)
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "rbac_override_used", "tenant", fmt.Sprintf("%d", tenantID), detail, r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
fromNode := srv.GetNode(req.From)
|
||||
if fromNode == nil {
|
||||
if ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("from=%s to=%s app=%s src=%d dst=%d reason=source_offline", req.From, req.To, req.AppName, req.SrcPort, req.DstPort)
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "connect_request_denied", "tenant", fmt.Sprintf("%d", tenantID), detail, r.RemoteAddr)
|
||||
}
|
||||
http.Error(w, "source node offline", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 && fromNode.TenantID != tenantID {
|
||||
if ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("from=%s to=%s app=%s src=%d dst=%d reason=tenant_mismatch_source", req.From, req.To, req.AppName, req.SrcPort, req.DstPort)
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "connect_request_denied", "tenant", fmt.Sprintf("%d", tenantID), detail, r.RemoteAddr)
|
||||
}
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
@@ -561,13 +737,41 @@ func main() {
|
||||
DstPort: req.DstPort,
|
||||
Enabled: 1,
|
||||
}
|
||||
// enforce same-tenant target
|
||||
// enforce same-tenant target by default; can be relaxed only with explicit advanced switch
|
||||
if tenantID > 0 {
|
||||
allowCrossTenant := false
|
||||
if ac != nil && ac.Kind == "session" && ac.Role == "operator" {
|
||||
if v, _, err := srv.Store().GetSetting("advanced_override_enabled"); err == nil && (v == "1" || v == "true") {
|
||||
if v2, _, err2 := srv.Store().GetSetting("allow_connect_cross_tenant"); err2 == nil && (v2 == "1" || v2 == "true") {
|
||||
allowCrossTenant = true
|
||||
}
|
||||
}
|
||||
}
|
||||
toNode := srv.GetNode(req.To)
|
||||
if toNode == nil || toNode.TenantID != tenantID {
|
||||
if toNode == nil {
|
||||
if ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("from=%s to=%s app=%s src=%d dst=%d reason=target_offline", req.From, req.To, req.AppName, req.SrcPort, req.DstPort)
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "connect_request_denied", "tenant", fmt.Sprintf("%d", tenantID), detail, r.RemoteAddr)
|
||||
}
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if !allowCrossTenant && toNode.TenantID != tenantID {
|
||||
if ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("from=%s to=%s app=%s src=%d dst=%d reason=tenant_mismatch_target", req.From, req.To, req.AppName, req.SrcPort, req.DstPort)
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "connect_request_denied", "tenant", fmt.Sprintf("%d", tenantID), detail, r.RemoteAddr)
|
||||
}
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if allowCrossTenant && ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("action=connect_cross_tenant tenant=%d from=%s to=%s", tenantID, req.From, req.To)
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "rbac_override_used", "tenant", fmt.Sprintf("%d", tenantID), detail, r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
if ac := server.GetAccessContext(r); ac != nil && srv.Store() != nil {
|
||||
detail := fmt.Sprintf("from=%s to=%s app=%s src=%d dst=%d", req.From, req.To, req.AppName, req.SrcPort, req.DstPort)
|
||||
_ = srv.Store().AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "connect_request", "tenant", fmt.Sprintf("%d", tenantID), detail, r.RemoteAddr)
|
||||
}
|
||||
if err := srv.PushConnect(fromNode, req.To, app); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
Reference in New Issue
Block a user