feat: configure HTTP/HTTPS public entry and managed certificates

This commit is contained in:
Codex committed 2026-10-26 14:06:41 +08:00
1 parent 35596cda9d
commit 88ea6b7854
21 files changed
+1309 -149

No files matched your search

+40 -10
View File
@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
"vpn-platform/backend/internal/model"
"vpn-platform/backend/internal/nginxconfig"
"vpn-platform/backend/internal/nginxgateway"
"vpn-platform/backend/internal/service"
"vpn-platform/backend/internal/store"
@@ -14,7 +15,8 @@ import (
// Only a single OpenVPN WebAuth instance may own /oauth2/ on the management
// hostname. Never route an arbitrary user-supplied address to a local port.
func authGatewayPort(domain string) (int, error) {
func authGatewayPort(publicBase string) (int, error) {
publicBase = strings.TrimRight(strings.TrimSpace(publicBase), "/")
var tunnels []model.Tunnel
if err := store.DB.Where("protocol = ? AND state = ?", "openvpn", "running").Find(&tunnels).Error; err != nil {
return 0, err
@@ -25,7 +27,7 @@ func authGatewayPort(domain string) (int, error) {
if err != nil {
return 0, err
}
if service.Str(cfg, "auth_mode") != "oidc" || !strings.EqualFold(strings.TrimRight(service.Str(cfg, "oidc_base_url"), "/"), "https://"+domain) {
if service.Str(cfg, "auth_mode") != "oidc" || !strings.EqualFold(strings.TrimRight(service.Str(cfg, "oidc_base_url"), "/"), publicBase) {
continue
}
if port != 0 {
@@ -49,14 +51,27 @@ 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" {
if status.Mode != "nginx" || status.RestartRequired || status.Domain == "" || status.PublicBase == "" {
return nil
}
port, err := authGatewayPort(status.Domain)
scheme := status.Scheme
if scheme == "" {
scheme = "https"
}
stage := nginxgateway.Stage()
if scheme == "https" && (!status.Valid || stage != "https") {
return nil
}
if scheme == "http" && stage == "none" {
return nil
}
port, err := authGatewayPort(status.PublicBase)
if err != nil {
return err
}
return nginxgateway.RefreshAuth(status.Domain, s.router.Routes(), port)
return nginxgateway.RefreshAuthEntry(nginxconfig.RenderOptions{
Domain: status.Domain, Scheme: scheme, Port: status.Port,
}, s.router.Routes(), port)
}
func (s *Server) syncAuthGatewayForTunnel(t *model.Tunnel) error {
@@ -70,19 +85,34 @@ 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" {
scheme := status.Scheme
if scheme == "" {
scheme = "https"
}
if status.Mode != "nginx" || status.RestartRequired || status.Domain == "" || status.PublicBase == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "请先完成公网入口配置"})
return
}
stage := nginxgateway.Stage()
if scheme == "https" && (!status.Valid || stage != "https") {
c.JSON(http.StatusBadRequest, gin.H{"error": "请先完成 Nginx HTTPS 入口配置"})
return
}
port, err := authGatewayPort(status.Domain)
if scheme == "http" && stage == "none" {
c.JSON(http.StatusBadRequest, gin.H{"error": "请先启用 HTTP 公网入口"})
return
}
port, err := authGatewayPort(status.PublicBase)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if err := nginxgateway.RefreshAuth(status.Domain, s.router.Routes(), port); err != nil {
if err := nginxgateway.RefreshAuthEntry(nginxconfig.RenderOptions{
Domain: status.Domain, Scheme: scheme, Port: status.Port,
}, s.router.Routes(), port); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
store.Audit(c.GetString("username"), "identity_gateway_updated", status.Domain, "")
c.JSON(200, gin.H{"ok": true, "identity_route": true, "auth_route": port != 0, "auth_port": port})
store.Audit(c.GetString("username"), "identity_gateway_updated", status.PublicBase, "")
c.JSON(200, gin.H{"ok": true, "identity_route": true, "auth_route": port != 0, "auth_port": port, "public_base": status.PublicBase})
}