Sync OpenVPN OAuth gateway routes on tunnel changes

This commit is contained in:
opsx committed 2026-10-22 19:50:24 +08:00
1 parent 9e26358df0
commit ff2db503ab
6 files changed
+106 -9

No files matched your search

+32 -3
View File
@@ -38,9 +38,10 @@ import (
// Server API 服务器
type Server struct {
mgr *core.Manager
taskMgr *taskmgr.Manager
router *gin.Engine
mgr *core.Manager
taskMgr *taskmgr.Manager
router *gin.Engine
gatewayMu sync.Mutex
}
func NewServer(mgr *core.Manager) *Server { return &Server{mgr: mgr, taskMgr: taskmgr.NewManager()} }
@@ -887,6 +888,12 @@ func (s *Server) createTunnel(c *gin.Context) {
t.State = "running"
}
store.DB.Save(&t)
if t.State == "running" {
if err := s.syncAuthGatewayForTunnel(&t); err != nil {
c.Header("X-Opsx-Gateway-Warning", "route-sync-failed")
log.Printf("专线 %d 已启动但认证入口同步失败: %v", t.ID, err)
}
}
}
c.JSON(200, t)
}
@@ -1007,6 +1014,12 @@ func (s *Server) updateTunnel(c *gin.Context) {
}
}
*t = candidate
if wasRunning {
if err := s.syncAuthGatewayForTunnel(t); err != nil {
c.Header("X-Opsx-Gateway-Warning", "route-sync-failed")
log.Printf("专线 %d 已更新但认证入口同步失败: %v", t.ID, err)
}
}
store.Audit(c.GetString("username"), "update_tunnel", t.Name, "")
c.JSON(200, t)
}
@@ -1027,6 +1040,10 @@ func (s *Server) deleteTunnel(c *gin.Context) {
}
s.mgr.RemoveStatus(t.ID)
store.DB.Delete(t)
if err := s.syncAuthGatewayForTunnel(t); err != nil {
c.Header("X-Opsx-Gateway-Warning", "route-sync-failed")
log.Printf("专线 %d 已删除但认证入口同步失败: %v", t.ID, err)
}
store.Audit(c.GetString("username"), "delete_tunnel", t.Name, "")
c.JSON(200, gin.H{"ok": true})
}
@@ -1048,6 +1065,10 @@ func (s *Server) startTunnel(c *gin.Context) {
return
}
if t.State == "running" {
if err := s.syncAuthGatewayForTunnel(t); err != nil {
c.JSON(200, gin.H{"ok": true, "state": "running", "warning": "专线运行中,但公网认证入口同步失败:" + err.Error()})
return
}
c.JSON(200, gin.H{"ok": true, "state": "running", "message": "专线已在运行"})
return
}
@@ -1064,6 +1085,10 @@ func (s *Server) startTunnel(c *gin.Context) {
store.DB.Save(t)
s.mgr.RemoveStatus(t.ID) // the monitor's previous snapshot can lag behind this transition
store.Audit(c.GetString("username"), "start_tunnel", t.Name, "")
if err := s.syncAuthGatewayForTunnel(t); err != nil {
c.JSON(200, gin.H{"ok": true, "state": "running", "warning": "专线已启动,但公网认证入口同步失败:" + err.Error()})
return
}
c.JSON(200, gin.H{"ok": true, "state": "running"})
}
@@ -1087,6 +1112,10 @@ func (s *Server) stopTunnel(c *gin.Context) {
store.DB.Save(t)
s.mgr.RemoveStatus(t.ID)
store.Audit(c.GetString("username"), "stop_tunnel", t.Name, "")
if err := s.syncAuthGatewayForTunnel(t); err != nil {
c.JSON(200, gin.H{"ok": true, "state": "stopped", "warning": "专线已停止,但公网认证入口同步失败:" + err.Error()})
return
}
c.JSON(200, gin.H{"ok": true, "state": "stopped"})
}
+14 -5
View File
@@ -121,6 +121,9 @@ func (s *Server) executeBatchAction(action taskmgr.TaskAction, tunnelID uint, us
t.LastError = ""
store.DB.Save(&t)
store.Audit(username, "batch_start", t.Name, "")
if err := s.syncAuthGatewayForTunnel(&t); err != nil {
return fmt.Errorf("专线已启动,但公网认证入口同步失败: %w", err)
}
case taskmgr.ActionStop:
p, err := s.mgr.Get(t.Protocol)
@@ -136,6 +139,9 @@ func (s *Server) executeBatchAction(action taskmgr.TaskAction, tunnelID uint, us
t.State = "stopped"
store.DB.Save(&t)
store.Audit(username, "batch_stop", t.Name, "")
if err := s.syncAuthGatewayForTunnel(&t); err != nil {
return fmt.Errorf("专线已停止,但公网认证入口同步失败: %w", err)
}
case taskmgr.ActionDelete:
if p, err := s.mgr.Get(t.Protocol); err == nil {
@@ -147,6 +153,9 @@ func (s *Server) executeBatchAction(action taskmgr.TaskAction, tunnelID uint, us
s.mgr.RemoveStatus(t.ID)
store.DB.Delete(&t)
store.Audit(username, "batch_delete", t.Name, "")
if err := s.syncAuthGatewayForTunnel(&t); err != nil {
return fmt.Errorf("专线已删除,但公网认证入口同步失败: %w", err)
}
}
return nil
@@ -185,11 +194,11 @@ func parseTunnelIDs(c *gin.Context) ([]uint, error) {
// batchTrafficPolicy 批量设置流量策略
type batchPolicyRequest struct {
TunnelIDs []uint `json:"tunnel_ids" binding:"required,min=1"`
RateLimitMbps int `json:"rate_limit_mbps"`
QuotaBytes int64 `json:"quota_bytes"`
ActionOnExceed string `json:"action_on_exceed"`
ResetCycle string `json:"reset_cycle"`
TunnelIDs []uint `json:"tunnel_ids" binding:"required,min=1"`
RateLimitMbps int `json:"rate_limit_mbps"`
QuotaBytes int64 `json:"quota_bytes"`
ActionOnExceed string `json:"action_on_exceed"`
ResetCycle string `json:"reset_cycle"`
}
func (s *Server) batchSetPolicy(c *gin.Context) {
+11
View File
@@ -46,6 +46,8 @@ func (*gatewayConflict) Error() string {
// with the binary after upgrades. It only touches this application's managed
// Nginx site; RefreshAuth validates it and rolls back on failed reloads.
func (s *Server) RefreshManagedGatewayOnStart() error {
s.gatewayMu.Lock()
defer s.gatewayMu.Unlock()
status := tlsservice.Status()
if !status.Valid || status.Mode != "nginx" || status.RestartRequired || nginxgateway.Stage() != "https" {
return nil
@@ -57,7 +59,16 @@ func (s *Server) RefreshManagedGatewayOnStart() error {
return nginxgateway.RefreshAuth(status.Domain, s.router.Routes(), port)
}
func (s *Server) syncAuthGatewayForTunnel(t *model.Tunnel) error {
if t.Protocol != "openvpn" || s.router == nil {
return nil
}
return s.RefreshManagedGatewayOnStart()
}
func (s *Server) identityGateway(c *gin.Context) {
s.gatewayMu.Lock()
defer s.gatewayMu.Unlock()
status := tlsservice.Status()
if !status.Valid || status.Mode != "nginx" || status.RestartRequired || nginxgateway.Stage() != "https" {
c.JSON(http.StatusBadRequest, gin.H{"error": "请先完成 Nginx HTTPS 入口配置"})
@@ -0,0 +1,39 @@
package api
import (
"testing"
"vpn-platform/backend/internal/model"
"vpn-platform/backend/internal/store"
)
func TestAuthGatewayPortFollowsRunningOIDCTunnel(t *testing.T) {
oldDB := store.DB
t.Cleanup(func() { store.DB = oldDB })
if err := store.Init(t.TempDir()); err != nil {
t.Fatal(err)
}
line := model.Tunnel{Name: "oauth-line", Protocol: "openvpn", State: "stopped", Config: `{"auth_mode":"oidc","oidc_base_url":"https://vpn.example.com","oidc_http_port":19090}`}
if err := store.DB.Create(&line).Error; err != nil {
t.Fatal(err)
}
check := func(want int) {
t.Helper()
got, err := authGatewayPort("vpn.example.com")
if err != nil || got != want {
t.Fatalf("gateway port = %d, %v; want %d", got, err, want)
}
}
check(0)
if err := store.DB.Model(&line).Update("state", "running").Error; err != nil {
t.Fatal(err)
}
check(19090)
if port, err := authGatewayPort("other.example.com"); err != nil || port != 0 {
t.Fatalf("wrong domain routed: %d, %v", port, err)
}
if err := store.DB.Model(&line).Update("state", "stopped").Error; err != nil {
t.Fatal(err)
}
check(0)
}