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  }