diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 179f97e..26cbb7c 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -26,7 +26,7 @@ jobs: with: go-version: stable - - uses: golangci/golangci-lint-action@v6 + - uses: golangci/golangci-lint-action@v9 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: latest diff --git a/.golangci.yml b/.golangci.yml index f3b4eef..220a9aa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,5 @@ +version: "2" + run: timeout: 5m modules-download-mode: readonly diff --git a/pkg/config/config.go b/pkg/config/config.go index 0448f55..5142346 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -68,6 +68,8 @@ type Config struct { DisableKeyboardRecord bool `mapstructure:"DISABLE_KEYBOARD_RECORD"` DriveScope string `mapstructure:"LION_DRIVE_SCOPE"` // user or session + // DOMAINS=* "demo.example.com:443,172.17.200.191:80" + DOMAINS string `mapstructure:"DOMAINS"` } func (c *Config) UpdateRedisPassword(val string) { diff --git a/pkg/tunnel/server.go b/pkg/tunnel/server.go index 4f0a850..d2a0fe5 100644 --- a/pkg/tunnel/server.go +++ b/pkg/tunnel/server.go @@ -7,6 +7,8 @@ import ( "io" "net" "net/http" + "net/netip" + "net/url" "os" "path/filepath" "strconv" @@ -32,13 +34,77 @@ const ( defaultBufferSize = 1024 ) +func normalizeHost(h string) string { + h = strings.TrimSpace(strings.ToLower(h)) + host, _, err := net.SplitHostPort(h) + if err == nil { + return strings.Trim(host, "[]") + } + return strings.Trim(h, "[]") +} + +func hostMatches(allowedHost, originHost string) bool { + allowedName := normalizeHost(allowedHost) + originName := normalizeHost(originHost) + if allowedName == "" || originName == "" { + return false + } + return allowedName == originName +} + +func isInternalHost(host string) bool { + host = normalizeHost(host) + if host == "localhost" { + return true + } + + addr, err := netip.ParseAddr(host) + if err != nil { + return false + } + return addr.IsLoopback() || addr.IsPrivate() || addr.IsLinkLocalUnicast() +} + +func domainsAllowOrigin(originHost, domains string) bool { + for _, domain := range strings.Split(domains, ",") { + domain = strings.TrimSpace(domain) + if domain == "" { + continue + } + if domain == "*" || hostMatches(domain, originHost) { + return true + } + } + return false +} + +func checkOrigin(r *http.Request) bool { + origin := r.Header.Get("Origin") + // 允许非浏览器,客户端访问 + if len(origin) == 0 { + return true + } + u, err := url.Parse(origin) + if err != nil { + return false + } + originHost := normalizeHost(u.Host) + reqHost := normalizeHost(r.Host) + if hostMatches(originHost, reqHost) { + return true + } + if isInternalHost(originHost) { + return true + } + + return domainsAllowOrigin(originHost, config.GlobalConfig.DOMAINS) +} + var upGrader = websocket.Upgrader{ ReadBufferSize: defaultBufferSize, WriteBufferSize: defaultBufferSize, Subprotocols: []string{"guacamole"}, - CheckOrigin: func(r *http.Request) bool { - return true - }, + CheckOrigin: checkOrigin, } type GuacamoleTunnelServer struct {