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 }