Sync OpenVPN OAuth gateway routes on tunnel changes
This commit is contained in:
1 parent
9e26358df0
commit
ff2db503ab
6 files changed
+106
-9
No files matched your search
@@ -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"})
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user