github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/getter/httpgetter.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 "bytes" 20 "crypto/tls" 21 "io" 22 "net/http" 23 "net/url" 24 "sync" 25 26 "github.com/pkg/errors" 27 28 "github.com/stefanmcshane/helm/internal/tlsutil" 29 "github.com/stefanmcshane/helm/internal/urlutil" 30 "github.com/stefanmcshane/helm/internal/version" 31 ) 32 33 // HTTPGetter is the default HTTP(/S) backend handler 34 type HTTPGetter struct { 35 opts options 36 transport *http.Transport 37 once sync.Once 38 } 39 40 // Get performs a Get from repo.Getter and returns the body. 41 func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { 42 for _, opt := range options { 43 opt(&g.opts) 44 } 45 return g.get(href) 46 } 47 48 func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) { 49 // Set a helm specific user agent so that a repo server and metrics can 50 // separate helm calls from other tools interacting with repos. 51 req, err := http.NewRequest(http.MethodGet, href, nil) 52 if err != nil { 53 return nil, err 54 } 55 56 req.Header.Set("User-Agent", version.GetUserAgent()) 57 if g.opts.userAgent != "" { 58 req.Header.Set("User-Agent", g.opts.userAgent) 59 } 60 61 // Before setting the basic auth credentials, make sure the URL associated 62 // with the basic auth is the one being fetched. 63 u1, err := url.Parse(g.opts.url) 64 if err != nil { 65 return nil, errors.Wrap(err, "Unable to parse getter URL") 66 } 67 u2, err := url.Parse(href) 68 if err != nil { 69 return nil, errors.Wrap(err, "Unable to parse URL getting from") 70 } 71 72 // Host on URL (returned from url.Parse) contains the port if present. 73 // This check ensures credentials are not passed between different 74 // services on different ports. 75 if g.opts.passCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { 76 if g.opts.username != "" && g.opts.password != "" { 77 req.SetBasicAuth(g.opts.username, g.opts.password) 78 } 79 } 80 81 client, err := g.httpClient() 82 if err != nil { 83 return nil, err 84 } 85 86 resp, err := client.Do(req) 87 if err != nil { 88 return nil, err 89 } 90 defer resp.Body.Close() 91 if resp.StatusCode != http.StatusOK { 92 return nil, errors.Errorf("failed to fetch %s : %s", href, resp.Status) 93 } 94 95 buf := bytes.NewBuffer(nil) 96 _, err = io.Copy(buf, resp.Body) 97 return buf, err 98 } 99 100 // NewHTTPGetter constructs a valid http/https client as a Getter 101 func NewHTTPGetter(options ...Option) (Getter, error) { 102 var client HTTPGetter 103 104 for _, opt := range options { 105 opt(&client.opts) 106 } 107 108 return &client, nil 109 } 110 111 func (g *HTTPGetter) httpClient() (*http.Client, error) { 112 if g.opts.transport != nil { 113 return &http.Client{ 114 Transport: g.opts.transport, 115 Timeout: g.opts.timeout, 116 }, nil 117 } 118 119 g.once.Do(func() { 120 g.transport = &http.Transport{ 121 DisableCompression: true, 122 Proxy: http.ProxyFromEnvironment, 123 } 124 }) 125 126 if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" { 127 tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile) 128 if err != nil { 129 return nil, errors.Wrap(err, "can't create TLS config for client") 130 } 131 132 sni, err := urlutil.ExtractHostname(g.opts.url) 133 if err != nil { 134 return nil, err 135 } 136 tlsConf.ServerName = sni 137 138 g.transport.TLSClientConfig = tlsConf 139 } 140 141 if g.opts.insecureSkipVerifyTLS { 142 if g.transport.TLSClientConfig == nil { 143 g.transport.TLSClientConfig = &tls.Config{ 144 InsecureSkipVerify: true, 145 } 146 } else { 147 g.transport.TLSClientConfig.InsecureSkipVerify = true 148 } 149 } 150 151 client := &http.Client{ 152 Transport: g.transport, 153 Timeout: g.opts.timeout, 154 } 155 156 return client, nil 157 }