serve_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package ipn
  4. import (
  5. "testing"
  6. "tailscale.com/ipn/ipnstate"
  7. "tailscale.com/tailcfg"
  8. )
  9. func TestCheckFunnelAccess(t *testing.T) {
  10. caps := func(c ...tailcfg.NodeCapability) []tailcfg.NodeCapability { return c }
  11. const portAttr tailcfg.NodeCapability = "https://tailscale.com/cap/funnel-ports?ports=443,8080-8090,8443,"
  12. tests := []struct {
  13. port uint16
  14. caps []tailcfg.NodeCapability
  15. wantErr bool
  16. }{
  17. {443, caps(portAttr), true}, // No "funnel" attribute
  18. {443, caps(portAttr, tailcfg.NodeAttrFunnel), true},
  19. {443, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), false},
  20. {8443, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), false},
  21. {8321, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), true},
  22. {8083, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), false},
  23. {8091, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), true},
  24. {3000, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), true},
  25. }
  26. for _, tt := range tests {
  27. cm := tailcfg.NodeCapMap{}
  28. for _, c := range tt.caps {
  29. cm[c] = nil
  30. }
  31. err := CheckFunnelAccess(tt.port, &ipnstate.PeerStatus{CapMap: cm})
  32. switch {
  33. case err != nil && tt.wantErr,
  34. err == nil && !tt.wantErr:
  35. continue
  36. case tt.wantErr:
  37. t.Fatalf("got no error, want error")
  38. case !tt.wantErr:
  39. t.Fatalf("got error %v, want no error", err)
  40. }
  41. }
  42. }
  43. func TestHasPathHandler(t *testing.T) {
  44. tests := []struct {
  45. name string
  46. cfg ServeConfig
  47. want bool
  48. }{
  49. {
  50. name: "empty-config",
  51. cfg: ServeConfig{},
  52. want: false,
  53. },
  54. {
  55. name: "with-bg-path-handler",
  56. cfg: ServeConfig{
  57. TCP: map[uint16]*TCPPortHandler{80: {HTTP: true}},
  58. Web: map[HostPort]*WebServerConfig{
  59. "foo.test.ts.net:80": {Handlers: map[string]*HTTPHandler{
  60. "/": {Path: "/tmp"},
  61. }},
  62. },
  63. },
  64. want: true,
  65. },
  66. {
  67. name: "with-fg-path-handler",
  68. cfg: ServeConfig{
  69. TCP: map[uint16]*TCPPortHandler{
  70. 443: {HTTPS: true},
  71. },
  72. Foreground: map[string]*ServeConfig{
  73. "abc123": {
  74. TCP: map[uint16]*TCPPortHandler{80: {HTTP: true}},
  75. Web: map[HostPort]*WebServerConfig{
  76. "foo.test.ts.net:80": {Handlers: map[string]*HTTPHandler{
  77. "/": {Path: "/tmp"},
  78. }},
  79. },
  80. },
  81. },
  82. },
  83. want: true,
  84. },
  85. {
  86. name: "with-no-bg-path-handler",
  87. cfg: ServeConfig{
  88. TCP: map[uint16]*TCPPortHandler{443: {HTTPS: true}},
  89. Web: map[HostPort]*WebServerConfig{
  90. "foo.test.ts.net:443": {Handlers: map[string]*HTTPHandler{
  91. "/": {Proxy: "http://127.0.0.1:3000"},
  92. }},
  93. },
  94. AllowFunnel: map[HostPort]bool{"foo.test.ts.net:443": true},
  95. },
  96. want: false,
  97. },
  98. {
  99. name: "with-no-fg-path-handler",
  100. cfg: ServeConfig{
  101. Foreground: map[string]*ServeConfig{
  102. "abc123": {
  103. TCP: map[uint16]*TCPPortHandler{443: {HTTPS: true}},
  104. Web: map[HostPort]*WebServerConfig{
  105. "foo.test.ts.net:443": {Handlers: map[string]*HTTPHandler{
  106. "/": {Proxy: "http://127.0.0.1:3000"},
  107. }},
  108. },
  109. AllowFunnel: map[HostPort]bool{"foo.test.ts.net:443": true},
  110. },
  111. },
  112. },
  113. want: false,
  114. },
  115. {
  116. name: "with-service-path-handler",
  117. cfg: ServeConfig{
  118. Services: map[tailcfg.ServiceName]*ServiceConfig{
  119. "svc:foo": {
  120. Web: map[HostPort]*WebServerConfig{
  121. "foo.test.ts.net:443": {Handlers: map[string]*HTTPHandler{
  122. "/": {Path: "/tmp"},
  123. }},
  124. },
  125. },
  126. },
  127. },
  128. want: true,
  129. },
  130. {
  131. name: "with-service-proxy-handler",
  132. cfg: ServeConfig{
  133. Services: map[tailcfg.ServiceName]*ServiceConfig{
  134. "svc:foo": {
  135. Web: map[HostPort]*WebServerConfig{
  136. "foo.test.ts.net:443": {Handlers: map[string]*HTTPHandler{
  137. "/": {Proxy: "http://127.0.0.1:3000"},
  138. }},
  139. },
  140. },
  141. },
  142. },
  143. want: false,
  144. },
  145. }
  146. for _, tt := range tests {
  147. t.Run(tt.name, func(t *testing.T) {
  148. got := tt.cfg.HasPathHandler()
  149. if tt.want != got {
  150. t.Errorf("HasPathHandler() = %v, want %v", got, tt.want)
  151. }
  152. })
  153. }
  154. }
  155. func TestIsTCPForwardingOnPort(t *testing.T) {
  156. tests := []struct {
  157. name string
  158. cfg ServeConfig
  159. svcName tailcfg.ServiceName
  160. port uint16
  161. want bool
  162. }{
  163. {
  164. name: "empty-config",
  165. cfg: ServeConfig{},
  166. svcName: "",
  167. port: 80,
  168. want: false,
  169. },
  170. {
  171. name: "node-tcp-config-match",
  172. cfg: ServeConfig{
  173. TCP: map[uint16]*TCPPortHandler{80: {TCPForward: "10.0.0.123:3000"}},
  174. },
  175. svcName: "",
  176. port: 80,
  177. want: true,
  178. },
  179. {
  180. name: "node-tcp-config-no-match",
  181. cfg: ServeConfig{
  182. TCP: map[uint16]*TCPPortHandler{80: {TCPForward: "10.0.0.123:3000"}},
  183. },
  184. svcName: "",
  185. port: 443,
  186. want: false,
  187. },
  188. {
  189. name: "node-tcp-config-no-match-with-service",
  190. cfg: ServeConfig{
  191. TCP: map[uint16]*TCPPortHandler{80: {TCPForward: "10.0.0.123:3000"}},
  192. },
  193. svcName: "svc:bar",
  194. port: 80,
  195. want: false,
  196. },
  197. {
  198. name: "node-web-config-no-match",
  199. cfg: ServeConfig{
  200. TCP: map[uint16]*TCPPortHandler{80: {HTTPS: true}},
  201. Web: map[HostPort]*WebServerConfig{
  202. "foo.test.ts.net:80": {
  203. Handlers: map[string]*HTTPHandler{
  204. "/": {Text: "Hello, world!"},
  205. },
  206. },
  207. },
  208. },
  209. svcName: "",
  210. port: 80,
  211. want: false,
  212. },
  213. {
  214. name: "service-tcp-config-match",
  215. cfg: ServeConfig{
  216. Services: map[tailcfg.ServiceName]*ServiceConfig{
  217. "svc:foo": {
  218. TCP: map[uint16]*TCPPortHandler{80: {TCPForward: "10.0.0.123:3000"}},
  219. },
  220. },
  221. },
  222. svcName: "svc:foo",
  223. port: 80,
  224. want: true,
  225. },
  226. {
  227. name: "service-tcp-config-no-match",
  228. cfg: ServeConfig{
  229. Services: map[tailcfg.ServiceName]*ServiceConfig{
  230. "svc:foo": {
  231. TCP: map[uint16]*TCPPortHandler{80: {TCPForward: "10.0.0.123:3000"}},
  232. },
  233. },
  234. },
  235. svcName: "svc:bar",
  236. port: 80,
  237. want: false,
  238. },
  239. {
  240. name: "service-web-config-no-match",
  241. cfg: ServeConfig{
  242. Services: map[tailcfg.ServiceName]*ServiceConfig{
  243. "svc:foo": {
  244. TCP: map[uint16]*TCPPortHandler{80: {HTTPS: true}},
  245. Web: map[HostPort]*WebServerConfig{
  246. "foo.test.ts.net:80": {
  247. Handlers: map[string]*HTTPHandler{
  248. "/": {Text: "Hello, world!"},
  249. },
  250. },
  251. },
  252. },
  253. },
  254. },
  255. svcName: "svc:foo",
  256. port: 80,
  257. want: false,
  258. },
  259. }
  260. for _, tt := range tests {
  261. t.Run(tt.name, func(t *testing.T) {
  262. got := tt.cfg.IsTCPForwardingOnPort(tt.port, tt.svcName)
  263. if tt.want != got {
  264. t.Errorf("IsTCPForwardingOnPort() = %v, want %v", got, tt.want)
  265. }
  266. })
  267. }
  268. }
  269. func TestExpandProxyTargetDev(t *testing.T) {
  270. tests := []struct {
  271. name string
  272. input string
  273. defaultScheme string
  274. supportedSchemes []string
  275. expected string
  276. wantErr bool
  277. }{
  278. {name: "port-only", input: "8080", expected: "http://127.0.0.1:8080"},
  279. {name: "hostname+port", input: "localhost:8080", expected: "http://localhost:8080"},
  280. {name: "no-change", input: "http://127.0.0.1:8080", expected: "http://127.0.0.1:8080"},
  281. {name: "include-path", input: "http://127.0.0.1:8080/foo", expected: "http://127.0.0.1:8080/foo"},
  282. {name: "https-scheme", input: "https://localhost:8080", expected: "https://localhost:8080"},
  283. {name: "https+insecure-scheme", input: "https+insecure://localhost:8080", expected: "https+insecure://localhost:8080"},
  284. {name: "change-default-scheme", input: "localhost:8080", defaultScheme: "https", expected: "https://localhost:8080"},
  285. {name: "change-supported-schemes", input: "localhost:8080", defaultScheme: "tcp", supportedSchemes: []string{"tcp"}, expected: "tcp://localhost:8080"},
  286. {name: "remote-target", input: "https://example.com:8080", expected: "https://example.com:8080"},
  287. {name: "remote-IP-target", input: "http://120.133.20.2:8080", expected: "http://120.133.20.2:8080"},
  288. {name: "remote-target-no-port", input: "https://example.com", expected: "https://example.com"},
  289. // errors
  290. {name: "invalid-port", input: "localhost:9999999", wantErr: true},
  291. {name: "invalid-hostname", input: "192.168.1:8080", wantErr: true},
  292. {name: "unsupported-scheme", input: "ftp://localhost:8080", expected: "", wantErr: true},
  293. {name: "empty-input", input: "", expected: "", wantErr: true},
  294. {name: "localhost-no-port", input: "localhost", expected: "", wantErr: true},
  295. }
  296. for _, tt := range tests {
  297. defaultScheme := "http"
  298. supportedSchemes := []string{"http", "https", "https+insecure"}
  299. if tt.supportedSchemes != nil {
  300. supportedSchemes = tt.supportedSchemes
  301. }
  302. if tt.defaultScheme != "" {
  303. defaultScheme = tt.defaultScheme
  304. }
  305. t.Run(tt.name, func(t *testing.T) {
  306. actual, err := ExpandProxyTargetValue(tt.input, supportedSchemes, defaultScheme)
  307. if tt.wantErr == true && err == nil {
  308. t.Errorf("Expected an error but got none")
  309. return
  310. }
  311. if tt.wantErr == false && err != nil {
  312. t.Errorf("Got an error, but didn't expect one: %v", err)
  313. return
  314. }
  315. if actual != tt.expected {
  316. t.Errorf("Got: %q; expected: %q", actual, tt.expected)
  317. }
  318. })
  319. }
  320. }
  321. func TestIsFunnelOn(t *testing.T) {
  322. tests := []struct {
  323. name string
  324. sc *ServeConfig
  325. want bool
  326. }{
  327. {
  328. name: "nil_config",
  329. },
  330. {
  331. name: "empty_config",
  332. sc: &ServeConfig{},
  333. },
  334. {
  335. name: "funnel_enabled_in_background",
  336. sc: &ServeConfig{
  337. AllowFunnel: map[HostPort]bool{
  338. "tailnet.xyz:443": true,
  339. },
  340. },
  341. want: true,
  342. },
  343. {
  344. name: "funnel_disabled_in_background",
  345. sc: &ServeConfig{
  346. AllowFunnel: map[HostPort]bool{
  347. "tailnet.xyz:443": false,
  348. },
  349. },
  350. },
  351. {
  352. name: "funnel_enabled_in_foreground",
  353. sc: &ServeConfig{
  354. Foreground: map[string]*ServeConfig{
  355. "abc123": {
  356. AllowFunnel: map[HostPort]bool{
  357. "tailnet.xyz:443": true,
  358. },
  359. },
  360. },
  361. },
  362. want: true,
  363. },
  364. {
  365. name: "funnel_disabled_in_both",
  366. sc: &ServeConfig{
  367. AllowFunnel: map[HostPort]bool{
  368. "tailnet.xyz:443": false,
  369. },
  370. Foreground: map[string]*ServeConfig{
  371. "abc123": {
  372. AllowFunnel: map[HostPort]bool{
  373. "tailnet.xyz:8443": false,
  374. },
  375. },
  376. },
  377. },
  378. },
  379. {
  380. name: "funnel_enabled_in_both",
  381. sc: &ServeConfig{
  382. AllowFunnel: map[HostPort]bool{
  383. "tailnet.xyz:443": true,
  384. },
  385. Foreground: map[string]*ServeConfig{
  386. "abc123": {
  387. AllowFunnel: map[HostPort]bool{
  388. "tailnet.xyz:8443": true,
  389. },
  390. },
  391. },
  392. },
  393. want: true,
  394. },
  395. }
  396. for _, tt := range tests {
  397. t.Run(tt.name, func(t *testing.T) {
  398. if got := tt.sc.IsFunnelOn(); got != tt.want {
  399. t.Errorf("ServeConfig.IsFunnelOn() = %v, want %v", got, tt.want)
  400. }
  401. })
  402. }
  403. }