// Copyright 2025 The frp Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package basic import ( "fmt" "os" "path/filepath" "github.com/onsi/ginkgo/v2" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/pkg/port" ) var _ = ginkgo.Describe("[Feature: TokenSource]", func() { f := framework.NewDefaultFramework() ginkgo.Describe("File-based token loading", func() { ginkgo.It("should work with file tokenSource", func() { // Create a temporary token file tmpDir := f.TempDirectory tokenFile := filepath.Join(tmpDir, "test_token") tokenContent := "test-token-123" err := os.WriteFile(tokenFile, []byte(tokenContent), 0o600) framework.ExpectNoError(err) serverConf := consts.DefaultServerConfig clientConf := consts.DefaultClientConfig portName := port.GenName("TCP") // Server config with tokenSource serverConf += fmt.Sprintf(` auth.tokenSource.type = "file" auth.tokenSource.file.path = "%s" `, tokenFile) // Client config with matching token clientConf += fmt.Sprintf(` auth.token = "%s" [[proxies]] name = "tcp" type = "tcp" localPort = {{ .%s }} remotePort = {{ .%s }} `, tokenContent, framework.TCPEchoServerPort, portName) f.RunProcesses([]string{serverConf}, []string{clientConf}) framework.NewRequestExpect(f).PortName(portName).Ensure() }) ginkgo.It("should work with client tokenSource", func() { // Create a temporary token file tmpDir := f.TempDirectory tokenFile := filepath.Join(tmpDir, "client_token") tokenContent := "client-token-456" err := os.WriteFile(tokenFile, []byte(tokenContent), 0o600) framework.ExpectNoError(err) serverConf := consts.DefaultServerConfig clientConf := consts.DefaultClientConfig portName := port.GenName("TCP") // Server config with matching token serverConf += fmt.Sprintf(` auth.token = "%s" `, tokenContent) // Client config with tokenSource clientConf += fmt.Sprintf(` auth.tokenSource.type = "file" auth.tokenSource.file.path = "%s" [[proxies]] name = "tcp" type = "tcp" localPort = {{ .%s }} remotePort = {{ .%s }} `, tokenFile, framework.TCPEchoServerPort, portName) f.RunProcesses([]string{serverConf}, []string{clientConf}) framework.NewRequestExpect(f).PortName(portName).Ensure() }) ginkgo.It("should work with both server and client tokenSource", func() { // Create temporary token files tmpDir := f.TempDirectory serverTokenFile := filepath.Join(tmpDir, "server_token") clientTokenFile := filepath.Join(tmpDir, "client_token") tokenContent := "shared-token-789" err := os.WriteFile(serverTokenFile, []byte(tokenContent), 0o600) framework.ExpectNoError(err) err = os.WriteFile(clientTokenFile, []byte(tokenContent), 0o600) framework.ExpectNoError(err) serverConf := consts.DefaultServerConfig clientConf := consts.DefaultClientConfig portName := port.GenName("TCP") // Server config with tokenSource serverConf += fmt.Sprintf(` auth.tokenSource.type = "file" auth.tokenSource.file.path = "%s" `, serverTokenFile) // Client config with tokenSource clientConf += fmt.Sprintf(` auth.tokenSource.type = "file" auth.tokenSource.file.path = "%s" [[proxies]] name = "tcp" type = "tcp" localPort = {{ .%s }} remotePort = {{ .%s }} `, clientTokenFile, framework.TCPEchoServerPort, portName) f.RunProcesses([]string{serverConf}, []string{clientConf}) framework.NewRequestExpect(f).PortName(portName).Ensure() }) ginkgo.It("should fail with mismatched tokens", func() { // Create temporary token files with different content tmpDir := f.TempDirectory serverTokenFile := filepath.Join(tmpDir, "server_token") clientTokenFile := filepath.Join(tmpDir, "client_token") err := os.WriteFile(serverTokenFile, []byte("server-token"), 0o600) framework.ExpectNoError(err) err = os.WriteFile(clientTokenFile, []byte("client-token"), 0o600) framework.ExpectNoError(err) serverConf := consts.DefaultServerConfig clientConf := consts.DefaultClientConfig portName := port.GenName("TCP") // Server config with tokenSource serverConf += fmt.Sprintf(` auth.tokenSource.type = "file" auth.tokenSource.file.path = "%s" `, serverTokenFile) // Client config with different tokenSource clientConf += fmt.Sprintf(` auth.tokenSource.type = "file" auth.tokenSource.file.path = "%s" [[proxies]] name = "tcp" type = "tcp" localPort = {{ .%s }} remotePort = {{ .%s }} `, clientTokenFile, framework.TCPEchoServerPort, portName) f.RunProcesses([]string{serverConf}, []string{clientConf}) // This should fail due to token mismatch - the client should not be able to connect // We expect the request to fail because the proxy tunnel is not established framework.NewRequestExpect(f).PortName(portName).ExpectError(true).Ensure() }) ginkgo.It("should fail with non-existent token file", func() { // This test verifies that server fails to start when tokenSource points to non-existent file // We'll verify this by checking that the configuration loading itself fails // Create a config that references a non-existent file tmpDir := f.TempDirectory nonExistentFile := filepath.Join(tmpDir, "non_existent_token") serverConf := consts.DefaultServerConfig // Server config with non-existent tokenSource file serverConf += fmt.Sprintf(` auth.tokenSource.type = "file" auth.tokenSource.file.path = "%s" `, nonExistentFile) // The test expectation is that this will fail during the RunProcesses call // because the server cannot load the configuration due to missing token file defer func() { if r := recover(); r != nil { // Expected: server should fail to start due to missing file ginkgo.By(fmt.Sprintf("Server correctly failed to start: %v", r)) } }() // This should cause a panic or error during server startup f.RunProcesses([]string{serverConf}, []string{}) }) }) })