diff --git a/config/config.go b/config/config.go index 9067d14f..3117853c 100644 --- a/config/config.go +++ b/config/config.go @@ -107,6 +107,12 @@ type Controller struct { ExternalUI string ExternalDohServer string Secret string + Cors Cors +} + +type Cors struct { + AllowOrigins []string + AllowPrivateNetwork bool } // Experimental config @@ -191,6 +197,11 @@ type Config struct { TLS *TLS } +type RawCors struct { + AllowOrigins []string `yaml:"allow-origins" json:"allow-origins"` + AllowPrivateNetwork bool `yaml:"allow-private-network" json:"allow-private-network"` +} + type RawDNS struct { Enable bool `yaml:"enable" json:"enable"` PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"` @@ -368,6 +379,7 @@ type RawConfig struct { ExternalControllerPipe string `yaml:"external-controller-pipe" json:"external-controller-pipe"` ExternalControllerUnix string `yaml:"external-controller-unix" json:"external-controller-unix"` ExternalControllerTLS string `yaml:"external-controller-tls" json:"external-controller-tls"` + ExternalControllerCors RawCors `yaml:"external-controller-cors" json:"external-controller-cors"` ExternalUI string `yaml:"external-ui" json:"external-ui"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"` @@ -541,6 +553,10 @@ func DefaultRawConfig() *RawConfig { OverrideDest: true, }, ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip", + ExternalControllerCors: RawCors{ + AllowOrigins: []string{"*"}, + AllowPrivateNetwork: true, + }, } } @@ -775,6 +791,10 @@ func parseController(cfg *RawConfig) (*Controller, error) { ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerTLS: cfg.ExternalControllerTLS, ExternalDohServer: cfg.ExternalDohServer, + Cors: Cors{ + AllowOrigins: cfg.ExternalControllerCors.AllowOrigins, + AllowPrivateNetwork: cfg.ExternalControllerCors.AllowPrivateNetwork, + }, }, nil } diff --git a/docs/config.yaml b/docs/config.yaml index 9c480b3f..15bcf607 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -58,6 +58,12 @@ external-controller: 0.0.0.0:9093 # RESTful API 监听地址 external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件 # secret: "123456" # `Authorization:Bearer ${secret}` +# RESTful API CORS标头配置 +external-controller-cors: + allow-origins: + - * + allow-private-network: true + # RESTful API Unix socket 监听地址( windows版本大于17063也可以使用,即大于等于1803/RS4版本即可使用 ) # !!!注意: 从Unix socket访问api接口不会验证secret, 如果开启请自行保证安全问题 !!! # 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/ diff --git a/go.mod b/go.mod index b74cee92..4b066c90 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/coreos/go-iptables v0.7.0 github.com/dlclark/regexp2 v1.11.4 github.com/go-chi/chi/v5 v5.1.0 - github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.4.0 github.com/gofrs/uuid/v5 v5.3.0 @@ -37,6 +36,7 @@ require ( github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 github.com/puzpuzpuz/xsync/v3 v3.4.0 + github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/sagernet/sing v0.5.0-alpha.13 diff --git a/go.sum b/go.sum index ba2d1819..60818034 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,6 @@ github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXb github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= -github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -160,6 +158,8 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= +github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/hub/hub.go b/hub/hub.go index 73a44eee..69f627ff 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -59,6 +59,10 @@ func applyRoute(cfg *config.Config) { PrivateKey: cfg.TLS.PrivateKey, DohServer: cfg.Controller.ExternalDohServer, IsDebug: cfg.General.LogLevel == log.DEBUG, + Cors: route.Cors{ + AllowOrigins: cfg.Controller.Cors.AllowOrigins, + AllowPrivateNetwork: cfg.Controller.Cors.AllowPrivateNetwork, + }, }) } diff --git a/hub/route/server.go b/hub/route/server.go index 1810a707..e71347fc 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -23,10 +23,10 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/cors" "github.com/go-chi/render" "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" + "github.com/sagernet/cors" ) var ( @@ -58,6 +58,22 @@ type Config struct { PrivateKey string DohServer string IsDebug bool + Cors Cors +} + +type Cors struct { + AllowOrigins []string + AllowPrivateNetwork bool +} + +func (c Cors) Apply(r chi.Router) { + r.Use(cors.New(cors.Options{ + AllowedOrigins: c.AllowOrigins, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, + AllowedHeaders: []string{"Content-Type", "Authorization"}, + AllowPrivateNetwork: c.AllowPrivateNetwork, + MaxAge: 300, + }).Handler) } func ReCreateServer(cfg *Config) { @@ -73,16 +89,9 @@ func SetUIPath(path string) { uiPath = C.Path.Resolve(path) } -func router(isDebug bool, secret string, dohServer string) *chi.Mux { +func router(isDebug bool, secret string, dohServer string, cors Cors) *chi.Mux { r := chi.NewRouter() - corsM := cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Content-Type", "Authorization"}, - MaxAge: 300, - }) - r.Use(setPrivateNetworkAccess) - r.Use(corsM.Handler) + cors.Apply(r) if isDebug { r.Mount("/debug", func() http.Handler { r := chi.NewRouter() @@ -151,7 +160,7 @@ func start(cfg *Config) { log.Infoln("RESTful API listening at: %s", l.Addr().String()) server := &http.Server{ - Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer), + Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors), } httpServer = server if err = server.Serve(l); err != nil { @@ -183,7 +192,7 @@ func startTLS(cfg *Config) { log.Infoln("RESTful API tls listening at: %s", l.Addr().String()) server := &http.Server{ - Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer), + Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors), TLSConfig: &tls.Config{ Certificates: []tls.Certificate{c}, }, @@ -232,7 +241,7 @@ func startUnix(cfg *Config) { log.Infoln("RESTful API unix listening at: %s", l.Addr().String()) server := &http.Server{ - Handler: router(cfg.IsDebug, "", cfg.DohServer), + Handler: router(cfg.IsDebug, "", cfg.DohServer, cfg.Cors), } unixServer = server if err = server.Serve(l); err != nil { @@ -263,7 +272,7 @@ func startPipe(cfg *Config) { log.Infoln("RESTful API pipe listening at: %s", l.Addr().String()) server := &http.Server{ - Handler: router(cfg.IsDebug, "", cfg.DohServer), + Handler: router(cfg.IsDebug, "", cfg.DohServer, cfg.Cors), } pipeServer = server if err = server.Serve(l); err != nil { @@ -272,15 +281,6 @@ func startPipe(cfg *Config) { } } -func setPrivateNetworkAccess(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { - w.Header().Add("Access-Control-Allow-Private-Network", "true") - } - next.ServeHTTP(w, r) - }) -} - func safeEuqal(a, b string) bool { aBuf := utils.ImmutableBytesFromString(a) bBuf := utils.ImmutableBytesFromString(b)