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