chore: track version files, docs, web app split, scripts (missed commits from March work)

This commit is contained in:
2026-09-13 08:41:35 +08:00
parent 84c709bbcc
commit edf76db56b
33 changed files with 3082 additions and 504 deletions
+2 -1
View File
@@ -37,7 +37,8 @@ func (s *Server) HandleAdminSettings(w http.ResponseWriter, r *http.Request) {
}
// allowlist
switch req.Key {
case "advanced_impersonate", "advanced_force_network", "advanced_cross_tenant":
case "advanced_impersonate", "advanced_force_network", "advanced_cross_tenant",
"advanced_override_enabled", "allow_apikey_mutate_sdwan", "allow_operator_kick_cross_node", "allow_connect_cross_tenant":
default:
writeJSON(w, http.StatusBadRequest, `{"error":1,"message":"invalid key"}`)
return
+22 -2
View File
@@ -6,7 +6,7 @@ import (
"strconv"
)
// GET /api/v1/admin/audit?tenant=3&limit=50&offset=0
// GET /api/v1/admin/audit?tenant=3&limit=50&offset=0&action=node_kick&actor_type=session&actor_id=12&target_type=node&target_id=nodeA&from=1700000000&to=1700003600
func (s *Server) HandleAdminAudit(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
@@ -30,7 +30,27 @@ func (s *Server) HandleAdminAudit(w http.ResponseWriter, r *http.Request) {
tenantID = i
}
}
logs, err := s.store.ListAuditLogs(tenantID, limit, offset)
action := r.URL.Query().Get("action")
actorType := r.URL.Query().Get("actor_type")
actorID := r.URL.Query().Get("actor_id")
targetType := r.URL.Query().Get("target_type")
targetID := r.URL.Query().Get("target_id")
fromTs := int64(0)
toTs := int64(0)
if v := r.URL.Query().Get("from"); v != "" {
if i, err := strconv.ParseInt(v, 10, 64); err == nil && i > 0 {
fromTs = i
}
}
if v := r.URL.Query().Get("to"); v != "" {
if i, err := strconv.ParseInt(v, 10, 64); err == nil && i > 0 {
toTs = i
}
}
logs, err := s.store.ListAuditLogs(tenantID, limit, offset, action, actorType, actorID, targetType, targetID, fromTs, toTs)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
+64
View File
@@ -3,6 +3,7 @@ package server
import (
"net/http"
"strconv"
"strings"
)
type AccessContext struct {
@@ -63,3 +64,66 @@ func (s *Server) ResolveTenantAccessToken(tok string) (*AccessContext, bool) {
return nil, false
}
// TenantCan implements action-level RBAC for tenant-scoped APIs.
func TenantCan(ac *AccessContext, action string) bool {
if ac == nil {
return false
}
switch action {
case "view":
return (ac.Kind == "session" && (ac.Role == "admin" || ac.Role == "operator")) || ac.Kind == "apikey"
case "mutate_node_meta", "mutate_sdwan", "mutate_connect", "mutate_node_control":
return ac.Kind == "session" && (ac.Role == "admin" || ac.Role == "operator")
case "enroll_manage":
return ac.Kind == "session" && ac.Role == "admin"
default:
return false
}
}
func settingOn(v string) bool {
v = strings.TrimSpace(strings.ToLower(v))
return v == "1" || v == "true" || v == "on" || v == "yes"
}
// TenantCanWithOverride evaluates baseline RBAC then optional advanced override switches.
// Return values: allowed, usedOverride
func (s *Server) TenantCanWithOverride(ac *AccessContext, action string) (bool, bool) {
if TenantCan(ac, action) {
return true, false
}
if ac == nil || s == nil || s.store == nil || ac.TenantID <= 0 {
return false, false
}
if ac.Kind == "master" {
return false, false
}
// global override gate
if v, _, err := s.store.GetSetting("advanced_override_enabled"); err != nil || !settingOn(v) {
return false, false
}
switch action {
case "mutate_sdwan":
if ac.Kind == "apikey" {
if v, _, err := s.store.GetSetting("allow_apikey_mutate_sdwan"); err == nil && settingOn(v) {
return true, true
}
}
case "mutate_connect":
if ac.Kind == "session" && ac.Role == "operator" {
if v, _, err := s.store.GetSetting("allow_connect_cross_tenant"); err == nil && settingOn(v) {
return true, true
}
}
case "mutate_node_control":
if ac.Kind == "session" && ac.Role == "operator" {
if v, _, err := s.store.GetSetting("allow_operator_kick_cross_node"); err == nil && settingOn(v) {
return true, true
}
}
}
return false, false
}
Binary file not shown.
Binary file not shown.
+17
View File
@@ -2,6 +2,7 @@ package server
import (
"encoding/json"
"fmt"
"net/http"
"strings"
@@ -22,6 +23,10 @@ func (s *Server) HandleNodeMeta(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusUnauthorized, `{"error":1,"message":"unauthorized"}`)
return
}
if !TenantCan(ac, "mutate_node_meta") {
writeJSON(w, http.StatusForbidden, `{"error":1,"message":"forbidden"}`)
return
}
if strings.HasSuffix(r.URL.Path, "/alias") {
var req struct {
@@ -36,6 +41,12 @@ func (s *Server) HandleNodeMeta(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusBadRequest, `{"error":1,"message":"`+err.Error()+`"}`)
return
}
actorID := "0"
if ac != nil {
actorID = fmt.Sprintf("%d", ac.UserID)
}
detail := fmt.Sprintf("tenant=%d node_uuid=%s alias=%s", ac.TenantID, req.NodeUUID, req.Alias)
_ = s.store.AddAuditLog(ac.Kind, actorID, "node_alias_update", "node", req.NodeUUID, detail, r.RemoteAddr)
writeJSON(w, http.StatusOK, `{"error":0,"message":"ok"}`)
return
}
@@ -53,6 +64,12 @@ func (s *Server) HandleNodeMeta(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusBadRequest, `{"error":1,"message":"`+err.Error()+`"}`)
return
}
actorID := "0"
if ac != nil {
actorID = fmt.Sprintf("%d", ac.UserID)
}
detail := fmt.Sprintf("tenant=%d node_uuid=%s virtual_ip=%s", ac.TenantID, req.NodeUUID, req.VirtualIP)
_ = s.store.AddAuditLog(ac.Kind, actorID, "node_ip_update", "node", req.NodeUUID, detail, r.RemoteAddr)
nodes := s.GetOnlineNodesByTenant(ac.TenantID)
affectedNode := ""
+35 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"log"
"net/netip"
"strings"
"github.com/openp2p-cn/inp2p/pkg/protocol"
)
@@ -35,17 +36,50 @@ func (s *Server) SetSDWANTenant(tenantID int64, cfg protocol.SDWANConfig, actorT
return errors.New("hub node must be online and relay-enabled")
}
}
cfg = s.rewriteTenantSDWAN(tenantID, cfg)
if err := s.sdwan.saveTenant(tenantID, cfg); err != nil {
return err
}
if actorType != "" && s.store != nil {
detail := fmt.Sprintf("mode=%s hub=%s nodes=%d subnetProxies=%d", cfg.Mode, cfg.HubNode, len(cfg.Nodes), len(cfg.SubnetProxies))
detail := fmt.Sprintf("mode=%s hub=%s nodes=%d subnetProxies=%d gateway=%s", cfg.Mode, cfg.HubNode, len(cfg.Nodes), len(cfg.SubnetProxies), cfg.GatewayCIDR)
_ = s.store.AddAuditLog(actorType, actorID, "sdwan_update", "tenant", fmt.Sprintf("%d", tenantID), detail, ip)
}
s.broadcastSDWANTenant(tenantID, s.sdwan.getTenant(tenantID))
return nil
}
func (s *Server) rewriteTenantSDWAN(tenantID int64, cfg protocol.SDWANConfig) protocol.SDWANConfig {
if s.store != nil {
if ten, err := s.store.GetTenantByID(tenantID); err == nil && ten != nil && ten.Subnet != "" {
cfg.GatewayCIDR = ten.Subnet
// keep routes aligned with subnet
found := false
for _, r := range cfg.Routes {
if strings.TrimSpace(r) == ten.Subnet {
found = true
break
}
}
if !found {
cfg.Routes = []string{ten.Subnet}
}
}
}
return cfg
}
func (s *Server) ReconcileTenantSDWAN(tenantID int64) {
cfg := s.sdwan.getTenant(tenantID)
cfg = s.rewriteTenantSDWAN(tenantID, cfg)
if err := s.sdwan.saveTenant(tenantID, cfg); err != nil {
log.Printf("[sdwan] reconcile tenant=%d failed: %v", tenantID, err)
return
}
s.broadcastSDWANTenant(tenantID, cfg)
}
func (s *Server) broadcastSDWAN(cfg protocol.SDWANConfig) {
if !cfg.Enabled || cfg.GatewayCIDR == "" {
return
+1 -1
View File
@@ -262,7 +262,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
// Verify token: master token OR tenant API key (DB) OR node_secret (DB)
valid := s.tokens[loginReq.Token]
log.Printf("[server] login check: token=%d, cfg.Token=%d, valid=%v", loginReq.Token, s.cfg.Token, valid)
log.Printf("[server] login check: node=%s valid=%v", loginReq.Node, valid)
var tenantID int64
if !valid && s.store != nil {
// try api key (string) or node secret
+2 -1
View File
@@ -170,7 +170,8 @@ func (s *Server) HandleAdminUsers(w http.ResponseWriter, r *http.Request) {
return
}
if ac := GetAccessContext(r); ac != nil {
_ = s.store.AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "user_create", "tenant", fmt.Sprintf("%d", req.TenantID), req.Email, r.RemoteAddr)
detail := fmt.Sprintf("username=%s role=%s", req.Email, req.Role)
_ = s.store.AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "user_create", "tenant", fmt.Sprintf("%d", req.TenantID), detail, r.RemoteAddr)
}
writeJSON(w, http.StatusOK, `{"error":0,"message":"ok"}`)
}