Add opt-in built-in Feishu OIDC issuer for OpenVPN
This commit is contained in:
1 parent
6ca609fde4
commit
63cdda4ae1
23 files changed
+1244
-17
No files matched your search
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"vpn-platform/backend/internal/binmgr"
|
||||
"vpn-platform/backend/internal/core"
|
||||
"vpn-platform/backend/internal/identityprovider"
|
||||
"vpn-platform/backend/internal/model"
|
||||
"vpn-platform/backend/internal/service"
|
||||
"vpn-platform/backend/internal/service/custom"
|
||||
@@ -213,6 +214,14 @@ func requirePasswordChanged() gin.HandlerFunc {
|
||||
func (s *Server) RegisterRoutes(r *gin.Engine) {
|
||||
s.router = r
|
||||
r.Use(securityHeaders())
|
||||
idp := identityprovider.New()
|
||||
id := r.Group("/id", identityprovider.SecureGateway(), identityprovider.RateLimit())
|
||||
id.GET("/.well-known/openid-configuration", idp.Discovery)
|
||||
id.GET("/jwks", idp.JWKS)
|
||||
id.GET("/authorize", idp.Authorize)
|
||||
id.GET("/feishu/callback", idp.FeishuCallback)
|
||||
id.POST("/token", idp.Token)
|
||||
id.GET("/userinfo", idp.UserInfo)
|
||||
api := r.Group("/api")
|
||||
api.POST("/login", s.login)
|
||||
api.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) })
|
||||
@@ -302,6 +311,9 @@ func (s *Server) RegisterRoutes(r *gin.Engine) {
|
||||
admin.GET("/system/env", s.systemEnv)
|
||||
admin.POST("/system/fix", s.systemFix)
|
||||
admin.GET("/system/tls", s.tlsStatus)
|
||||
admin.GET("/system/identity", idp.Status)
|
||||
admin.PUT("/system/identity", idp.Configure)
|
||||
admin.POST("/system/identity/gateway", identityprovider.SecureGateway(), s.identityGateway)
|
||||
admin.PUT("/system/tls/domain", s.setTLSDomain)
|
||||
admin.PUT("/system/tls/mode", s.setTLSMode)
|
||||
admin.GET("/system/tls/nginx-preview", s.nginxPreview)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"vpn-platform/backend/internal/model"
|
||||
"vpn-platform/backend/internal/nginxgateway"
|
||||
"vpn-platform/backend/internal/service"
|
||||
"vpn-platform/backend/internal/store"
|
||||
"vpn-platform/backend/internal/tlsservice"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
var tunnels []model.Tunnel
|
||||
if err := store.DB.Where("protocol = ? AND state = ?", "openvpn", "running").Find(&tunnels).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
port := 0
|
||||
for _, tunnel := range tunnels {
|
||||
cfg, err := store.TunnelConfig(&tunnel)
|
||||
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) {
|
||||
continue
|
||||
}
|
||||
if port != 0 {
|
||||
return 0, &gatewayConflict{}
|
||||
}
|
||||
port = int(service.Num(cfg, "oidc_http_port", 0))
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
type gatewayConflict struct{}
|
||||
|
||||
func (*gatewayConflict) Error() string {
|
||||
return "当前域名存在多条运行中的 OIDC 专线,请先保留一条"
|
||||
}
|
||||
|
||||
func (s *Server) identityGateway(c *gin.Context) {
|
||||
status := tlsservice.Status()
|
||||
if !status.Valid || status.Mode != "nginx" || status.RestartRequired || nginxgateway.Stage() != "https" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请先完成 Nginx HTTPS 入口配置"})
|
||||
return
|
||||
}
|
||||
port, err := authGatewayPort(status.Domain)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := nginxgateway.RefreshAuth(status.Domain, 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})
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"vpn-platform/backend/internal/identityprovider"
|
||||
"vpn-platform/backend/internal/nginxgateway"
|
||||
"vpn-platform/backend/internal/service"
|
||||
"vpn-platform/backend/internal/tlsservice"
|
||||
@@ -37,10 +38,15 @@ func (s *Server) openvpnOIDCReadiness(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
tls := tlsservice.Status()
|
||||
builtInIssuer, builtInClientID, builtInEnabled := identityprovider.PublicClient()
|
||||
port, _ := authGatewayPort(tls.Domain)
|
||||
issuerRoute, callbackRoute := nginxgateway.AuthRouteReady(port)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"component_installed": path != "", "component_version": version,
|
||||
"https_management_ready": tls.Mode == "nginx" && tls.Valid && nginxgateway.Stage() == "https",
|
||||
"management_domain": tls.Domain,
|
||||
"authentication_entry_ready": false, // Depends on a distinct callback site and a configured provider.
|
||||
"authentication_entry_ready": port > 0 && callbackRoute,
|
||||
"built_in_issuer": builtInIssuer, "built_in_client_id": builtInClientID,
|
||||
"built_in_enabled": builtInEnabled, "built_in_gateway_ready": issuerRoute,
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user