1/*
2Copyright The Helm Authors.
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License.
14*/
15
16package getter
17
18import (
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	"helm.sh/helm/v3/internal/tlsutil"
34	"helm.sh/helm/v3/internal/version"
35	"helm.sh/helm/v3/pkg/cli"
36)
37
38func 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
54	// Test with options
55	g, err = NewHTTPGetter(
56		WithBasicAuth("I", "Am"),
57		WithUserAgent("Groot"),
58		WithTLSClientConfig(pub, priv, ca),
59		WithInsecureSkipVerifyTLS(insecure),
60		WithTimeout(timeout),
61	)
62	if err != nil {
63		t.Fatal(err)
64	}
65
66	hg, ok := g.(*HTTPGetter)
67	if !ok {
68		t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter")
69	}
70
71	if hg.opts.username != "I" {
72		t.Errorf("Expected NewHTTPGetter to contain %q as the username, got %q", "I", hg.opts.username)
73	}
74
75	if hg.opts.password != "Am" {
76		t.Errorf("Expected NewHTTPGetter to contain %q as the password, got %q", "Am", hg.opts.password)
77	}
78
79	if hg.opts.userAgent != "Groot" {
80		t.Errorf("Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent)
81	}
82
83	if hg.opts.certFile != pub {
84		t.Errorf("Expected NewHTTPGetter to contain %q as the public key file, got %q", pub, hg.opts.certFile)
85	}
86
87	if hg.opts.keyFile != priv {
88		t.Errorf("Expected NewHTTPGetter to contain %q as the private key file, got %q", priv, hg.opts.keyFile)
89	}
90
91	if hg.opts.caFile != ca {
92		t.Errorf("Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile)
93	}
94
95	if hg.opts.insecureSkipVerifyTLS != insecure {
96		t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", false, hg.opts.insecureSkipVerifyTLS)
97	}
98
99	if hg.opts.timeout != timeout {
100		t.Errorf("Expected NewHTTPGetter to contain %s as Timeout flag, got %s", timeout, hg.opts.timeout)
101	}
102
103	// Test if setting insecureSkipVerifyTLS is being passed to the ops
104	insecure = true
105
106	g, err = NewHTTPGetter(
107		WithInsecureSkipVerifyTLS(insecure),
108	)
109	if err != nil {
110		t.Fatal(err)
111	}
112
113	hg, ok = g.(*HTTPGetter)
114	if !ok {
115		t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter")
116	}
117
118	if hg.opts.insecureSkipVerifyTLS != insecure {
119		t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS)
120	}
121}
122
123func TestDownload(t *testing.T) {
124	expect := "Call me Ishmael"
125	expectedUserAgent := "I am Groot"
126	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
127		defaultUserAgent := "Helm/" + strings.TrimPrefix(version.GetVersion(), "v")
128		if r.UserAgent() != defaultUserAgent {
129			t.Errorf("Expected '%s', got '%s'", defaultUserAgent, r.UserAgent())
130		}
131		fmt.Fprint(w, expect)
132	}))
133	defer srv.Close()
134
135	g, err := All(cli.New()).ByScheme("http")
136	if err != nil {
137		t.Fatal(err)
138	}
139	got, err := g.Get(srv.URL, WithURL(srv.URL))
140	if err != nil {
141		t.Fatal(err)
142	}
143
144	if got.String() != expect {
145		t.Errorf("Expected %q, got %q", expect, got.String())
146	}
147
148	// test with http server
149	basicAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
150		username, password, ok := r.BasicAuth()
151		if !ok || username != "username" || password != "password" {
152			t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
153		}
154		if r.UserAgent() != expectedUserAgent {
155			t.Errorf("Expected '%s', got '%s'", expectedUserAgent, r.UserAgent())
156		}
157		fmt.Fprint(w, expect)
158	}))
159
160	defer basicAuthSrv.Close()
161
162	u, _ := url.ParseRequestURI(basicAuthSrv.URL)
163	httpgetter, err := NewHTTPGetter(
164		WithURL(u.String()),
165		WithBasicAuth("username", "password"),
166		WithUserAgent(expectedUserAgent),
167	)
168	if err != nil {
169		t.Fatal(err)
170	}
171	got, err = httpgetter.Get(u.String())
172	if err != nil {
173		t.Fatal(err)
174	}
175
176	if got.String() != expect {
177		t.Errorf("Expected %q, got %q", expect, got.String())
178	}
179}
180
181func TestDownloadTLS(t *testing.T) {
182	cd := "../../testdata"
183	ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem")
184
185	tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
186	tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca)
187	if err != nil {
188		t.Fatal(errors.Wrap(err, "can't create TLS config for client"))
189	}
190	tlsConf.BuildNameToCertificate()
191	tlsConf.ServerName = "helm.sh"
192	tlsSrv.TLS = tlsConf
193	tlsSrv.StartTLS()
194	defer tlsSrv.Close()
195
196	u, _ := url.ParseRequestURI(tlsSrv.URL)
197	g, err := NewHTTPGetter(
198		WithURL(u.String()),
199		WithTLSClientConfig(pub, priv, ca),
200	)
201	if err != nil {
202		t.Fatal(err)
203	}
204
205	if _, err := g.Get(u.String()); err != nil {
206		t.Error(err)
207	}
208
209	// now test with TLS config being passed along in .Get (see #6635)
210	g, err = NewHTTPGetter()
211	if err != nil {
212		t.Fatal(err)
213	}
214
215	if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)); err != nil {
216		t.Error(err)
217	}
218
219	// test with only the CA file (see also #6635)
220	g, err = NewHTTPGetter()
221	if err != nil {
222		t.Fatal(err)
223	}
224
225	if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)); err != nil {
226		t.Error(err)
227	}
228}
229
230func TestDownloadInsecureSkipTLSVerify(t *testing.T) {
231	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
232	defer ts.Close()
233
234	u, _ := url.ParseRequestURI(ts.URL)
235
236	// Ensure the default behaviour did not change
237	g, err := NewHTTPGetter(
238		WithURL(u.String()),
239	)
240	if err != nil {
241		t.Error(err)
242	}
243
244	if _, err := g.Get(u.String()); err == nil {
245		t.Errorf("Expected Getter to throw an error, got %s", err)
246	}
247
248	// Test certificate check skip
249	g, err = NewHTTPGetter(
250		WithURL(u.String()),
251		WithInsecureSkipVerifyTLS(true),
252	)
253	if err != nil {
254		t.Error(err)
255	}
256	if _, err = g.Get(u.String()); err != nil {
257		t.Error(err)
258	}
259
260}
261
262func TestHTTPGetterTarDownload(t *testing.T) {
263	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
264		f, _ := os.Open("testdata/empty-0.0.1.tgz")
265		defer f.Close()
266
267		b := make([]byte, 512)
268		f.Read(b)
269		//Get the file size
270		FileStat, _ := f.Stat()
271		FileSize := strconv.FormatInt(FileStat.Size(), 10)
272
273		//Simulating improper header values from bitbucket
274		w.Header().Set("Content-Type", "application/x-tar")
275		w.Header().Set("Content-Encoding", "gzip")
276		w.Header().Set("Content-Length", FileSize)
277
278		f.Seek(0, 0)
279		io.Copy(w, f)
280	}))
281
282	defer srv.Close()
283
284	g, err := NewHTTPGetter(WithURL(srv.URL))
285	if err != nil {
286		t.Fatal(err)
287	}
288
289	data, _ := g.Get(srv.URL)
290	mimeType := http.DetectContentType(data.Bytes())
291
292	expectedMimeType := "application/x-gzip"
293	if mimeType != expectedMimeType {
294		t.Fatalf("Expected response with MIME type %s, but got %s", expectedMimeType, mimeType)
295	}
296}
297
298func TestHttpClientInsecureSkipVerify(t *testing.T) {
299	g := HTTPGetter{}
300	g.opts.url = "https://localhost"
301	verifyInsecureSkipVerify(t, g, "Blank HTTPGetter", false)
302
303	g = HTTPGetter{}
304	g.opts.url = "https://localhost"
305	g.opts.caFile = "testdata/ca.crt"
306	verifyInsecureSkipVerify(t, g, "HTTPGetter with ca file", false)
307
308	g = HTTPGetter{}
309	g.opts.url = "https://localhost"
310	g.opts.insecureSkipVerifyTLS = true
311	verifyInsecureSkipVerify(t, g, "HTTPGetter with skip cert verification only", true)
312
313	g = HTTPGetter{}
314	g.opts.url = "https://localhost"
315	g.opts.certFile = "testdata/client.crt"
316	g.opts.keyFile = "testdata/client.key"
317	g.opts.insecureSkipVerifyTLS = true
318	transport := verifyInsecureSkipVerify(t, g, "HTTPGetter with 2 way ssl", true)
319	if len(transport.TLSClientConfig.Certificates) <= 0 {
320		t.Fatal("transport.TLSClientConfig.Certificates is not present")
321	}
322	if transport.TLSClientConfig.ServerName == "" {
323		t.Fatal("TLSClientConfig.ServerName is blank")
324	}
325}
326
327func verifyInsecureSkipVerify(t *testing.T, g HTTPGetter, caseName string, expectedValue bool) *http.Transport {
328	returnVal, err := g.httpClient()
329
330	if err != nil {
331		t.Fatal(err)
332	}
333
334	if returnVal == nil {
335		t.Fatalf("Expected non nil value for http client")
336	}
337	transport := (returnVal.Transport).(*http.Transport)
338	gotValue := false
339	if transport.TLSClientConfig != nil {
340		gotValue = transport.TLSClientConfig.InsecureSkipVerify
341	}
342	if gotValue != expectedValue {
343		t.Fatalf("Case Name = %s\nInsecureSkipVerify did not come as expected. Expected = %t; Got = %v",
344			caseName, expectedValue, gotValue)
345	}
346	return transport
347}
348