github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/proxy/transport_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package proxy 18 19 import ( 20 "bytes" 21 "compress/flate" 22 "compress/gzip" 23 "fmt" 24 "io/ioutil" 25 "net/http" 26 "net/http/httptest" 27 "net/url" 28 "strings" 29 "testing" 30 ) 31 32 func parseURLOrDie(inURL string) *url.URL { 33 parsed, err := url.Parse(inURL) 34 if err != nil { 35 panic(err) 36 } 37 return parsed 38 } 39 40 func TestProxyTransport(t *testing.T) { 41 testTransport := &Transport{ 42 Scheme: "http", 43 Host: "foo.com", 44 PathPrepend: "/proxy/node/node1:10250", 45 } 46 testTransport2 := &Transport{ 47 Scheme: "https", 48 Host: "foo.com", 49 PathPrepend: "/proxy/node/node1:8080", 50 } 51 emptyHostTransport := &Transport{ 52 Scheme: "https", 53 PathPrepend: "/proxy/node/node1:10250", 54 } 55 emptySchemeTransport := &Transport{ 56 Host: "foo.com", 57 PathPrepend: "/proxy/node/node1:10250", 58 } 59 emptyHostAndSchemeTransport := &Transport{ 60 PathPrepend: "/proxy/node/node1:10250", 61 } 62 type Item struct { 63 input string 64 sourceURL string 65 transport *Transport 66 output string 67 contentType string 68 forwardedURI string 69 redirect string 70 redirectWant string 71 reqHost string 72 } 73 74 table := map[string]Item{ 75 "normal": { 76 input: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre>`, 77 sourceURL: "http://mynode.com/logs/log.log", 78 transport: testTransport, 79 output: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre>`, 80 contentType: "text/html", 81 forwardedURI: "/proxy/node/node1:10250/logs/log.log", 82 }, 83 "full document": { 84 input: `<html><header></header><body><pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre></body></html>`, 85 sourceURL: "http://mynode.com/logs/log.log", 86 transport: testTransport, 87 output: `<html><header></header><body><pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre></body></html>`, 88 contentType: "text/html", 89 forwardedURI: "/proxy/node/node1:10250/logs/log.log", 90 }, 91 "trailing slash": { 92 input: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log/">google.log</a></pre>`, 93 sourceURL: "http://mynode.com/logs/log.log", 94 transport: testTransport, 95 output: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log/">google.log</a></pre>`, 96 contentType: "text/html", 97 forwardedURI: "/proxy/node/node1:10250/logs/log.log", 98 }, 99 "content-type charset": { 100 input: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre>`, 101 sourceURL: "http://mynode.com/logs/log.log", 102 transport: testTransport, 103 output: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre>`, 104 contentType: "text/html; charset=utf-8", 105 forwardedURI: "/proxy/node/node1:10250/logs/log.log", 106 }, 107 "content-type passthrough": { 108 input: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre>`, 109 sourceURL: "http://mynode.com/logs/log.log", 110 transport: testTransport, 111 output: `<pre><a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a></pre>`, 112 contentType: "text/plain", 113 forwardedURI: "/proxy/node/node1:10250/logs/log.log", 114 }, 115 "subdir": { 116 input: `<a href="kubelet.log">kubelet.log</a><a href="/google.log">google.log</a>`, 117 sourceURL: "http://mynode.com/whatever/apt/somelog.log", 118 transport: testTransport2, 119 output: `<a href="kubelet.log">kubelet.log</a><a href="https://foo.com/proxy/node/node1:8080/google.log">google.log</a>`, 120 contentType: "text/html", 121 forwardedURI: "/proxy/node/node1:8080/whatever/apt/somelog.log", 122 }, 123 "image": { 124 input: `<pre><img src="kubernetes.jpg"/><img src="/kubernetes_abs.jpg"/></pre>`, 125 sourceURL: "http://mynode.com/", 126 transport: testTransport, 127 output: `<pre><img src="kubernetes.jpg"/><img src="http://foo.com/proxy/node/node1:10250/kubernetes_abs.jpg"/></pre>`, 128 contentType: "text/html", 129 forwardedURI: "/proxy/node/node1:10250/", 130 }, 131 "abs": { 132 input: `<script src="http://google.com/kubernetes.js"/>`, 133 sourceURL: "http://mynode.com/any/path/", 134 transport: testTransport, 135 output: `<script src="http://google.com/kubernetes.js"/>`, 136 contentType: "text/html", 137 forwardedURI: "/proxy/node/node1:10250/any/path/", 138 }, 139 "abs but same host": { 140 input: `<script src="http://mynode.com/kubernetes.js"/>`, 141 sourceURL: "http://mynode.com/any/path/", 142 transport: testTransport, 143 output: `<script src="http://foo.com/proxy/node/node1:10250/kubernetes.js"/>`, 144 contentType: "text/html", 145 forwardedURI: "/proxy/node/node1:10250/any/path/", 146 }, 147 "redirect rel": { 148 sourceURL: "http://mynode.com/redirect", 149 transport: testTransport, 150 redirect: "/redirected/target/", 151 redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/", 152 forwardedURI: "/proxy/node/node1:10250/redirect", 153 }, 154 "redirect abs same host": { 155 sourceURL: "http://mynode.com/redirect", 156 transport: testTransport, 157 redirect: "http://mynode.com/redirected/target/", 158 redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/", 159 forwardedURI: "/proxy/node/node1:10250/redirect", 160 }, 161 "redirect abs other host": { 162 sourceURL: "http://mynode.com/redirect", 163 transport: testTransport, 164 redirect: "http://example.com/redirected/target/", 165 redirectWant: "http://example.com/redirected/target/", 166 forwardedURI: "/proxy/node/node1:10250/redirect", 167 }, 168 "redirect abs use reqHost no host no scheme": { 169 sourceURL: "http://mynode.com/redirect", 170 transport: emptyHostAndSchemeTransport, 171 redirect: "http://10.0.0.1:8001/redirected/target/", 172 redirectWant: "http://10.0.0.1:8001/proxy/node/node1:10250/redirected/target/", 173 forwardedURI: "/proxy/node/node1:10250/redirect", 174 reqHost: "10.0.0.1:8001", 175 }, 176 "source contains the redirect already": { 177 input: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre>`, 178 sourceURL: "http://foo.com/logs/log.log", 179 transport: testTransport, 180 output: `<pre><a href="kubelet.log">kubelet.log</a><a href="http://foo.com/proxy/node/node1:10250/google.log">google.log</a></pre>`, 181 contentType: "text/html", 182 forwardedURI: "/proxy/node/node1:10250/logs/log.log", 183 }, 184 "no host": { 185 input: "<html></html>", 186 sourceURL: "http://mynode.com/logs/log.log", 187 transport: emptyHostTransport, 188 output: "<html></html>", 189 contentType: "text/html", 190 forwardedURI: "/proxy/node/node1:10250/logs/log.log", 191 }, 192 "no scheme": { 193 input: "<html></html>", 194 sourceURL: "http://mynode.com/logs/log.log", 195 transport: emptySchemeTransport, 196 output: "<html></html>", 197 contentType: "text/html", 198 forwardedURI: "/proxy/node/node1:10250/logs/log.log", 199 }, 200 "forwarded URI must be escaped": { 201 input: "<html></html>", 202 sourceURL: "http://mynode.com/logs/log.log%00<script>alert(1)</script>", 203 transport: testTransport, 204 output: "<html></html>", 205 contentType: "text/html", 206 forwardedURI: "/proxy/node/node1:10250/logs/log.log%00%3Cscript%3Ealert%281%29%3C/script%3E", 207 }, 208 "redirect rel must be escaped": { 209 sourceURL: "http://mynode.com/redirect", 210 transport: testTransport, 211 redirect: "/redirected/target/%00<script>alert(1)</script>/", 212 redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", 213 forwardedURI: "/proxy/node/node1:10250/redirect", 214 }, 215 "redirect abs same host must be escaped": { 216 sourceURL: "http://mynode.com/redirect", 217 transport: testTransport, 218 redirect: "http://mynode.com/redirected/target/%00<script>alert(1)</script>/", 219 redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", 220 forwardedURI: "/proxy/node/node1:10250/redirect", 221 }, 222 "redirect abs other host must be escaped": { 223 sourceURL: "http://mynode.com/redirect", 224 transport: testTransport, 225 redirect: "http://example.com/redirected/target/%00<script>alert(1)</script>/", 226 redirectWant: "http://example.com/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", 227 forwardedURI: "/proxy/node/node1:10250/redirect", 228 }, 229 "redirect abs use reqHost no host no scheme must be escaped": { 230 sourceURL: "http://mynode.com/redirect", 231 transport: emptyHostAndSchemeTransport, 232 redirect: "http://10.0.0.1:8001/redirected/target/%00<script>alert(1)</script>/", 233 redirectWant: "http://10.0.0.1:8001/proxy/node/node1:10250/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", 234 forwardedURI: "/proxy/node/node1:10250/redirect", 235 reqHost: "10.0.0.1:8001", 236 }, 237 } 238 239 testItem := func(name string, item *Item) { 240 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 241 // Check request headers. 242 if got, want := r.Header.Get("X-Forwarded-Uri"), item.forwardedURI; got != want { 243 t.Errorf("%v: X-Forwarded-Uri = %q, want %q", name, got, want) 244 } 245 if len(item.transport.Host) == 0 { 246 _, present := r.Header["X-Forwarded-Host"] 247 if present { 248 t.Errorf("%v: X-Forwarded-Host header should not be present", name) 249 } 250 } else { 251 if got, want := r.Header.Get("X-Forwarded-Host"), item.transport.Host; got != want { 252 t.Errorf("%v: X-Forwarded-Host = %q, want %q", name, got, want) 253 } 254 } 255 if len(item.transport.Scheme) == 0 { 256 _, present := r.Header["X-Forwarded-Proto"] 257 if present { 258 t.Errorf("%v: X-Forwarded-Proto header should not be present", name) 259 } 260 } else { 261 if got, want := r.Header.Get("X-Forwarded-Proto"), item.transport.Scheme; got != want { 262 t.Errorf("%v: X-Forwarded-Proto = %q, want %q", name, got, want) 263 } 264 } 265 266 // Send response. 267 if item.redirect != "" { 268 http.Redirect(w, r, item.redirect, http.StatusMovedPermanently) 269 return 270 } 271 w.Header().Set("Content-Type", item.contentType) 272 fmt.Fprint(w, item.input) 273 })) 274 defer server.Close() 275 276 // Replace source URL with our test server address. 277 sourceURL := parseURLOrDie(item.sourceURL) 278 serverURL := parseURLOrDie(server.URL) 279 item.input = strings.Replace(item.input, sourceURL.Host, serverURL.Host, -1) 280 item.redirect = strings.Replace(item.redirect, sourceURL.Host, serverURL.Host, -1) 281 sourceURL.Host = serverURL.Host 282 283 req, err := http.NewRequest("GET", sourceURL.String(), nil) 284 if err != nil { 285 t.Errorf("%v: Unexpected error: %v", name, err) 286 return 287 } 288 if item.reqHost != "" { 289 req.Host = item.reqHost 290 } 291 resp, err := item.transport.RoundTrip(req) 292 if err != nil { 293 t.Errorf("%v: Unexpected error: %v", name, err) 294 return 295 } 296 if item.redirect != "" { 297 // Check that redirect URLs get rewritten properly. 298 if got, want := resp.Header.Get("Location"), item.redirectWant; got != want { 299 t.Errorf("%v: Location header = %q, want %q", name, got, want) 300 } 301 return 302 } 303 body, err := ioutil.ReadAll(resp.Body) 304 if err != nil { 305 t.Errorf("%v: Unexpected error: %v", name, err) 306 return 307 } 308 if e, a := item.output, string(body); e != a { 309 t.Errorf("%v: expected %v, but got %v", name, e, a) 310 } 311 } 312 313 for name, item := range table { 314 testItem(name, &item) 315 } 316 } 317 318 func TestRewriteResponse(t *testing.T) { 319 gzipbuf := bytes.NewBuffer(nil) 320 flatebuf := bytes.NewBuffer(nil) 321 322 testTransport := &Transport{ 323 Scheme: "http", 324 Host: "foo.com", 325 PathPrepend: "/proxy/node/node1:10250", 326 } 327 expected := []string{ 328 "short body test", 329 strings.Repeat("long body test", 4097), 330 } 331 test := []struct { 332 encodeType string 333 writer func(string) *http.Response 334 reader func(*http.Response) string 335 }{ 336 { 337 encodeType: "gzip", 338 writer: func(ept string) *http.Response { 339 gzw := gzip.NewWriter(gzipbuf) 340 defer gzw.Close() 341 342 gzw.Write([]byte(ept)) 343 gzw.Flush() 344 return &http.Response{ 345 Body: ioutil.NopCloser(gzipbuf), 346 } 347 }, 348 reader: func(rep *http.Response) string { 349 reader, _ := gzip.NewReader(rep.Body) 350 s, _ := ioutil.ReadAll(reader) 351 return string(s) 352 }, 353 }, 354 { 355 encodeType: "deflate", 356 writer: func(ept string) *http.Response { 357 flw, _ := flate.NewWriter(flatebuf, flate.BestCompression) 358 defer flw.Close() 359 360 flw.Write([]byte(ept)) 361 flw.Flush() 362 return &http.Response{ 363 Body: ioutil.NopCloser(flatebuf), 364 } 365 }, 366 reader: func(rep *http.Response) string { 367 reader := flate.NewReader(rep.Body) 368 s, _ := ioutil.ReadAll(reader) 369 return string(s) 370 }, 371 }, 372 } 373 374 errFn := func(encode string, err error) { 375 t.Errorf("%s failed to read and write: %v", encode, err) 376 } 377 for _, v := range test { 378 request, _ := http.NewRequest("GET", "http://mynode.com/", nil) 379 request.Header.Set("Content-Encoding", v.encodeType) 380 request.Header.Add("Accept-Encoding", v.encodeType) 381 382 for _, exp := range expected { 383 resp := v.writer(exp) 384 gotResponse, err := testTransport.rewriteResponse(request, resp) 385 386 if err != nil { 387 errFn(v.encodeType, err) 388 } 389 390 result := v.reader(gotResponse) 391 if result != exp { 392 errFn(v.encodeType, fmt.Errorf("expected %s, get %s", exp, result)) 393 } 394 } 395 } 396 }