token_source.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. // Copyright 2025 The frp Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package basic
  15. import (
  16. "fmt"
  17. "os"
  18. "path/filepath"
  19. "github.com/onsi/ginkgo/v2"
  20. "github.com/fatedier/frp/test/e2e/framework"
  21. "github.com/fatedier/frp/test/e2e/framework/consts"
  22. "github.com/fatedier/frp/test/e2e/pkg/port"
  23. )
  24. var _ = ginkgo.Describe("[Feature: TokenSource]", func() {
  25. f := framework.NewDefaultFramework()
  26. createExecTokenScript := func(name string) string {
  27. scriptPath := filepath.Join(f.TempDirectory, name)
  28. scriptContent := `#!/bin/sh
  29. printf '%s\n' "$1"
  30. `
  31. err := os.WriteFile(scriptPath, []byte(scriptContent), 0o600)
  32. framework.ExpectNoError(err)
  33. err = os.Chmod(scriptPath, 0o700)
  34. framework.ExpectNoError(err)
  35. return scriptPath
  36. }
  37. ginkgo.Describe("File-based token loading", func() {
  38. ginkgo.It("should work with file tokenSource", func() {
  39. // Create a temporary token file
  40. tmpDir := f.TempDirectory
  41. tokenFile := filepath.Join(tmpDir, "test_token")
  42. tokenContent := "test-token-123"
  43. err := os.WriteFile(tokenFile, []byte(tokenContent), 0o600)
  44. framework.ExpectNoError(err)
  45. serverConf := consts.DefaultServerConfig
  46. clientConf := consts.DefaultClientConfig
  47. portName := port.GenName("TCP")
  48. // Server config with tokenSource
  49. serverConf += fmt.Sprintf(`
  50. auth.tokenSource.type = "file"
  51. auth.tokenSource.file.path = "%s"
  52. `, tokenFile)
  53. // Client config with matching token
  54. clientConf += fmt.Sprintf(`
  55. auth.token = "%s"
  56. [[proxies]]
  57. name = "tcp"
  58. type = "tcp"
  59. localPort = {{ .%s }}
  60. remotePort = {{ .%s }}
  61. `, tokenContent, framework.TCPEchoServerPort, portName)
  62. f.RunProcesses([]string{serverConf}, []string{clientConf})
  63. framework.NewRequestExpect(f).PortName(portName).Ensure()
  64. })
  65. ginkgo.It("should work with client tokenSource", func() {
  66. // Create a temporary token file
  67. tmpDir := f.TempDirectory
  68. tokenFile := filepath.Join(tmpDir, "client_token")
  69. tokenContent := "client-token-456"
  70. err := os.WriteFile(tokenFile, []byte(tokenContent), 0o600)
  71. framework.ExpectNoError(err)
  72. serverConf := consts.DefaultServerConfig
  73. clientConf := consts.DefaultClientConfig
  74. portName := port.GenName("TCP")
  75. // Server config with matching token
  76. serverConf += fmt.Sprintf(`
  77. auth.token = "%s"
  78. `, tokenContent)
  79. // Client config with tokenSource
  80. clientConf += fmt.Sprintf(`
  81. auth.tokenSource.type = "file"
  82. auth.tokenSource.file.path = "%s"
  83. [[proxies]]
  84. name = "tcp"
  85. type = "tcp"
  86. localPort = {{ .%s }}
  87. remotePort = {{ .%s }}
  88. `, tokenFile, framework.TCPEchoServerPort, portName)
  89. f.RunProcesses([]string{serverConf}, []string{clientConf})
  90. framework.NewRequestExpect(f).PortName(portName).Ensure()
  91. })
  92. ginkgo.It("should work with both server and client tokenSource", func() {
  93. // Create temporary token files
  94. tmpDir := f.TempDirectory
  95. serverTokenFile := filepath.Join(tmpDir, "server_token")
  96. clientTokenFile := filepath.Join(tmpDir, "client_token")
  97. tokenContent := "shared-token-789"
  98. err := os.WriteFile(serverTokenFile, []byte(tokenContent), 0o600)
  99. framework.ExpectNoError(err)
  100. err = os.WriteFile(clientTokenFile, []byte(tokenContent), 0o600)
  101. framework.ExpectNoError(err)
  102. serverConf := consts.DefaultServerConfig
  103. clientConf := consts.DefaultClientConfig
  104. portName := port.GenName("TCP")
  105. // Server config with tokenSource
  106. serverConf += fmt.Sprintf(`
  107. auth.tokenSource.type = "file"
  108. auth.tokenSource.file.path = "%s"
  109. `, serverTokenFile)
  110. // Client config with tokenSource
  111. clientConf += fmt.Sprintf(`
  112. auth.tokenSource.type = "file"
  113. auth.tokenSource.file.path = "%s"
  114. [[proxies]]
  115. name = "tcp"
  116. type = "tcp"
  117. localPort = {{ .%s }}
  118. remotePort = {{ .%s }}
  119. `, clientTokenFile, framework.TCPEchoServerPort, portName)
  120. f.RunProcesses([]string{serverConf}, []string{clientConf})
  121. framework.NewRequestExpect(f).PortName(portName).Ensure()
  122. })
  123. ginkgo.It("should fail with mismatched tokens", func() {
  124. // Create temporary token files with different content
  125. tmpDir := f.TempDirectory
  126. serverTokenFile := filepath.Join(tmpDir, "server_token")
  127. clientTokenFile := filepath.Join(tmpDir, "client_token")
  128. err := os.WriteFile(serverTokenFile, []byte("server-token"), 0o600)
  129. framework.ExpectNoError(err)
  130. err = os.WriteFile(clientTokenFile, []byte("client-token"), 0o600)
  131. framework.ExpectNoError(err)
  132. serverConf := consts.DefaultServerConfig
  133. clientConf := consts.DefaultClientConfig
  134. portName := port.GenName("TCP")
  135. // Server config with tokenSource
  136. serverConf += fmt.Sprintf(`
  137. auth.tokenSource.type = "file"
  138. auth.tokenSource.file.path = "%s"
  139. `, serverTokenFile)
  140. // Client config with different tokenSource
  141. clientConf += fmt.Sprintf(`
  142. auth.tokenSource.type = "file"
  143. auth.tokenSource.file.path = "%s"
  144. [[proxies]]
  145. name = "tcp"
  146. type = "tcp"
  147. localPort = {{ .%s }}
  148. remotePort = {{ .%s }}
  149. `, clientTokenFile, framework.TCPEchoServerPort, portName)
  150. f.RunProcesses([]string{serverConf}, []string{clientConf})
  151. // This should fail due to token mismatch - the client should not be able to connect
  152. // We expect the request to fail because the proxy tunnel is not established
  153. framework.NewRequestExpect(f).PortName(portName).ExpectError(true).Ensure()
  154. })
  155. ginkgo.It("should fail with non-existent token file", func() {
  156. // This test verifies that server fails to start when tokenSource points to non-existent file
  157. // We'll verify this by checking that the configuration loading itself fails
  158. // Create a config that references a non-existent file
  159. tmpDir := f.TempDirectory
  160. nonExistentFile := filepath.Join(tmpDir, "non_existent_token")
  161. serverConf := consts.DefaultServerConfig
  162. // Server config with non-existent tokenSource file
  163. serverConf += fmt.Sprintf(`
  164. auth.tokenSource.type = "file"
  165. auth.tokenSource.file.path = "%s"
  166. `, nonExistentFile)
  167. // The test expectation is that this will fail during the RunProcesses call
  168. // because the server cannot load the configuration due to missing token file
  169. defer func() {
  170. if r := recover(); r != nil {
  171. // Expected: server should fail to start due to missing file
  172. ginkgo.By(fmt.Sprintf("Server correctly failed to start: %v", r))
  173. }
  174. }()
  175. // This should cause a panic or error during server startup
  176. f.RunProcesses([]string{serverConf}, []string{})
  177. })
  178. })
  179. ginkgo.Describe("Exec-based token loading", func() {
  180. ginkgo.It("should work with server tokenSource", func() {
  181. execValue := "exec-server-value"
  182. scriptPath := createExecTokenScript("server_token_exec.sh")
  183. serverPort := f.AllocPort()
  184. remotePort := f.AllocPort()
  185. serverConf := fmt.Sprintf(`
  186. bindAddr = "0.0.0.0"
  187. bindPort = %d
  188. auth.tokenSource.type = "exec"
  189. auth.tokenSource.exec.command = %q
  190. auth.tokenSource.exec.args = [%q]
  191. `, serverPort, scriptPath, execValue)
  192. clientConf := fmt.Sprintf(`
  193. serverAddr = "127.0.0.1"
  194. serverPort = %d
  195. loginFailExit = false
  196. auth.token = %q
  197. [[proxies]]
  198. name = "tcp"
  199. type = "tcp"
  200. localPort = %d
  201. remotePort = %d
  202. `, serverPort, execValue, f.PortByName(framework.TCPEchoServerPort), remotePort)
  203. serverConfigPath := f.GenerateConfigFile(serverConf)
  204. clientConfigPath := f.GenerateConfigFile(clientConf)
  205. _, _, err := f.RunFrps("-c", serverConfigPath, "--allow-unsafe=TokenSourceExec")
  206. framework.ExpectNoError(err)
  207. _, _, err = f.RunFrpc("-c", clientConfigPath, "--allow-unsafe=TokenSourceExec")
  208. framework.ExpectNoError(err)
  209. framework.NewRequestExpect(f).Port(remotePort).Ensure()
  210. })
  211. ginkgo.It("should work with client tokenSource", func() {
  212. execValue := "exec-client-value"
  213. scriptPath := createExecTokenScript("client_token_exec.sh")
  214. serverPort := f.AllocPort()
  215. remotePort := f.AllocPort()
  216. serverConf := fmt.Sprintf(`
  217. bindAddr = "0.0.0.0"
  218. bindPort = %d
  219. auth.token = %q
  220. `, serverPort, execValue)
  221. clientConf := fmt.Sprintf(`
  222. serverAddr = "127.0.0.1"
  223. serverPort = %d
  224. loginFailExit = false
  225. auth.tokenSource.type = "exec"
  226. auth.tokenSource.exec.command = %q
  227. auth.tokenSource.exec.args = [%q]
  228. [[proxies]]
  229. name = "tcp"
  230. type = "tcp"
  231. localPort = %d
  232. remotePort = %d
  233. `, serverPort, scriptPath, execValue, f.PortByName(framework.TCPEchoServerPort), remotePort)
  234. serverConfigPath := f.GenerateConfigFile(serverConf)
  235. clientConfigPath := f.GenerateConfigFile(clientConf)
  236. _, _, err := f.RunFrps("-c", serverConfigPath, "--allow-unsafe=TokenSourceExec")
  237. framework.ExpectNoError(err)
  238. _, _, err = f.RunFrpc("-c", clientConfigPath, "--allow-unsafe=TokenSourceExec")
  239. framework.ExpectNoError(err)
  240. framework.NewRequestExpect(f).Port(remotePort).Ensure()
  241. })
  242. ginkgo.It("should work with both server and client tokenSource", func() {
  243. execValue := "exec-shared-value"
  244. scriptPath := createExecTokenScript("shared_token_exec.sh")
  245. serverPort := f.AllocPort()
  246. remotePort := f.AllocPort()
  247. serverConf := fmt.Sprintf(`
  248. bindAddr = "0.0.0.0"
  249. bindPort = %d
  250. auth.tokenSource.type = "exec"
  251. auth.tokenSource.exec.command = %q
  252. auth.tokenSource.exec.args = [%q]
  253. `, serverPort, scriptPath, execValue)
  254. clientConf := fmt.Sprintf(`
  255. serverAddr = "127.0.0.1"
  256. serverPort = %d
  257. loginFailExit = false
  258. auth.tokenSource.type = "exec"
  259. auth.tokenSource.exec.command = %q
  260. auth.tokenSource.exec.args = [%q]
  261. [[proxies]]
  262. name = "tcp"
  263. type = "tcp"
  264. localPort = %d
  265. remotePort = %d
  266. `, serverPort, scriptPath, execValue, f.PortByName(framework.TCPEchoServerPort), remotePort)
  267. serverConfigPath := f.GenerateConfigFile(serverConf)
  268. clientConfigPath := f.GenerateConfigFile(clientConf)
  269. _, _, err := f.RunFrps("-c", serverConfigPath, "--allow-unsafe=TokenSourceExec")
  270. framework.ExpectNoError(err)
  271. _, _, err = f.RunFrpc("-c", clientConfigPath, "--allow-unsafe=TokenSourceExec")
  272. framework.ExpectNoError(err)
  273. framework.NewRequestExpect(f).Port(remotePort).Ensure()
  274. })
  275. ginkgo.It("should fail validation without allow-unsafe", func() {
  276. execValue := "exec-unsafe-value"
  277. scriptPath := createExecTokenScript("unsafe_token_exec.sh")
  278. serverPort := f.AllocPort()
  279. serverConf := fmt.Sprintf(`
  280. bindAddr = "0.0.0.0"
  281. bindPort = %d
  282. auth.tokenSource.type = "exec"
  283. auth.tokenSource.exec.command = %q
  284. auth.tokenSource.exec.args = [%q]
  285. `, serverPort, scriptPath, execValue)
  286. serverConfigPath := f.GenerateConfigFile(serverConf)
  287. _, output, err := f.RunFrps("verify", "-c", serverConfigPath)
  288. framework.ExpectNoError(err)
  289. framework.ExpectContainSubstring(output, "unsafe feature \"TokenSourceExec\" is not enabled")
  290. })
  291. })
  292. })