소스 검색

Merge pull request #4496 from fatedier/dev

bump version
fatedier 3 달 전
부모
커밋
4bbec09d57

+ 2 - 2
.github/workflows/golangci-lint.yml

@@ -17,13 +17,13 @@ jobs:
     - uses: actions/checkout@v4
     - uses: actions/setup-go@v5
       with:
-        go-version: '1.22'
+        go-version: '1.23'
         cache: false
     - name: golangci-lint
       uses: golangci/golangci-lint-action@v4
       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: v1.57
+        version: v1.61
 
         # Optional: golangci-lint command line arguments.
         # args: --issues-exit-code=0

+ 1 - 1
.github/workflows/goreleaser.yml

@@ -15,7 +15,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v5
         with:
-          go-version: '1.22'
+          go-version: '1.23'
           
       - name: Make All
         run: |

+ 3 - 2
.golangci.yml

@@ -1,5 +1,5 @@
 service:
-  golangci-lint-version: 1.57.x # use the fixed version to not introduce new linters unexpectedly
+  golangci-lint-version: 1.61.x # use the fixed version to not introduce new linters unexpectedly
 
 run:
   concurrency: 4
@@ -14,7 +14,7 @@ linters:
   enable:
   - unused
   - errcheck
-  - exportloopref
+  - copyloopvar
   - gocritic
   - gofumpt
   - goimports
@@ -90,6 +90,7 @@ linters-settings:
     - G402
     - G404
     - G501
+    - G115 # integer overflow conversion
 
 issues:
   # List of regexps of issue texts to exclude, empty list by default.

+ 1 - 1
README_zh.md

@@ -95,7 +95,7 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
 
 您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
 
-国内用户可以通过 [爱发电](https://afdian.net/a/fatedier) 赞助我们。
+国内用户可以通过 [爱发电](https://afdian.com/a/fatedier) 赞助我们。
 
 企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
 

+ 2 - 6
Release.md

@@ -1,8 +1,4 @@
 ### Features
 
-* Added a new plugin `tls2raw`: Enables TLS termination and forwarding of decrypted raw traffic to local service.
-* Added a default timeout of 30 seconds for the frpc subcommands to prevent commands from being stuck for a long time due to network issues.
-
-### Fixes
-
-* Fixed the issue that when `loginFailExit = false`, the frpc stop command cannot be stopped correctly if the server is not successfully connected after startup.
+* The frpc visitor command-line parameter adds the `--server-user` option to specify the username of the server-side proxy to connect to.
+* Support multiple frpc instances with different subjects when using oidc authentication.

+ 1 - 1
client/control.go

@@ -230,7 +230,7 @@ func (ctl *Control) registerMsgHandlers() {
 	ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
 }
 
-// headerWorker sends heartbeat to server and check heartbeat timeout.
+// heartbeatWorker sends heartbeat to server and check heartbeat timeout.
 func (ctl *Control) heartbeatWorker() {
 	xl := ctl.xl
 

+ 1 - 1
client/proxy/proxy_wrapper.go

@@ -137,7 +137,7 @@ func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
 		pw.Phase = ProxyPhaseStartErr
 		pw.Err = respErr
 		pw.lastStartErr = time.Now()
-		return fmt.Errorf(pw.Err)
+		return fmt.Errorf("%s", pw.Err)
 	}
 
 	if err := pw.pxy.Run(); err != nil {

+ 1 - 1
conf/frpc_full_example.toml

@@ -327,7 +327,7 @@ requestHeaders.set.x-from-where = "frp"
 
 [[proxies]]
 name = "plugin_tls2raw"
-type = "https"
+type = "tcp"
 remotePort = 6008
 [proxies.plugin]
 type = "tls2raw"

+ 1 - 1
dockerfiles/Dockerfile-for-frpc

@@ -1,4 +1,4 @@
-FROM golang:1.22 AS building
+FROM golang:1.23 AS building
 
 COPY . /building
 WORKDIR /building

+ 1 - 1
dockerfiles/Dockerfile-for-frps

@@ -1,4 +1,4 @@
-FROM golang:1.22 AS building
+FROM golang:1.23 AS building
 
 COPY . /building
 WORKDIR /building

+ 2 - 1
pkg/auth/auth.go

@@ -50,7 +50,8 @@ func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
 	case v1.AuthMethodToken:
 		authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
 	case v1.AuthMethodOIDC:
-		authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC)
+		tokenVerifier := NewTokenVerifier(cfg.OIDC)
+		authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, tokenVerifier)
 	}
 	return authVerifier
 }

+ 19 - 8
pkg/auth/oidc.go

@@ -87,14 +87,18 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
 	return err
 }
 
+type TokenVerifier interface {
+	Verify(context.Context, string) (*oidc.IDToken, error)
+}
+
 type OidcAuthConsumer struct {
 	additionalAuthScopes []v1.AuthScope
 
-	verifier         *oidc.IDTokenVerifier
-	subjectFromLogin string
+	verifier          TokenVerifier
+	subjectsFromLogin []string
 }
 
-func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer {
+func NewTokenVerifier(cfg v1.AuthOIDCServerConfig) TokenVerifier {
 	provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
 	if err != nil {
 		panic(err)
@@ -105,9 +109,14 @@ func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCSer
 		SkipExpiryCheck:   cfg.SkipExpiryCheck,
 		SkipIssuerCheck:   cfg.SkipIssuerCheck,
 	}
+	return provider.Verifier(&verifierConf)
+}
+
+func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, verifier TokenVerifier) *OidcAuthConsumer {
 	return &OidcAuthConsumer{
 		additionalAuthScopes: additionalAuthScopes,
-		verifier:             provider.Verifier(&verifierConf),
+		verifier:             verifier,
+		subjectsFromLogin:    []string{},
 	}
 }
 
@@ -116,7 +125,9 @@ func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {
 	if err != nil {
 		return fmt.Errorf("invalid OIDC token in login: %v", err)
 	}
-	auth.subjectFromLogin = token.Subject
+	if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
+		auth.subjectsFromLogin = append(auth.subjectsFromLogin, token.Subject)
+	}
 	return nil
 }
 
@@ -125,11 +136,11 @@ func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err err
 	if err != nil {
 		return fmt.Errorf("invalid OIDC token in ping: %v", err)
 	}
-	if token.Subject != auth.subjectFromLogin {
+	if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
 		return fmt.Errorf("received different OIDC subject in login and ping. "+
-			"original subject: %s, "+
+			"original subjects: %s, "+
 			"new subject: %s",
-			auth.subjectFromLogin, token.Subject)
+			auth.subjectsFromLogin, token.Subject)
 	}
 	return nil
 }

+ 64 - 0
pkg/auth/oidc_test.go

@@ -0,0 +1,64 @@
+package auth_test
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/coreos/go-oidc/v3/oidc"
+	"github.com/stretchr/testify/require"
+
+	"github.com/fatedier/frp/pkg/auth"
+	v1 "github.com/fatedier/frp/pkg/config/v1"
+	"github.com/fatedier/frp/pkg/msg"
+)
+
+type mockTokenVerifier struct{}
+
+func (m *mockTokenVerifier) Verify(ctx context.Context, subject string) (*oidc.IDToken, error) {
+	return &oidc.IDToken{
+		Subject: subject,
+	}, nil
+}
+
+func TestPingWithEmptySubjectFromLoginFails(t *testing.T) {
+	r := require.New(t)
+	consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
+	err := consumer.VerifyPing(&msg.Ping{
+		PrivilegeKey: "ping-without-login",
+		Timestamp:    time.Now().UnixMilli(),
+	})
+	r.Error(err)
+	r.Contains(err.Error(), "received different OIDC subject in login and ping")
+}
+
+func TestPingAfterLoginWithNewSubjectSucceeds(t *testing.T) {
+	r := require.New(t)
+	consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
+	err := consumer.VerifyLogin(&msg.Login{
+		PrivilegeKey: "ping-after-login",
+	})
+	r.NoError(err)
+
+	err = consumer.VerifyPing(&msg.Ping{
+		PrivilegeKey: "ping-after-login",
+		Timestamp:    time.Now().UnixMilli(),
+	})
+	r.NoError(err)
+}
+
+func TestPingAfterLoginWithDifferentSubjectFails(t *testing.T) {
+	r := require.New(t)
+	consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
+	err := consumer.VerifyLogin(&msg.Login{
+		PrivilegeKey: "login-with-first-subject",
+	})
+	r.NoError(err)
+
+	err = consumer.VerifyPing(&msg.Ping{
+		PrivilegeKey: "ping-with-different-subject",
+		Timestamp:    time.Now().UnixMilli(),
+	})
+	r.Error(err)
+	r.Contains(err.Error(), "received different OIDC subject in login and ping")
+}

+ 1 - 0
pkg/config/flags.go

@@ -140,6 +140,7 @@ func registerVisitorBaseConfigFlags(cmd *cobra.Command, c *v1.VisitorBaseConfig,
 	cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
 	cmd.Flags().StringVarP(&c.SecretKey, "sk", "", "", "secret key")
 	cmd.Flags().StringVarP(&c.ServerName, "server_name", "", "", "server name")
+	cmd.Flags().StringVarP(&c.ServerUser, "server-user", "", "", "server user")
 	cmd.Flags().StringVarP(&c.BindAddr, "bind_addr", "", "", "bind addr")
 	cmd.Flags().IntVarP(&c.BindPort, "bind_port", "", 0, "bind port")
 }

+ 4 - 4
pkg/config/types/types.go

@@ -159,18 +159,18 @@ func NewPortsRangeSliceFromString(str string) ([]PortsRange, error) {
 			out = append(out, PortsRange{Single: int(singleNum)})
 		case 2:
 			// range numbers
-			min, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
+			minNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
 			if err != nil {
 				return nil, fmt.Errorf("range number is invalid, %v", err)
 			}
-			max, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
+			maxNum, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
 			if err != nil {
 				return nil, fmt.Errorf("range number is invalid, %v", err)
 			}
-			if max < min {
+			if maxNum < minNum {
 				return nil, fmt.Errorf("range number is invalid")
 			}
-			out = append(out, PortsRange{Start: int(min), End: int(max)})
+			out = append(out, PortsRange{Start: int(minNum), End: int(maxNum)})
 		default:
 			return nil, fmt.Errorf("range number is invalid")
 		}

+ 5 - 5
pkg/nathole/utils.go

@@ -78,9 +78,9 @@ func ListAllLocalIPs() ([]net.IP, error) {
 	return ips, nil
 }
 
-func ListLocalIPsForNatHole(max int) ([]string, error) {
-	if max <= 0 {
-		return nil, fmt.Errorf("max must be greater than 0")
+func ListLocalIPsForNatHole(maxItems int) ([]string, error) {
+	if maxItems <= 0 {
+		return nil, fmt.Errorf("maxItems must be greater than 0")
 	}
 
 	ips, err := ListAllLocalIPs()
@@ -88,9 +88,9 @@ func ListLocalIPsForNatHole(max int) ([]string, error) {
 		return nil, err
 	}
 
-	filtered := make([]string, 0, max)
+	filtered := make([]string, 0, maxItems)
 	for _, ip := range ips {
-		if len(filtered) >= max {
+		if len(filtered) >= maxItems {
 			break
 		}
 

+ 9 - 9
pkg/util/util/util.go

@@ -85,21 +85,21 @@ func ParseRangeNumbers(rangeStr string) (numbers []int64, err error) {
 			numbers = append(numbers, singleNum)
 		case 2:
 			// range numbers
-			min, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
+			minValue, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
 			if errRet != nil {
 				err = fmt.Errorf("range number is invalid, %v", errRet)
 				return
 			}
-			max, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
+			maxValue, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
 			if errRet != nil {
 				err = fmt.Errorf("range number is invalid, %v", errRet)
 				return
 			}
-			if max < min {
+			if maxValue < minValue {
 				err = fmt.Errorf("range number is invalid")
 				return
 			}
-			for i := min; i <= max; i++ {
+			for i := minValue; i <= maxValue; i++ {
 				numbers = append(numbers, i)
 			}
 		default:
@@ -118,13 +118,13 @@ func GenerateResponseErrorString(summary string, err error, detailed bool) strin
 }
 
 func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Duration {
-	min := int64(minRatio * 1000.0)
-	max := int64(maxRatio * 1000.0)
+	minValue := int64(minRatio * 1000.0)
+	maxValue := int64(maxRatio * 1000.0)
 	var n int64
-	if max <= min {
-		n = min
+	if maxValue <= minValue {
+		n = minValue
 	} else {
-		n = mathrand.Int64N(max-min) + min
+		n = mathrand.Int64N(maxValue-minValue) + minValue
 	}
 	d := duration * time.Duration(n) / time.Duration(1000)
 	time.Sleep(d)

+ 1 - 1
pkg/util/version/version.go

@@ -14,7 +14,7 @@
 
 package version
 
-var version = "0.60.0"
+var version = "0.61.0"
 
 func Full() string {
 	return version

+ 6 - 6
server/proxy/proxy.go

@@ -137,17 +137,17 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
 			dstAddr    string
 			srcPortStr string
 			dstPortStr string
-			srcPort    int
-			dstPort    int
+			srcPort    uint64
+			dstPort    uint64
 		)
 
 		if src != nil {
 			srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
-			srcPort, _ = strconv.Atoi(srcPortStr)
+			srcPort, _ = strconv.ParseUint(srcPortStr, 10, 16)
 		}
 		if dst != nil {
 			dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
-			dstPort, _ = strconv.Atoi(dstPortStr)
+			dstPort, _ = strconv.ParseUint(dstPortStr, 10, 16)
 		}
 		err := msg.WriteMsg(workConn, &msg.StartWorkConn{
 			ProxyName: pxy.GetName(),
@@ -190,8 +190,8 @@ func (pxy *BaseProxy) startCommonTCPListenersHandler() {
 						} else {
 							tempDelay *= 2
 						}
-						if max := 1 * time.Second; tempDelay > max {
-							tempDelay = max
+						if maxTime := 1 * time.Second; tempDelay > maxTime {
+							tempDelay = maxTime
 						}
 						xl.Infof("met temporary error: %s, sleep for %s ...", err, tempDelay)
 						time.Sleep(tempDelay)