proxy_server_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package proxy
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "net/http"
  18. "net/http/httptest"
  19. "net/url"
  20. "os"
  21. "path/filepath"
  22. "strings"
  23. "testing"
  24. "k8s.io/apimachinery/pkg/util/proxy"
  25. "k8s.io/client-go/rest"
  26. )
  27. func TestAccept(t *testing.T) {
  28. tests := []struct {
  29. name string
  30. acceptPaths string
  31. rejectPaths string
  32. acceptHosts string
  33. rejectMethods string
  34. path string
  35. host string
  36. method string
  37. expectAccept bool
  38. }{
  39. {
  40. name: "test1",
  41. acceptPaths: DefaultPathAcceptRE,
  42. rejectPaths: DefaultPathRejectRE,
  43. acceptHosts: DefaultHostAcceptRE,
  44. rejectMethods: DefaultMethodRejectRE,
  45. path: "",
  46. host: "127.0.0.1",
  47. method: "GET",
  48. expectAccept: true,
  49. },
  50. {
  51. name: "test2",
  52. acceptPaths: DefaultPathAcceptRE,
  53. rejectPaths: DefaultPathRejectRE,
  54. acceptHosts: DefaultHostAcceptRE,
  55. rejectMethods: DefaultMethodRejectRE,
  56. path: "/api/v1/pods",
  57. host: "127.0.0.1",
  58. method: "GET",
  59. expectAccept: true,
  60. },
  61. {
  62. name: "test3",
  63. acceptPaths: DefaultPathAcceptRE,
  64. rejectPaths: DefaultPathRejectRE,
  65. acceptHosts: DefaultHostAcceptRE,
  66. rejectMethods: DefaultMethodRejectRE,
  67. path: "/api/v1/pods",
  68. host: "localhost",
  69. method: "GET",
  70. expectAccept: true,
  71. },
  72. {
  73. name: "test4",
  74. acceptPaths: DefaultPathAcceptRE,
  75. rejectPaths: DefaultPathRejectRE,
  76. acceptHosts: DefaultHostAcceptRE,
  77. rejectMethods: DefaultMethodRejectRE,
  78. path: "/api/v1/namespaces/default/pods/foo",
  79. host: "localhost",
  80. method: "GET",
  81. expectAccept: true,
  82. },
  83. {
  84. name: "test5",
  85. acceptPaths: DefaultPathAcceptRE,
  86. rejectPaths: DefaultPathRejectRE,
  87. acceptHosts: DefaultHostAcceptRE,
  88. rejectMethods: DefaultMethodRejectRE,
  89. path: "/api/v1/namespaces/default/pods/attachfoo",
  90. host: "localhost",
  91. method: "GET",
  92. expectAccept: true,
  93. },
  94. {
  95. name: "test7",
  96. acceptPaths: DefaultPathAcceptRE,
  97. rejectPaths: DefaultPathRejectRE,
  98. acceptHosts: DefaultHostAcceptRE,
  99. rejectMethods: DefaultMethodRejectRE,
  100. path: "/api/v1/namespaces/default/pods/execfoo",
  101. host: "localhost",
  102. method: "GET",
  103. expectAccept: true,
  104. },
  105. {
  106. name: "test8",
  107. acceptPaths: DefaultPathAcceptRE,
  108. rejectPaths: DefaultPathRejectRE,
  109. acceptHosts: DefaultHostAcceptRE,
  110. rejectMethods: DefaultMethodRejectRE,
  111. path: "/api/v1/namespaces/default/pods/foo/exec",
  112. host: "127.0.0.1",
  113. method: "GET",
  114. expectAccept: false,
  115. },
  116. {
  117. name: "test9",
  118. acceptPaths: DefaultPathAcceptRE,
  119. rejectPaths: DefaultPathRejectRE,
  120. acceptHosts: DefaultHostAcceptRE,
  121. rejectMethods: DefaultMethodRejectRE,
  122. path: "/api/v1/namespaces/default/pods/foo/attach",
  123. host: "127.0.0.1",
  124. method: "GET",
  125. expectAccept: false,
  126. },
  127. {
  128. name: "test10",
  129. acceptPaths: DefaultPathAcceptRE,
  130. rejectPaths: DefaultPathRejectRE,
  131. acceptHosts: DefaultHostAcceptRE,
  132. rejectMethods: DefaultMethodRejectRE,
  133. path: "/api/v1/pods",
  134. host: "evil.com",
  135. method: "GET",
  136. expectAccept: false,
  137. },
  138. {
  139. name: "test11",
  140. acceptPaths: DefaultPathAcceptRE,
  141. rejectPaths: DefaultPathRejectRE,
  142. acceptHosts: DefaultHostAcceptRE,
  143. rejectMethods: DefaultMethodRejectRE,
  144. path: "/api/v1/pods",
  145. host: "localhost.evil.com",
  146. method: "GET",
  147. expectAccept: false,
  148. },
  149. {
  150. name: "test12",
  151. acceptPaths: DefaultPathAcceptRE,
  152. rejectPaths: DefaultPathRejectRE,
  153. acceptHosts: DefaultHostAcceptRE,
  154. rejectMethods: DefaultMethodRejectRE,
  155. path: "/api/v1/pods",
  156. host: "127a0b0c1",
  157. method: "GET",
  158. expectAccept: false,
  159. },
  160. {
  161. name: "test13",
  162. acceptPaths: DefaultPathAcceptRE,
  163. rejectPaths: DefaultPathRejectRE,
  164. acceptHosts: DefaultHostAcceptRE,
  165. rejectMethods: DefaultMethodRejectRE,
  166. path: "/ui",
  167. host: "localhost",
  168. method: "GET",
  169. expectAccept: true,
  170. },
  171. {
  172. name: "test14",
  173. acceptPaths: DefaultPathAcceptRE,
  174. rejectPaths: DefaultPathRejectRE,
  175. acceptHosts: DefaultHostAcceptRE,
  176. rejectMethods: DefaultMethodRejectRE,
  177. path: "/api/v1/pods",
  178. host: "localhost",
  179. method: "POST",
  180. expectAccept: true,
  181. },
  182. {
  183. name: "test15",
  184. acceptPaths: DefaultPathAcceptRE,
  185. rejectPaths: DefaultPathRejectRE,
  186. acceptHosts: DefaultHostAcceptRE,
  187. rejectMethods: DefaultMethodRejectRE,
  188. path: "/api/v1/namespaces/default/pods/somepod",
  189. host: "localhost",
  190. method: "PUT",
  191. expectAccept: true,
  192. },
  193. {
  194. name: "test16",
  195. acceptPaths: DefaultPathAcceptRE,
  196. rejectPaths: DefaultPathRejectRE,
  197. acceptHosts: DefaultHostAcceptRE,
  198. rejectMethods: DefaultMethodRejectRE,
  199. path: "/api/v1/namespaces/default/pods/somepod",
  200. host: "localhost",
  201. method: "PATCH",
  202. expectAccept: true,
  203. },
  204. {
  205. name: "test17",
  206. acceptPaths: DefaultPathAcceptRE,
  207. rejectPaths: DefaultPathRejectRE,
  208. acceptHosts: DefaultHostAcceptRE,
  209. rejectMethods: "GET",
  210. path: "/api/v1/pods",
  211. host: "127.0.0.1",
  212. method: "GET",
  213. expectAccept: false,
  214. },
  215. {
  216. name: "test18",
  217. acceptPaths: DefaultPathAcceptRE,
  218. rejectPaths: DefaultPathRejectRE,
  219. acceptHosts: DefaultHostAcceptRE,
  220. rejectMethods: "POST",
  221. path: "/api/v1/pods",
  222. host: "localhost",
  223. method: "POST",
  224. expectAccept: false,
  225. },
  226. {
  227. name: "test19",
  228. acceptPaths: DefaultPathAcceptRE,
  229. rejectPaths: DefaultPathRejectRE,
  230. acceptHosts: DefaultHostAcceptRE,
  231. rejectMethods: "PUT",
  232. path: "/api/v1/namespaces/default/pods/somepod",
  233. host: "localhost",
  234. method: "PUT",
  235. expectAccept: false,
  236. },
  237. {
  238. name: "test20",
  239. acceptPaths: DefaultPathAcceptRE,
  240. rejectPaths: DefaultPathRejectRE,
  241. acceptHosts: DefaultHostAcceptRE,
  242. rejectMethods: "PATCH",
  243. path: "/api/v1/namespaces/default/pods/somepod",
  244. host: "localhost",
  245. method: "PATCH",
  246. expectAccept: false,
  247. },
  248. {
  249. name: "test21",
  250. acceptPaths: DefaultPathAcceptRE,
  251. rejectPaths: DefaultPathRejectRE,
  252. acceptHosts: DefaultHostAcceptRE,
  253. rejectMethods: "POST,PUT,PATCH",
  254. path: "/api/v1/namespaces/default/pods/somepod",
  255. host: "localhost",
  256. method: "PATCH",
  257. expectAccept: false,
  258. },
  259. {
  260. name: "test22",
  261. acceptPaths: DefaultPathAcceptRE,
  262. rejectPaths: DefaultPathRejectRE,
  263. acceptHosts: DefaultHostAcceptRE,
  264. rejectMethods: "POST,PUT,PATCH",
  265. path: "/api/v1/namespaces/default/pods/somepod",
  266. host: "localhost",
  267. method: "PUT",
  268. expectAccept: false,
  269. },
  270. }
  271. for _, tt := range tests {
  272. t.Run(tt.name, func(t *testing.T) {
  273. filter := &FilterServer{
  274. AcceptPaths: MakeRegexpArrayOrDie(tt.acceptPaths),
  275. RejectPaths: MakeRegexpArrayOrDie(tt.rejectPaths),
  276. AcceptHosts: MakeRegexpArrayOrDie(tt.acceptHosts),
  277. RejectMethods: MakeRegexpArrayOrDie(tt.rejectMethods),
  278. }
  279. accept := filter.accept(tt.method, tt.path, tt.host)
  280. if accept != tt.expectAccept {
  281. t.Errorf("expected: %v, got %v for %#v", tt.expectAccept, accept, tt)
  282. }
  283. })
  284. }
  285. }
  286. func TestRegexpMatch(t *testing.T) {
  287. tests := []struct {
  288. name string
  289. str string
  290. regexps string
  291. expectMatch bool
  292. }{
  293. {
  294. name: "test1",
  295. str: "foo",
  296. regexps: "bar,.*",
  297. expectMatch: true,
  298. },
  299. {
  300. name: "test2",
  301. str: "foo",
  302. regexps: "bar,fo.*",
  303. expectMatch: true,
  304. },
  305. {
  306. name: "test3",
  307. str: "bar",
  308. regexps: "bar,fo.*",
  309. expectMatch: true,
  310. },
  311. {
  312. name: "test4",
  313. str: "baz",
  314. regexps: "bar,fo.*",
  315. expectMatch: false,
  316. },
  317. }
  318. for _, tt := range tests {
  319. t.Run(tt.name, func(t *testing.T) {
  320. match := matchesRegexp(tt.str, MakeRegexpArrayOrDie(tt.regexps))
  321. if tt.expectMatch != match {
  322. t.Errorf("expected: %v, found: %v, for %s and %v", tt.expectMatch, match, tt.str, tt.regexps)
  323. }
  324. })
  325. }
  326. }
  327. func TestFileServing(t *testing.T) {
  328. const (
  329. fname = "test.txt"
  330. data = "This is test data"
  331. )
  332. dir, err := ioutil.TempDir("", "data")
  333. if err != nil {
  334. t.Fatalf("error creating tmp dir: %v", err)
  335. }
  336. defer os.RemoveAll(dir)
  337. if err := ioutil.WriteFile(filepath.Join(dir, fname), []byte(data), 0755); err != nil {
  338. t.Fatalf("error writing tmp file: %v", err)
  339. }
  340. const prefix = "/foo/"
  341. handler := newFileHandler(prefix, dir)
  342. server := httptest.NewServer(handler)
  343. defer server.Close()
  344. url := server.URL + prefix + fname
  345. res, err := http.Get(url)
  346. if err != nil {
  347. t.Fatalf("http.Get(%q) error: %v", url, err)
  348. }
  349. defer res.Body.Close()
  350. if res.StatusCode != http.StatusOK {
  351. t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, http.StatusOK)
  352. }
  353. b, err := ioutil.ReadAll(res.Body)
  354. if err != nil {
  355. t.Fatalf("error reading resp body: %v", err)
  356. }
  357. if string(b) != data {
  358. t.Errorf("have %q; want %q", string(b), data)
  359. }
  360. }
  361. func newProxy(target *url.URL) http.Handler {
  362. p := proxy.NewUpgradeAwareHandler(target, http.DefaultTransport, false, false, &responder{})
  363. p.UseRequestLocation = true
  364. return p
  365. }
  366. func TestAPIRequests(t *testing.T) {
  367. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  368. b, err := ioutil.ReadAll(r.Body)
  369. if err != nil {
  370. http.Error(w, err.Error(), http.StatusInternalServerError)
  371. return
  372. }
  373. fmt.Fprintf(w, "%s %s %s", r.Method, r.RequestURI, string(b))
  374. }))
  375. defer ts.Close()
  376. // httptest.NewServer should always generate a valid URL.
  377. target, _ := url.Parse(ts.URL)
  378. target.Path = "/"
  379. proxy := newProxy(target)
  380. tests := []struct{ name, method, body string }{
  381. {"test1", "GET", ""},
  382. {"test2", "DELETE", ""},
  383. {"test3", "POST", "test payload"},
  384. {"test4", "PUT", "test payload"},
  385. }
  386. const path = "/api/test?fields=ID%3Dfoo&labels=key%3Dvalue"
  387. for i, tt := range tests {
  388. t.Run(tt.name, func(t *testing.T) {
  389. r, err := http.NewRequest(tt.method, path, strings.NewReader(tt.body))
  390. if err != nil {
  391. t.Errorf("error creating request: %v", err)
  392. return
  393. }
  394. w := httptest.NewRecorder()
  395. proxy.ServeHTTP(w, r)
  396. if w.Code != http.StatusOK {
  397. t.Errorf("%d: proxy.ServeHTTP w.Code = %d; want %d", i, w.Code, http.StatusOK)
  398. }
  399. want := strings.Join([]string{tt.method, path, tt.body}, " ")
  400. if w.Body.String() != want {
  401. t.Errorf("%d: response body = %q; want %q", i, w.Body.String(), want)
  402. }
  403. })
  404. }
  405. }
  406. func TestPathHandling(t *testing.T) {
  407. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  408. fmt.Fprint(w, r.URL.Path)
  409. }))
  410. defer ts.Close()
  411. table := []struct {
  412. name string
  413. prefix string
  414. reqPath string
  415. expectPath string
  416. }{
  417. {"test1", "/api/", "/metrics", "404 page not found\n"},
  418. {"test2", "/api/", "/api/metrics", "/api/metrics"},
  419. {"test3", "/api/", "/api/v1/pods/", "/api/v1/pods/"},
  420. {"test4", "/", "/metrics", "/metrics"},
  421. {"test5", "/", "/api/v1/pods/", "/api/v1/pods/"},
  422. {"test6", "/custom/", "/metrics", "404 page not found\n"},
  423. {"test7", "/custom/", "/api/metrics", "404 page not found\n"},
  424. {"test8", "/custom/", "/api/v1/pods/", "404 page not found\n"},
  425. {"test9", "/custom/", "/custom/api/metrics", "/api/metrics"},
  426. {"test10", "/custom/", "/custom/api/v1/pods/", "/api/v1/pods/"},
  427. }
  428. cc := &rest.Config{
  429. Host: ts.URL,
  430. }
  431. for _, tt := range table {
  432. t.Run(tt.name, func(t *testing.T) {
  433. p, err := NewServer("", tt.prefix, "/not/used/for/this/test", nil, cc, 0)
  434. if err != nil {
  435. t.Fatalf("%#v: %v", tt, err)
  436. }
  437. pts := httptest.NewServer(p.handler)
  438. defer pts.Close()
  439. r, err := http.Get(pts.URL + tt.reqPath)
  440. if err != nil {
  441. t.Fatalf("%#v: %v", tt, err)
  442. }
  443. body, err := ioutil.ReadAll(r.Body)
  444. r.Body.Close()
  445. if err != nil {
  446. t.Fatalf("%#v: %v", tt, err)
  447. }
  448. if e, a := tt.expectPath, string(body); e != a {
  449. t.Errorf("%#v: Wanted %q, got %q", tt, e, a)
  450. }
  451. })
  452. }
  453. }
  454. func TestExtractHost(t *testing.T) {
  455. fixtures := map[string]string{
  456. "localhost:8085": "localhost",
  457. "marmalade": "marmalade",
  458. }
  459. for header, expected := range fixtures {
  460. host := extractHost(header)
  461. if host != expected {
  462. t.Fatalf("%s != %s", host, expected)
  463. }
  464. }
  465. }