Files
frp/test/e2e/v1/basic/token_source.go

380 lines
11 KiB
Go

// 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()
createExecTokenScript := func(name string) string {
scriptPath := filepath.Join(f.TempDirectory, name)
scriptContent := `#!/bin/sh
printf '%s\n' "$1"
`
err := os.WriteFile(scriptPath, []byte(scriptContent), 0o600)
framework.ExpectNoError(err)
err = os.Chmod(scriptPath, 0o700)
framework.ExpectNoError(err)
return scriptPath
}
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{})
})
})
ginkgo.Describe("Exec-based token loading", func() {
ginkgo.It("should work with server tokenSource", func() {
execValue := "exec-server-value"
scriptPath := createExecTokenScript("server_token_exec.sh")
serverPort := f.AllocPort()
remotePort := f.AllocPort()
serverConf := fmt.Sprintf(`
bindAddr = "0.0.0.0"
bindPort = %d
auth.tokenSource.type = "exec"
auth.tokenSource.exec.command = %q
auth.tokenSource.exec.args = [%q]
`, serverPort, scriptPath, execValue)
clientConf := fmt.Sprintf(`
serverAddr = "127.0.0.1"
serverPort = %d
loginFailExit = false
auth.token = %q
[[proxies]]
name = "tcp"
type = "tcp"
localPort = %d
remotePort = %d
`, serverPort, execValue, f.PortByName(framework.TCPEchoServerPort), remotePort)
serverConfigPath := f.GenerateConfigFile(serverConf)
clientConfigPath := f.GenerateConfigFile(clientConf)
_, _, err := f.RunFrps("-c", serverConfigPath, "--allow-unsafe=TokenSourceExec")
framework.ExpectNoError(err)
_, _, err = f.RunFrpc("-c", clientConfigPath, "--allow-unsafe=TokenSourceExec")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
ginkgo.It("should work with client tokenSource", func() {
execValue := "exec-client-value"
scriptPath := createExecTokenScript("client_token_exec.sh")
serverPort := f.AllocPort()
remotePort := f.AllocPort()
serverConf := fmt.Sprintf(`
bindAddr = "0.0.0.0"
bindPort = %d
auth.token = %q
`, serverPort, execValue)
clientConf := fmt.Sprintf(`
serverAddr = "127.0.0.1"
serverPort = %d
loginFailExit = false
auth.tokenSource.type = "exec"
auth.tokenSource.exec.command = %q
auth.tokenSource.exec.args = [%q]
[[proxies]]
name = "tcp"
type = "tcp"
localPort = %d
remotePort = %d
`, serverPort, scriptPath, execValue, f.PortByName(framework.TCPEchoServerPort), remotePort)
serverConfigPath := f.GenerateConfigFile(serverConf)
clientConfigPath := f.GenerateConfigFile(clientConf)
_, _, err := f.RunFrps("-c", serverConfigPath, "--allow-unsafe=TokenSourceExec")
framework.ExpectNoError(err)
_, _, err = f.RunFrpc("-c", clientConfigPath, "--allow-unsafe=TokenSourceExec")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
ginkgo.It("should work with both server and client tokenSource", func() {
execValue := "exec-shared-value"
scriptPath := createExecTokenScript("shared_token_exec.sh")
serverPort := f.AllocPort()
remotePort := f.AllocPort()
serverConf := fmt.Sprintf(`
bindAddr = "0.0.0.0"
bindPort = %d
auth.tokenSource.type = "exec"
auth.tokenSource.exec.command = %q
auth.tokenSource.exec.args = [%q]
`, serverPort, scriptPath, execValue)
clientConf := fmt.Sprintf(`
serverAddr = "127.0.0.1"
serverPort = %d
loginFailExit = false
auth.tokenSource.type = "exec"
auth.tokenSource.exec.command = %q
auth.tokenSource.exec.args = [%q]
[[proxies]]
name = "tcp"
type = "tcp"
localPort = %d
remotePort = %d
`, serverPort, scriptPath, execValue, f.PortByName(framework.TCPEchoServerPort), remotePort)
serverConfigPath := f.GenerateConfigFile(serverConf)
clientConfigPath := f.GenerateConfigFile(clientConf)
_, _, err := f.RunFrps("-c", serverConfigPath, "--allow-unsafe=TokenSourceExec")
framework.ExpectNoError(err)
_, _, err = f.RunFrpc("-c", clientConfigPath, "--allow-unsafe=TokenSourceExec")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
ginkgo.It("should fail validation without allow-unsafe", func() {
execValue := "exec-unsafe-value"
scriptPath := createExecTokenScript("unsafe_token_exec.sh")
serverPort := f.AllocPort()
serverConf := fmt.Sprintf(`
bindAddr = "0.0.0.0"
bindPort = %d
auth.tokenSource.type = "exec"
auth.tokenSource.exec.command = %q
auth.tokenSource.exec.args = [%q]
`, serverPort, scriptPath, execValue)
serverConfigPath := f.GenerateConfigFile(serverConf)
_, output, err := f.RunFrps("verify", "-c", serverConfigPath)
framework.ExpectNoError(err)
framework.ExpectContainSubstring(output, "unsafe feature \"TokenSourceExec\" is not enabled")
})
})
})