github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/getter/httpgetter_test.go (about)

     1  /*
     2  Copyright The Helm 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  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package getter
    17  
    18  import (
    19  	"fmt"
    20  	"io"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"net/url"
    24  	"os"
    25  	"path/filepath"
    26  	"strconv"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/pkg/errors"
    32  
    33  	"github.com/stefanmcshane/helm/internal/tlsutil"
    34  	"github.com/stefanmcshane/helm/internal/version"
    35  	"github.com/stefanmcshane/helm/pkg/cli"
    36  )
    37  
    38  func TestHTTPGetter(t *testing.T) {
    39  	g, err := NewHTTPGetter(WithURL("http://example.com"))
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  
    44  	if _, ok := g.(*HTTPGetter); !ok {
    45  		t.Fatal("Expected NewHTTPGetter to produce an *HTTPGetter")
    46  	}
    47  
    48  	cd := "../../testdata"
    49  	join := filepath.Join
    50  	ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem")
    51  	insecure := false
    52  	timeout := time.Second * 5
    53  	transport := &http.Transport{}
    54  
    55  	// Test with options
    56  	g, err = NewHTTPGetter(
    57  		WithBasicAuth("I", "Am"),
    58  		WithPassCredentialsAll(false),
    59  		WithUserAgent("Groot"),
    60  		WithTLSClientConfig(pub, priv, ca),
    61  		WithInsecureSkipVerifyTLS(insecure),
    62  		WithTimeout(timeout),
    63  		WithTransport(transport),
    64  	)
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  
    69  	hg, ok := g.(*HTTPGetter)
    70  	if !ok {
    71  		t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter")
    72  	}
    73  
    74  	if hg.opts.username != "I" {
    75  		t.Errorf("Expected NewHTTPGetter to contain %q as the username, got %q", "I", hg.opts.username)
    76  	}
    77  
    78  	if hg.opts.password != "Am" {
    79  		t.Errorf("Expected NewHTTPGetter to contain %q as the password, got %q", "Am", hg.opts.password)
    80  	}
    81  
    82  	if hg.opts.passCredentialsAll != false {
    83  		t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll)
    84  	}
    85  
    86  	if hg.opts.userAgent != "Groot" {
    87  		t.Errorf("Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent)
    88  	}
    89  
    90  	if hg.opts.certFile != pub {
    91  		t.Errorf("Expected NewHTTPGetter to contain %q as the public key file, got %q", pub, hg.opts.certFile)
    92  	}
    93  
    94  	if hg.opts.keyFile != priv {
    95  		t.Errorf("Expected NewHTTPGetter to contain %q as the private key file, got %q", priv, hg.opts.keyFile)
    96  	}
    97  
    98  	if hg.opts.caFile != ca {
    99  		t.Errorf("Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile)
   100  	}
   101  
   102  	if hg.opts.insecureSkipVerifyTLS != insecure {
   103  		t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", false, hg.opts.insecureSkipVerifyTLS)
   104  	}
   105  
   106  	if hg.opts.timeout != timeout {
   107  		t.Errorf("Expected NewHTTPGetter to contain %s as Timeout flag, got %s", timeout, hg.opts.timeout)
   108  	}
   109  
   110  	if hg.opts.transport != transport {
   111  		t.Errorf("Expected NewHTTPGetter to contain %p as Transport, got %p", transport, hg.opts.transport)
   112  	}
   113  
   114  	// Test if setting insecureSkipVerifyTLS is being passed to the ops
   115  	insecure = true
   116  
   117  	g, err = NewHTTPGetter(
   118  		WithInsecureSkipVerifyTLS(insecure),
   119  	)
   120  	if err != nil {
   121  		t.Fatal(err)
   122  	}
   123  
   124  	hg, ok = g.(*HTTPGetter)
   125  	if !ok {
   126  		t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter")
   127  	}
   128  
   129  	if hg.opts.insecureSkipVerifyTLS != insecure {
   130  		t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS)
   131  	}
   132  
   133  	// Checking false by default
   134  	if hg.opts.passCredentialsAll != false {
   135  		t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll)
   136  	}
   137  
   138  	// Test setting PassCredentialsAll
   139  	g, err = NewHTTPGetter(
   140  		WithBasicAuth("I", "Am"),
   141  		WithPassCredentialsAll(true),
   142  	)
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  
   147  	hg, ok = g.(*HTTPGetter)
   148  	if !ok {
   149  		t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter")
   150  	}
   151  	if hg.opts.passCredentialsAll != true {
   152  		t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", true, hg.opts.passCredentialsAll)
   153  	}
   154  }
   155  
   156  func TestDownload(t *testing.T) {
   157  	expect := "Call me Ishmael"
   158  	expectedUserAgent := "I am Groot"
   159  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   160  		defaultUserAgent := "Helm/" + strings.TrimPrefix(version.GetVersion(), "v")
   161  		if r.UserAgent() != defaultUserAgent {
   162  			t.Errorf("Expected '%s', got '%s'", defaultUserAgent, r.UserAgent())
   163  		}
   164  		fmt.Fprint(w, expect)
   165  	}))
   166  	defer srv.Close()
   167  
   168  	g, err := All(cli.New()).ByScheme("http")
   169  	if err != nil {
   170  		t.Fatal(err)
   171  	}
   172  	got, err := g.Get(srv.URL, WithURL(srv.URL))
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  
   177  	if got.String() != expect {
   178  		t.Errorf("Expected %q, got %q", expect, got.String())
   179  	}
   180  
   181  	// test with http server
   182  	basicAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   183  		username, password, ok := r.BasicAuth()
   184  		if !ok || username != "username" || password != "password" {
   185  			t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
   186  		}
   187  		if r.UserAgent() != expectedUserAgent {
   188  			t.Errorf("Expected '%s', got '%s'", expectedUserAgent, r.UserAgent())
   189  		}
   190  		fmt.Fprint(w, expect)
   191  	}))
   192  
   193  	defer basicAuthSrv.Close()
   194  
   195  	u, _ := url.ParseRequestURI(basicAuthSrv.URL)
   196  	httpgetter, err := NewHTTPGetter(
   197  		WithURL(u.String()),
   198  		WithBasicAuth("username", "password"),
   199  		WithPassCredentialsAll(false),
   200  		WithUserAgent(expectedUserAgent),
   201  	)
   202  	if err != nil {
   203  		t.Fatal(err)
   204  	}
   205  	got, err = httpgetter.Get(u.String())
   206  	if err != nil {
   207  		t.Fatal(err)
   208  	}
   209  
   210  	if got.String() != expect {
   211  		t.Errorf("Expected %q, got %q", expect, got.String())
   212  	}
   213  
   214  	// test with Get URL differing from withURL
   215  	crossAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   216  		username, password, ok := r.BasicAuth()
   217  		if ok || username == "username" || password == "password" {
   218  			t.Errorf("Expected request to not include but got '%v', '%s', '%s'", ok, username, password)
   219  		}
   220  		fmt.Fprint(w, expect)
   221  	}))
   222  
   223  	defer crossAuthSrv.Close()
   224  
   225  	u, _ = url.ParseRequestURI(crossAuthSrv.URL)
   226  
   227  	// A different host is provided for the WithURL from the one used for Get
   228  	u2, _ := url.ParseRequestURI(crossAuthSrv.URL)
   229  	host := strings.Split(u2.Host, ":")
   230  	host[0] = host[0] + "a"
   231  	u2.Host = strings.Join(host, ":")
   232  	httpgetter, err = NewHTTPGetter(
   233  		WithURL(u2.String()),
   234  		WithBasicAuth("username", "password"),
   235  		WithPassCredentialsAll(false),
   236  	)
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  	got, err = httpgetter.Get(u.String())
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  
   245  	if got.String() != expect {
   246  		t.Errorf("Expected %q, got %q", expect, got.String())
   247  	}
   248  
   249  	// test with Get URL differing from withURL and should pass creds
   250  	crossAuthSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   251  		username, password, ok := r.BasicAuth()
   252  		if !ok || username != "username" || password != "password" {
   253  			t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
   254  		}
   255  		fmt.Fprint(w, expect)
   256  	}))
   257  
   258  	defer crossAuthSrv.Close()
   259  
   260  	u, _ = url.ParseRequestURI(crossAuthSrv.URL)
   261  
   262  	// A different host is provided for the WithURL from the one used for Get
   263  	u2, _ = url.ParseRequestURI(crossAuthSrv.URL)
   264  	host = strings.Split(u2.Host, ":")
   265  	host[0] = host[0] + "a"
   266  	u2.Host = strings.Join(host, ":")
   267  	httpgetter, err = NewHTTPGetter(
   268  		WithURL(u2.String()),
   269  		WithBasicAuth("username", "password"),
   270  		WithPassCredentialsAll(true),
   271  	)
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  	got, err = httpgetter.Get(u.String())
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  
   280  	if got.String() != expect {
   281  		t.Errorf("Expected %q, got %q", expect, got.String())
   282  	}
   283  }
   284  
   285  func TestDownloadTLS(t *testing.T) {
   286  	cd := "../../testdata"
   287  	ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem")
   288  
   289  	tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
   290  	tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca)
   291  	if err != nil {
   292  		t.Fatal(errors.Wrap(err, "can't create TLS config for client"))
   293  	}
   294  	tlsConf.ServerName = "helm.sh"
   295  	tlsSrv.TLS = tlsConf
   296  	tlsSrv.StartTLS()
   297  	defer tlsSrv.Close()
   298  
   299  	u, _ := url.ParseRequestURI(tlsSrv.URL)
   300  	g, err := NewHTTPGetter(
   301  		WithURL(u.String()),
   302  		WithTLSClientConfig(pub, priv, ca),
   303  	)
   304  	if err != nil {
   305  		t.Fatal(err)
   306  	}
   307  
   308  	if _, err := g.Get(u.String()); err != nil {
   309  		t.Error(err)
   310  	}
   311  
   312  	// now test with TLS config being passed along in .Get (see #6635)
   313  	g, err = NewHTTPGetter()
   314  	if err != nil {
   315  		t.Fatal(err)
   316  	}
   317  
   318  	if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)); err != nil {
   319  		t.Error(err)
   320  	}
   321  
   322  	// test with only the CA file (see also #6635)
   323  	g, err = NewHTTPGetter()
   324  	if err != nil {
   325  		t.Fatal(err)
   326  	}
   327  
   328  	if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)); err != nil {
   329  		t.Error(err)
   330  	}
   331  }
   332  
   333  func TestDownloadInsecureSkipTLSVerify(t *testing.T) {
   334  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
   335  	defer ts.Close()
   336  
   337  	u, _ := url.ParseRequestURI(ts.URL)
   338  
   339  	// Ensure the default behavior did not change
   340  	g, err := NewHTTPGetter(
   341  		WithURL(u.String()),
   342  	)
   343  	if err != nil {
   344  		t.Error(err)
   345  	}
   346  
   347  	if _, err := g.Get(u.String()); err == nil {
   348  		t.Errorf("Expected Getter to throw an error, got %s", err)
   349  	}
   350  
   351  	// Test certificate check skip
   352  	g, err = NewHTTPGetter(
   353  		WithURL(u.String()),
   354  		WithInsecureSkipVerifyTLS(true),
   355  	)
   356  	if err != nil {
   357  		t.Error(err)
   358  	}
   359  	if _, err = g.Get(u.String()); err != nil {
   360  		t.Error(err)
   361  	}
   362  
   363  }
   364  
   365  func TestHTTPGetterTarDownload(t *testing.T) {
   366  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   367  		f, _ := os.Open("testdata/empty-0.0.1.tgz")
   368  		defer f.Close()
   369  
   370  		b := make([]byte, 512)
   371  		f.Read(b)
   372  		//Get the file size
   373  		FileStat, _ := f.Stat()
   374  		FileSize := strconv.FormatInt(FileStat.Size(), 10)
   375  
   376  		//Simulating improper header values from bitbucket
   377  		w.Header().Set("Content-Type", "application/x-tar")
   378  		w.Header().Set("Content-Encoding", "gzip")
   379  		w.Header().Set("Content-Length", FileSize)
   380  
   381  		f.Seek(0, 0)
   382  		io.Copy(w, f)
   383  	}))
   384  
   385  	defer srv.Close()
   386  
   387  	g, err := NewHTTPGetter(WithURL(srv.URL))
   388  	if err != nil {
   389  		t.Fatal(err)
   390  	}
   391  
   392  	data, _ := g.Get(srv.URL)
   393  	mimeType := http.DetectContentType(data.Bytes())
   394  
   395  	expectedMimeType := "application/x-gzip"
   396  	if mimeType != expectedMimeType {
   397  		t.Fatalf("Expected response with MIME type %s, but got %s", expectedMimeType, mimeType)
   398  	}
   399  }
   400  
   401  func TestHttpClientInsecureSkipVerify(t *testing.T) {
   402  	g := HTTPGetter{}
   403  	g.opts.url = "https://localhost"
   404  	verifyInsecureSkipVerify(t, &g, "Blank HTTPGetter", false)
   405  
   406  	g = HTTPGetter{}
   407  	g.opts.url = "https://localhost"
   408  	g.opts.caFile = "testdata/ca.crt"
   409  	verifyInsecureSkipVerify(t, &g, "HTTPGetter with ca file", false)
   410  
   411  	g = HTTPGetter{}
   412  	g.opts.url = "https://localhost"
   413  	g.opts.insecureSkipVerifyTLS = true
   414  	verifyInsecureSkipVerify(t, &g, "HTTPGetter with skip cert verification only", true)
   415  
   416  	g = HTTPGetter{}
   417  	g.opts.url = "https://localhost"
   418  	g.opts.certFile = "testdata/client.crt"
   419  	g.opts.keyFile = "testdata/client.key"
   420  	g.opts.insecureSkipVerifyTLS = true
   421  	transport := verifyInsecureSkipVerify(t, &g, "HTTPGetter with 2 way ssl", true)
   422  	if len(transport.TLSClientConfig.Certificates) <= 0 {
   423  		t.Fatal("transport.TLSClientConfig.Certificates is not present")
   424  	}
   425  	if transport.TLSClientConfig.ServerName == "" {
   426  		t.Fatal("TLSClientConfig.ServerName is blank")
   427  	}
   428  }
   429  
   430  func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expectedValue bool) *http.Transport {
   431  	returnVal, err := g.httpClient()
   432  
   433  	if err != nil {
   434  		t.Fatal(err)
   435  	}
   436  
   437  	if returnVal == nil {
   438  		t.Fatalf("Expected non nil value for http client")
   439  	}
   440  	transport := (returnVal.Transport).(*http.Transport)
   441  	gotValue := false
   442  	if transport.TLSClientConfig != nil {
   443  		gotValue = transport.TLSClientConfig.InsecureSkipVerify
   444  	}
   445  	if gotValue != expectedValue {
   446  		t.Fatalf("Case Name = %s\nInsecureSkipVerify did not come as expected. Expected = %t; Got = %v",
   447  			caseName, expectedValue, gotValue)
   448  	}
   449  	return transport
   450  }
   451  
   452  func TestDefaultHTTPTransportReuse(t *testing.T) {
   453  	g := HTTPGetter{}
   454  
   455  	httpClient1, err := g.httpClient()
   456  
   457  	if err != nil {
   458  		t.Fatal(err)
   459  	}
   460  
   461  	if httpClient1 == nil {
   462  		t.Fatalf("Expected non nil value for http client")
   463  	}
   464  
   465  	transport1 := (httpClient1.Transport).(*http.Transport)
   466  
   467  	httpClient2, err := g.httpClient()
   468  
   469  	if err != nil {
   470  		t.Fatal(err)
   471  	}
   472  
   473  	if httpClient2 == nil {
   474  		t.Fatalf("Expected non nil value for http client")
   475  	}
   476  
   477  	transport2 := (httpClient2.Transport).(*http.Transport)
   478  
   479  	if transport1 != transport2 {
   480  		t.Fatalf("Expected default transport to be reused")
   481  	}
   482  }
   483  
   484  func TestHTTPTransportOption(t *testing.T) {
   485  	transport := &http.Transport{}
   486  
   487  	g := HTTPGetter{}
   488  	g.opts.transport = transport
   489  	httpClient1, err := g.httpClient()
   490  
   491  	if err != nil {
   492  		t.Fatal(err)
   493  	}
   494  
   495  	if httpClient1 == nil {
   496  		t.Fatalf("Expected non nil value for http client")
   497  	}
   498  
   499  	transport1 := (httpClient1.Transport).(*http.Transport)
   500  
   501  	if transport1 != transport {
   502  		t.Fatalf("Expected transport option to be applied")
   503  	}
   504  
   505  	httpClient2, err := g.httpClient()
   506  
   507  	if err != nil {
   508  		t.Fatal(err)
   509  	}
   510  
   511  	if httpClient2 == nil {
   512  		t.Fatalf("Expected non nil value for http client")
   513  	}
   514  
   515  	transport2 := (httpClient2.Transport).(*http.Transport)
   516  
   517  	if transport1 != transport2 {
   518  		t.Fatalf("Expected applied transport to be reused")
   519  	}
   520  
   521  	g = HTTPGetter{}
   522  	g.opts.url = "https://localhost"
   523  	g.opts.certFile = "testdata/client.crt"
   524  	g.opts.keyFile = "testdata/client.key"
   525  	g.opts.insecureSkipVerifyTLS = true
   526  	g.opts.transport = transport
   527  	usedTransport := verifyInsecureSkipVerify(t, &g, "HTTPGetter with 2 way ssl", false)
   528  	if usedTransport.TLSClientConfig != nil {
   529  		t.Fatal("transport.TLSClientConfig should not be set")
   530  	}
   531  }