ingress_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. package ingress
  2. import (
  3. "flag"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "regexp"
  8. "testing"
  9. "time"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "github.com/urfave/cli/v2"
  13. yaml "gopkg.in/yaml.v3"
  14. "github.com/cloudflare/cloudflared/config"
  15. "github.com/cloudflare/cloudflared/ipaccess"
  16. "github.com/cloudflare/cloudflared/tlsconfig"
  17. )
  18. func TestParseUnixSocket(t *testing.T) {
  19. rawYAML := `
  20. ingress:
  21. - service: unix:/tmp/echo.sock
  22. `
  23. ing, err := ParseIngress(MustReadIngress(rawYAML))
  24. require.NoError(t, err)
  25. s, ok := ing.Rules[0].Service.(*unixSocketPath)
  26. require.True(t, ok)
  27. require.Equal(t, "http", s.scheme)
  28. }
  29. func TestParseUnixSocketTLS(t *testing.T) {
  30. rawYAML := `
  31. ingress:
  32. - service: unix+tls:/tmp/echo.sock
  33. `
  34. ing, err := ParseIngress(MustReadIngress(rawYAML))
  35. require.NoError(t, err)
  36. s, ok := ing.Rules[0].Service.(*unixSocketPath)
  37. require.True(t, ok)
  38. require.Equal(t, "https", s.scheme)
  39. }
  40. func TestParseIngressNilConfig(t *testing.T) {
  41. _, err := ParseIngress(nil)
  42. require.Error(t, err)
  43. }
  44. func TestParseIngress(t *testing.T) {
  45. localhost8000 := MustParseURL(t, "https://localhost:8000")
  46. localhost8001 := MustParseURL(t, "https://localhost:8001")
  47. fourOhFour := newStatusCode(404)
  48. defaultConfig := setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{})
  49. require.Equal(t, defaultKeepAliveConnections, defaultConfig.KeepAliveConnections)
  50. tr := true
  51. type args struct {
  52. rawYAML string
  53. }
  54. tests := []struct {
  55. name string
  56. args args
  57. want []Rule
  58. wantErr bool
  59. }{
  60. {
  61. name: "Empty file",
  62. args: args{rawYAML: ""},
  63. wantErr: true,
  64. },
  65. {
  66. name: "Multiple rules",
  67. args: args{rawYAML: `
  68. ingress:
  69. - hostname: tunnel1.example.com
  70. service: https://localhost:8000
  71. - hostname: "*"
  72. service: https://localhost:8001
  73. `},
  74. want: []Rule{
  75. {
  76. Hostname: "tunnel1.example.com",
  77. Service: &httpService{url: localhost8000},
  78. Config: defaultConfig,
  79. },
  80. {
  81. Hostname: "*",
  82. Service: &httpService{url: localhost8001},
  83. Config: defaultConfig,
  84. },
  85. },
  86. },
  87. {
  88. name: "Extra keys",
  89. args: args{rawYAML: `
  90. ingress:
  91. - hostname: "*"
  92. service: https://localhost:8000
  93. extraKey: extraValue
  94. `},
  95. want: []Rule{
  96. {
  97. Hostname: "*",
  98. Service: &httpService{url: localhost8000},
  99. Config: defaultConfig,
  100. },
  101. },
  102. },
  103. {
  104. name: "ws service",
  105. args: args{rawYAML: `
  106. ingress:
  107. - hostname: "*"
  108. service: wss://localhost:8000
  109. `},
  110. want: []Rule{
  111. {
  112. Hostname: "*",
  113. Service: &httpService{url: MustParseURL(t, "wss://localhost:8000")},
  114. Config: defaultConfig,
  115. },
  116. },
  117. },
  118. {
  119. name: "Hostname can be omitted",
  120. args: args{rawYAML: `
  121. ingress:
  122. - service: https://localhost:8000
  123. `},
  124. want: []Rule{
  125. {
  126. Service: &httpService{url: localhost8000},
  127. Config: defaultConfig,
  128. },
  129. },
  130. },
  131. {
  132. name: "Unicode domain",
  133. args: args{rawYAML: `
  134. ingress:
  135. - hostname: môô.cloudflare.com
  136. service: https://localhost:8000
  137. - service: https://localhost:8001
  138. `},
  139. want: []Rule{
  140. {
  141. Hostname: "môô.cloudflare.com",
  142. punycodeHostname: "xn--m-xgaa.cloudflare.com",
  143. Service: &httpService{url: localhost8000},
  144. Config: defaultConfig,
  145. },
  146. {
  147. Service: &httpService{url: localhost8001},
  148. Config: defaultConfig,
  149. },
  150. },
  151. },
  152. {
  153. name: "Invalid unicode domain",
  154. args: args{rawYAML: fmt.Sprintf(`
  155. ingress:
  156. - hostname: %s
  157. service: https://localhost:8000
  158. `, string(rune(0xd8f3))+".cloudflare.com")},
  159. wantErr: true,
  160. },
  161. {
  162. name: "Invalid service",
  163. args: args{rawYAML: `
  164. ingress:
  165. - hostname: "*"
  166. service: https://local host:8000
  167. `},
  168. wantErr: true,
  169. },
  170. {
  171. name: "Last rule isn't catchall",
  172. args: args{rawYAML: `
  173. ingress:
  174. - hostname: example.com
  175. service: https://localhost:8000
  176. `},
  177. wantErr: true,
  178. },
  179. {
  180. name: "First rule is catchall",
  181. args: args{rawYAML: `
  182. ingress:
  183. - service: https://localhost:8000
  184. - hostname: example.com
  185. service: https://localhost:8000
  186. `},
  187. wantErr: true,
  188. },
  189. {
  190. name: "Catch-all rule can't have a path",
  191. args: args{rawYAML: `
  192. ingress:
  193. - service: https://localhost:8001
  194. path: /subpath1/(.*)/subpath2
  195. `},
  196. wantErr: true,
  197. },
  198. {
  199. name: "Invalid regex",
  200. args: args{rawYAML: `
  201. ingress:
  202. - hostname: example.com
  203. service: https://localhost:8000
  204. path: "*/subpath2"
  205. - service: https://localhost:8001
  206. `},
  207. wantErr: true,
  208. },
  209. {
  210. name: "Service must have a scheme",
  211. args: args{rawYAML: `
  212. ingress:
  213. - service: localhost:8000
  214. `},
  215. wantErr: true,
  216. },
  217. {
  218. name: "Wildcard not at start",
  219. args: args{rawYAML: `
  220. ingress:
  221. - hostname: "test.*.example.com"
  222. service: https://localhost:8000
  223. `},
  224. wantErr: true,
  225. },
  226. {
  227. name: "Service can't have a path",
  228. args: args{rawYAML: `
  229. ingress:
  230. - service: https://localhost:8000/static/
  231. `},
  232. wantErr: true,
  233. },
  234. {
  235. name: "Invalid HTTP status",
  236. args: args{rawYAML: `
  237. ingress:
  238. - service: http_status:asdf
  239. `},
  240. wantErr: true,
  241. },
  242. {
  243. name: "Invalid HTTP status code",
  244. args: args{rawYAML: `
  245. ingress:
  246. - service: http_status:8080
  247. `},
  248. wantErr: true,
  249. },
  250. {
  251. name: "Valid HTTP status",
  252. args: args{rawYAML: `
  253. ingress:
  254. - service: http_status:404
  255. `},
  256. want: []Rule{
  257. {
  258. Hostname: "",
  259. Service: &fourOhFour,
  260. Config: defaultConfig,
  261. },
  262. },
  263. },
  264. {
  265. name: "Valid hello world service",
  266. args: args{rawYAML: `
  267. ingress:
  268. - service: hello_world
  269. `},
  270. want: []Rule{
  271. {
  272. Hostname: "",
  273. Service: new(helloWorld),
  274. Config: defaultConfig,
  275. },
  276. },
  277. },
  278. {
  279. name: "TCP services",
  280. args: args{rawYAML: `
  281. ingress:
  282. - hostname: tcp.foo.com
  283. service: tcp://127.0.0.1
  284. - hostname: tcp2.foo.com
  285. service: tcp://localhost:8000
  286. - service: http_status:404
  287. `},
  288. want: []Rule{
  289. {
  290. Hostname: "tcp.foo.com",
  291. Service: newTCPOverWSService(MustParseURL(t, "tcp://127.0.0.1:7864")),
  292. Config: defaultConfig,
  293. },
  294. {
  295. Hostname: "tcp2.foo.com",
  296. Service: newTCPOverWSService(MustParseURL(t, "tcp://localhost:8000")),
  297. Config: defaultConfig,
  298. },
  299. {
  300. Service: &fourOhFour,
  301. Config: defaultConfig,
  302. },
  303. },
  304. },
  305. {
  306. name: "SSH services",
  307. args: args{rawYAML: `
  308. ingress:
  309. - service: ssh://127.0.0.1
  310. `},
  311. want: []Rule{
  312. {
  313. Service: newTCPOverWSService(MustParseURL(t, "ssh://127.0.0.1:22")),
  314. Config: defaultConfig,
  315. },
  316. },
  317. },
  318. {
  319. name: "RDP services",
  320. args: args{rawYAML: `
  321. ingress:
  322. - service: rdp://127.0.0.1
  323. `},
  324. want: []Rule{
  325. {
  326. Service: newTCPOverWSService(MustParseURL(t, "rdp://127.0.0.1:3389")),
  327. Config: defaultConfig,
  328. },
  329. },
  330. },
  331. {
  332. name: "SMB services",
  333. args: args{rawYAML: `
  334. ingress:
  335. - service: smb://127.0.0.1
  336. `},
  337. want: []Rule{
  338. {
  339. Service: newTCPOverWSService(MustParseURL(t, "smb://127.0.0.1:445")),
  340. Config: defaultConfig,
  341. },
  342. },
  343. },
  344. {
  345. name: "Other TCP services",
  346. args: args{rawYAML: `
  347. ingress:
  348. - service: ftp://127.0.0.1
  349. `},
  350. want: []Rule{
  351. {
  352. Service: newTCPOverWSService(MustParseURL(t, "ftp://127.0.0.1")),
  353. Config: defaultConfig,
  354. },
  355. },
  356. },
  357. {
  358. name: "SOCKS services",
  359. args: args{rawYAML: `
  360. ingress:
  361. - hostname: socks.foo.com
  362. service: socks-proxy
  363. originRequest:
  364. ipRules:
  365. - prefix: 1.1.1.0/24
  366. ports: [80, 443]
  367. allow: true
  368. - prefix: 0.0.0.0/0
  369. allow: false
  370. - service: http_status:404
  371. `},
  372. want: []Rule{
  373. {
  374. Hostname: "socks.foo.com",
  375. Service: newSocksProxyOverWSService(accessPolicy()),
  376. Config: setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{IPRules: []config.IngressIPRule{
  377. {
  378. Prefix: ipRulePrefix("1.1.1.0/24"),
  379. Ports: []int{80, 443},
  380. Allow: true,
  381. },
  382. {
  383. Prefix: ipRulePrefix("0.0.0.0/0"),
  384. Allow: false,
  385. },
  386. }}),
  387. },
  388. {
  389. Service: &fourOhFour,
  390. Config: defaultConfig,
  391. },
  392. },
  393. },
  394. {
  395. name: "URL isn't necessary if using bastion",
  396. args: args{rawYAML: `
  397. ingress:
  398. - hostname: bastion.foo.com
  399. originRequest:
  400. bastionMode: true
  401. - service: http_status:404
  402. `},
  403. want: []Rule{
  404. {
  405. Hostname: "bastion.foo.com",
  406. Service: newBastionService(),
  407. Config: setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}),
  408. },
  409. {
  410. Service: &fourOhFour,
  411. Config: defaultConfig,
  412. },
  413. },
  414. },
  415. {
  416. name: "Bastion service",
  417. args: args{rawYAML: `
  418. ingress:
  419. - hostname: bastion.foo.com
  420. service: bastion
  421. - service: http_status:404
  422. `},
  423. want: []Rule{
  424. {
  425. Hostname: "bastion.foo.com",
  426. Service: newBastionService(),
  427. Config: setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}),
  428. },
  429. {
  430. Service: &fourOhFour,
  431. Config: defaultConfig,
  432. },
  433. },
  434. },
  435. {
  436. name: "Hostname contains port",
  437. args: args{rawYAML: `
  438. ingress:
  439. - hostname: "test.example.com:443"
  440. service: https://localhost:8000
  441. - hostname: "*"
  442. service: https://localhost:8001
  443. `},
  444. wantErr: true,
  445. },
  446. }
  447. for _, tt := range tests {
  448. t.Run(tt.name, func(t *testing.T) {
  449. got, err := ParseIngress(MustReadIngress(tt.args.rawYAML))
  450. if (err != nil) != tt.wantErr {
  451. t.Errorf("ParseIngress() error = %v, wantErr %v", err, tt.wantErr)
  452. return
  453. }
  454. require.Equal(t, tt.want, got.Rules)
  455. })
  456. }
  457. }
  458. func ipRulePrefix(s string) *string {
  459. return &s
  460. }
  461. func TestSingleOriginSetsConfig(t *testing.T) {
  462. flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError)
  463. flagSet.Bool("hello-world", true, "")
  464. flagSet.Duration(ProxyConnectTimeoutFlag, time.Second, "")
  465. flagSet.Duration(ProxyTLSTimeoutFlag, time.Second, "")
  466. flagSet.Duration(ProxyTCPKeepAliveFlag, time.Second, "")
  467. flagSet.Bool(ProxyNoHappyEyeballsFlag, true, "")
  468. flagSet.Int(ProxyKeepAliveConnectionsFlag, 10, "")
  469. flagSet.Duration(ProxyKeepAliveTimeoutFlag, time.Second, "")
  470. flagSet.String(HTTPHostHeaderFlag, "example.com:8080", "")
  471. flagSet.String(OriginServerNameFlag, "example.com", "")
  472. flagSet.String(tlsconfig.OriginCAPoolFlag, "/etc/certs/ca.pem", "")
  473. flagSet.Bool(NoTLSVerifyFlag, true, "")
  474. flagSet.Bool(NoChunkedEncodingFlag, true, "")
  475. flagSet.Bool(config.BastionFlag, true, "")
  476. flagSet.String(ProxyAddressFlag, "localhost:8080", "")
  477. flagSet.Uint(ProxyPortFlag, 8080, "")
  478. flagSet.Bool(Socks5Flag, true, "")
  479. cliCtx := cli.NewContext(cli.NewApp(), flagSet, nil)
  480. err := cliCtx.Set("hello-world", "true")
  481. require.NoError(t, err)
  482. err = cliCtx.Set(ProxyConnectTimeoutFlag, "1s")
  483. require.NoError(t, err)
  484. err = cliCtx.Set(ProxyTLSTimeoutFlag, "1s")
  485. require.NoError(t, err)
  486. err = cliCtx.Set(ProxyTCPKeepAliveFlag, "1s")
  487. require.NoError(t, err)
  488. err = cliCtx.Set(ProxyNoHappyEyeballsFlag, "true")
  489. require.NoError(t, err)
  490. err = cliCtx.Set(ProxyKeepAliveConnectionsFlag, "10")
  491. require.NoError(t, err)
  492. err = cliCtx.Set(ProxyKeepAliveTimeoutFlag, "1s")
  493. require.NoError(t, err)
  494. err = cliCtx.Set(HTTPHostHeaderFlag, "example.com:8080")
  495. require.NoError(t, err)
  496. err = cliCtx.Set(OriginServerNameFlag, "example.com")
  497. require.NoError(t, err)
  498. err = cliCtx.Set(tlsconfig.OriginCAPoolFlag, "/etc/certs/ca.pem")
  499. require.NoError(t, err)
  500. err = cliCtx.Set(NoTLSVerifyFlag, "true")
  501. require.NoError(t, err)
  502. err = cliCtx.Set(NoChunkedEncodingFlag, "true")
  503. require.NoError(t, err)
  504. err = cliCtx.Set(config.BastionFlag, "true")
  505. require.NoError(t, err)
  506. err = cliCtx.Set(ProxyAddressFlag, "localhost:8080")
  507. require.NoError(t, err)
  508. err = cliCtx.Set(ProxyPortFlag, "8080")
  509. require.NoError(t, err)
  510. err = cliCtx.Set(Socks5Flag, "true")
  511. require.NoError(t, err)
  512. allowURLFromArgs := false
  513. require.NoError(t, err)
  514. ingress, err := parseCLIIngress(cliCtx, allowURLFromArgs)
  515. require.NoError(t, err)
  516. assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.ConnectTimeout)
  517. assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.TLSTimeout)
  518. assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.TCPKeepAlive)
  519. assert.True(t, ingress.Rules[0].Config.NoHappyEyeballs)
  520. assert.Equal(t, 10, ingress.Rules[0].Config.KeepAliveConnections)
  521. assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.KeepAliveTimeout)
  522. assert.Equal(t, "example.com:8080", ingress.Rules[0].Config.HTTPHostHeader)
  523. assert.Equal(t, "example.com", ingress.Rules[0].Config.OriginServerName)
  524. assert.Equal(t, "/etc/certs/ca.pem", ingress.Rules[0].Config.CAPool)
  525. assert.True(t, ingress.Rules[0].Config.NoTLSVerify)
  526. assert.True(t, ingress.Rules[0].Config.DisableChunkedEncoding)
  527. assert.True(t, ingress.Rules[0].Config.BastionMode)
  528. assert.Equal(t, "localhost:8080", ingress.Rules[0].Config.ProxyAddress)
  529. assert.Equal(t, uint(8080), ingress.Rules[0].Config.ProxyPort)
  530. assert.Equal(t, socksProxy, ingress.Rules[0].Config.ProxyType)
  531. }
  532. func TestSingleOriginServices(t *testing.T) {
  533. host := "://localhost:8080"
  534. httpURL := urlMustParse("http" + host)
  535. tcpURL := urlMustParse("tcp" + host)
  536. unix := "unix://service"
  537. newCli := func(params ...string) *cli.Context {
  538. flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError)
  539. flagSet.Bool("hello-world", false, "")
  540. flagSet.Bool("bastion", false, "")
  541. flagSet.String("url", "", "")
  542. flagSet.String("unix-socket", "", "")
  543. cliCtx := cli.NewContext(cli.NewApp(), flagSet, nil)
  544. for i := 0; i < len(params); i += 2 {
  545. cliCtx.Set(params[i], params[i+1])
  546. }
  547. return cliCtx
  548. }
  549. tests := []struct {
  550. name string
  551. cli *cli.Context
  552. expectedService OriginService
  553. err error
  554. }{
  555. {
  556. name: "Valid hello-world",
  557. cli: newCli("hello-world", "true"),
  558. expectedService: &helloWorld{},
  559. },
  560. {
  561. name: "Valid bastion",
  562. cli: newCli("bastion", "true"),
  563. expectedService: newBastionService(),
  564. },
  565. {
  566. name: "Valid http url",
  567. cli: newCli("url", httpURL.String()),
  568. expectedService: &httpService{url: httpURL},
  569. },
  570. {
  571. name: "Valid tcp url",
  572. cli: newCli("url", tcpURL.String()),
  573. expectedService: newTCPOverWSService(tcpURL),
  574. },
  575. {
  576. name: "Valid unix-socket",
  577. cli: newCli("unix-socket", unix),
  578. expectedService: &unixSocketPath{path: unix, scheme: "http"},
  579. },
  580. {
  581. name: "No origins defined",
  582. cli: newCli(),
  583. err: ErrNoIngressRulesCLI,
  584. },
  585. }
  586. for _, test := range tests {
  587. t.Run(test.name, func(t *testing.T) {
  588. ingress, err := parseCLIIngress(test.cli, false)
  589. require.Equal(t, err, test.err)
  590. if test.err != nil {
  591. return
  592. }
  593. require.Equal(t, 1, len(ingress.Rules))
  594. rule := ingress.Rules[0]
  595. require.Equal(t, test.expectedService, rule.Service)
  596. })
  597. }
  598. }
  599. func urlMustParse(s string) *url.URL {
  600. u, err := url.Parse(s)
  601. if err != nil {
  602. panic(err)
  603. }
  604. return u
  605. }
  606. func TestSingleOriginServices_URL(t *testing.T) {
  607. host := "://localhost:8080"
  608. newCli := func(param string, value string) *cli.Context {
  609. flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError)
  610. flagSet.String("url", "", "")
  611. cliCtx := cli.NewContext(cli.NewApp(), flagSet, nil)
  612. cliCtx.Set(param, value)
  613. return cliCtx
  614. }
  615. httpTests := []string{"http", "https"}
  616. for _, test := range httpTests {
  617. t.Run(test, func(t *testing.T) {
  618. url := urlMustParse(test + host)
  619. ingress, err := parseCLIIngress(newCli("url", url.String()), false)
  620. require.NoError(t, err)
  621. require.Equal(t, 1, len(ingress.Rules))
  622. rule := ingress.Rules[0]
  623. require.Equal(t, &httpService{url: url}, rule.Service)
  624. })
  625. }
  626. tcpTests := []string{"ssh", "rdp", "smb", "tcp"}
  627. for _, test := range tcpTests {
  628. t.Run(test, func(t *testing.T) {
  629. url := urlMustParse(test + host)
  630. ingress, err := parseCLIIngress(newCli("url", url.String()), false)
  631. require.NoError(t, err)
  632. require.Equal(t, 1, len(ingress.Rules))
  633. rule := ingress.Rules[0]
  634. require.Equal(t, newTCPOverWSService(url), rule.Service)
  635. })
  636. }
  637. }
  638. func TestFindMatchingRule(t *testing.T) {
  639. ingress := Ingress{
  640. Rules: []Rule{
  641. {
  642. Hostname: "tunnel-a.example.com",
  643. Path: nil,
  644. },
  645. {
  646. Hostname: "tunnel-b.example.com",
  647. Path: MustParsePath(t, "/health"),
  648. },
  649. {
  650. Hostname: "*",
  651. },
  652. },
  653. }
  654. tests := []struct {
  655. host string
  656. path string
  657. req *http.Request
  658. wantRuleIndex int
  659. }{
  660. {
  661. host: "tunnel-a.example.com",
  662. path: "/",
  663. wantRuleIndex: 0,
  664. },
  665. {
  666. host: "tunnel-a.example.com",
  667. path: "/pages/about",
  668. wantRuleIndex: 0,
  669. },
  670. {
  671. host: "tunnel-a.example.com:443",
  672. path: "/pages/about",
  673. wantRuleIndex: 0,
  674. },
  675. {
  676. host: "tunnel-b.example.com",
  677. path: "/health",
  678. wantRuleIndex: 1,
  679. },
  680. {
  681. host: "tunnel-b.example.com",
  682. path: "/index.html",
  683. wantRuleIndex: 2,
  684. },
  685. {
  686. host: "tunnel-c.example.com",
  687. path: "/",
  688. wantRuleIndex: 2,
  689. },
  690. }
  691. for _, test := range tests {
  692. _, ruleIndex := ingress.FindMatchingRule(test.host, test.path)
  693. assert.Equal(t, test.wantRuleIndex, ruleIndex, fmt.Sprintf("Expect host=%s, path=%s to match rule %d, got %d", test.host, test.path, test.wantRuleIndex, ruleIndex))
  694. }
  695. }
  696. func TestIsHTTPService(t *testing.T) {
  697. tests := []struct {
  698. url *url.URL
  699. isHTTP bool
  700. }{
  701. {
  702. url: MustParseURL(t, "http://localhost"),
  703. isHTTP: true,
  704. },
  705. {
  706. url: MustParseURL(t, "https://127.0.0.1:8000"),
  707. isHTTP: true,
  708. },
  709. {
  710. url: MustParseURL(t, "ws://localhost"),
  711. isHTTP: true,
  712. },
  713. {
  714. url: MustParseURL(t, "wss://localhost:8000"),
  715. isHTTP: true,
  716. },
  717. {
  718. url: MustParseURL(t, "tcp://localhost:9000"),
  719. isHTTP: false,
  720. },
  721. }
  722. for _, test := range tests {
  723. assert.Equal(t, test.isHTTP, isHTTPService(test.url))
  724. }
  725. }
  726. func MustParsePath(t *testing.T, path string) *Regexp {
  727. regexp, err := regexp.Compile(path)
  728. assert.NoError(t, err)
  729. return &Regexp{Regexp: regexp}
  730. }
  731. func MustParseURL(t *testing.T, rawURL string) *url.URL {
  732. u, err := url.Parse(rawURL)
  733. require.NoError(t, err)
  734. return u
  735. }
  736. func accessPolicy() *ipaccess.Policy {
  737. cidr1 := "1.1.1.0/24"
  738. cidr2 := "0.0.0.0/0"
  739. rule1, _ := ipaccess.NewRuleByCIDR(&cidr1, []int{80, 443}, true)
  740. rule2, _ := ipaccess.NewRuleByCIDR(&cidr2, nil, false)
  741. rules := []ipaccess.Rule{rule1, rule2}
  742. accessPolicy, _ := ipaccess.NewPolicy(false, rules)
  743. return accessPolicy
  744. }
  745. func BenchmarkFindMatch(b *testing.B) {
  746. rulesYAML := `
  747. ingress:
  748. - hostname: tunnel1.example.com
  749. service: https://localhost:8000
  750. - hostname: tunnel2.example.com
  751. service: https://localhost:8001
  752. - hostname: "*"
  753. service: https://localhost:8002
  754. `
  755. ing, err := ParseIngress(MustReadIngress(rulesYAML))
  756. if err != nil {
  757. b.Error(err)
  758. }
  759. for n := 0; n < b.N; n++ {
  760. ing.FindMatchingRule("tunnel1.example.com", "")
  761. ing.FindMatchingRule("tunnel2.example.com", "")
  762. ing.FindMatchingRule("tunnel3.example.com", "")
  763. }
  764. }
  765. func TestParseAccessConfig(t *testing.T) {
  766. tests := []struct {
  767. name string
  768. cfg config.AccessConfig
  769. expectError bool
  770. }{
  771. {
  772. name: "Config required with teamName only",
  773. cfg: config.AccessConfig{Required: true, TeamName: "team"},
  774. expectError: false,
  775. },
  776. {
  777. name: "required false",
  778. cfg: config.AccessConfig{Required: false},
  779. expectError: false,
  780. },
  781. {
  782. name: "required true but empty config",
  783. cfg: config.AccessConfig{Required: true},
  784. expectError: false,
  785. },
  786. {
  787. name: "complete config",
  788. cfg: config.AccessConfig{Required: true, TeamName: "team", AudTag: []string{"a"}},
  789. expectError: false,
  790. },
  791. {
  792. name: "required true with audTags but no teamName",
  793. cfg: config.AccessConfig{Required: true, AudTag: []string{"a"}},
  794. expectError: true,
  795. },
  796. }
  797. for _, test := range tests {
  798. t.Run(test.name, func(t *testing.T) {
  799. err := validateAccessConfiguration(&test.cfg)
  800. require.Equal(t, err != nil, test.expectError)
  801. })
  802. }
  803. }
  804. func MustReadIngress(s string) *config.Configuration {
  805. var conf config.Configuration
  806. err := yaml.Unmarshal([]byte(s), &conf)
  807. if err != nil {
  808. panic(err)
  809. }
  810. return &conf
  811. }