diff --git a/cmd/helm/dependency_build.go b/cmd/helm/dependency_build.go index 326435de2..062307611 100644 --- a/cmd/helm/dependency_build.go +++ b/cmd/helm/dependency_build.go @@ -17,11 +17,13 @@ package main import ( "io" + "net/http" "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/downloader" "k8s.io/helm/cmd/helm/helmpath" + "k8s.io/helm/pkg/util" ) const dependencyBuildDesc = ` @@ -36,11 +38,16 @@ of 'helm dependency update'. ` type dependencyBuildCmd struct { - out io.Writer chartpath string verify bool keyring string helmhome helmpath.Home + + certFile string + keyFile string + caFile string + + out io.Writer } func newDependencyBuildCmd(out io.Writer) *cobra.Command { @@ -66,16 +73,30 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVar(&dbc.verify, "verify", false, "verify the packages against signatures") f.StringVar(&dbc.keyring, "keyring", defaultKeyring(), "keyring containing public keys") + f.StringVar(&dbc.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&dbc.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.StringVar(&dbc.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") return cmd } func (d *dependencyBuildCmd) run() error { + var client *http.Client + var err error + if d.certFile != "" && d.keyFile != "" && d.caFile != "" { + client, err = util.NewHTTPClientTLS(d.certFile, d.keyFile, d.caFile) + if err != nil { + return err + } + } else { + client = http.DefaultClient + } man := &downloader.Manager{ Out: d.out, ChartPath: d.chartpath, HelmHome: d.helmhome, Keyring: d.keyring, + Client: client, } if d.verify { man.Verify = downloader.VerifyIfPossible diff --git a/cmd/helm/dependency_update.go b/cmd/helm/dependency_update.go index b80b632dd..fbac0b8e5 100644 --- a/cmd/helm/dependency_update.go +++ b/cmd/helm/dependency_update.go @@ -17,11 +17,13 @@ package main import ( "io" + "net/http" "path/filepath" "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/downloader" "k8s.io/helm/cmd/helm/helmpath" + "k8s.io/helm/pkg/util" ) const dependencyUpDesc = ` @@ -36,11 +38,16 @@ rebuild the requirements to an exact version. // dependencyUpdateCmd describes a 'helm dependency update' type dependencyUpdateCmd struct { - out io.Writer chartpath string helmhome helmpath.Home verify bool keyring string + + certFile string + keyFile string + caFile string + + out io.Writer } // newDependencyUpdateCmd creates a new dependency update command. @@ -75,17 +82,31 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVar(&duc.verify, "verify", false, "verify the packages against signatures") f.StringVar(&duc.keyring, "keyring", defaultKeyring(), "keyring containing public keys") + f.StringVar(&duc.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&duc.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.StringVar(&duc.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") return cmd } // run runs the full dependency update process. func (d *dependencyUpdateCmd) run() error { + var client *http.Client + var err error + if d.certFile != "" && d.keyFile != "" && d.caFile != "" { + client, err = util.NewHTTPClientTLS(d.certFile, d.keyFile, d.caFile) + if err != nil { + return err + } + } else { + client = http.DefaultClient + } man := &downloader.Manager{ Out: d.out, ChartPath: d.chartpath, HelmHome: d.helmhome, Keyring: d.keyring, + Client: client, } if d.verify { man.Verify = downloader.VerifyIfPossible diff --git a/cmd/helm/downloader/chart_downloader.go b/cmd/helm/downloader/chart_downloader.go index 84def6cd0..d77f6fb7c 100644 --- a/cmd/helm/downloader/chart_downloader.go +++ b/cmd/helm/downloader/chart_downloader.go @@ -58,6 +58,8 @@ type ChartDownloader struct { Keyring string // HelmHome is the $HELM_HOME. HelmHome helmpath.Home + // A Client is an HTTP client. + Client *http.Client } // DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. @@ -76,7 +78,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven if err != nil { return "", nil, err } - data, err := download(u.String()) + data, err := download(u.String(), c.Client) if err != nil { return "", nil, err } @@ -91,7 +93,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven ver := &provenance.Verification{} if c.Verify > VerifyNever { - body, err := download(u.String() + ".prov") + body, err := download(u.String()+".prov", c.Client) if err != nil { if c.Verify == VerifyAlways { return destfile, ver, fmt.Errorf("Failed to fetch provenance %q", u.String()+".prov") @@ -211,10 +213,10 @@ func VerifyChart(path string, keyring string) (*provenance.Verification, error) } // download performs a simple HTTP Get and returns the body. -func download(href string) (*bytes.Buffer, error) { +func download(href string, client *http.Client) (*bytes.Buffer, error) { buf := bytes.NewBuffer(nil) - resp, err := http.Get(href) + resp, err := client.Get(href) if err != nil { return buf, err } diff --git a/cmd/helm/downloader/chart_downloader_test.go b/cmd/helm/downloader/chart_downloader_test.go index d2cf191f9..5ceda2abd 100644 --- a/cmd/helm/downloader/chart_downloader_test.go +++ b/cmd/helm/downloader/chart_downloader_test.go @@ -82,7 +82,7 @@ func TestDownload(t *testing.T) { })) defer srv.Close() - got, err := download(srv.URL) + got, err := download(srv.URL, http.DefaultClient) if err != nil { t.Fatal(err) } @@ -132,6 +132,7 @@ func TestDownloadTo(t *testing.T) { Out: os.Stderr, Verify: VerifyAlways, Keyring: "testdata/helm-test-key.pub", + Client: http.DefaultClient, } cname := "/signtest-0.1.0.tgz" where, v, err := c.DownloadTo(srv.URL()+cname, "", dest) diff --git a/cmd/helm/downloader/manager.go b/cmd/helm/downloader/manager.go index db3d3c4f0..88de22e85 100644 --- a/cmd/helm/downloader/manager.go +++ b/cmd/helm/downloader/manager.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "net/url" "os" "path" @@ -49,6 +50,8 @@ type Manager struct { Verify VerificationStrategy // Keyring is the key ring file. Keyring string + // A Client is an HTTP client. + Client *http.Client } // Build rebuilds a local charts directory from a lockfile. @@ -179,6 +182,7 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error { Verify: m.Verify, Keyring: m.Keyring, HelmHome: m.HelmHome, + Client: m.Client, } destPath := filepath.Join(m.ChartPath, "charts") @@ -295,7 +299,7 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) { for _, re := range repos { wg.Add(1) go func(n, u string) { - if err := repo.DownloadIndexFile(n, u, m.HelmHome.CacheIndex(n)); err != nil { + if err := repo.DownloadIndexFile(n, u, m.HelmHome.CacheIndex(n), http.DefaultClient); err != nil { fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", n, u, err) } else { fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", n) diff --git a/cmd/helm/fetch.go b/cmd/helm/fetch.go index c67b4714a..4837486e3 100644 --- a/cmd/helm/fetch.go +++ b/cmd/helm/fetch.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" "path/filepath" @@ -27,6 +28,7 @@ import ( "k8s.io/helm/cmd/helm/downloader" "k8s.io/helm/cmd/helm/helmpath" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/util" ) const fetchDesc = ` @@ -54,6 +56,10 @@ type fetchCmd struct { verify bool keyring string + certFile string + keyFile string + caFile string + out io.Writer } @@ -85,17 +91,30 @@ func newFetchCmd(out io.Writer) *cobra.Command { f.StringVar(&fch.version, "version", "", "specific version of a chart. Without this, the latest version is fetched") f.StringVar(&fch.keyring, "keyring", defaultKeyring(), "keyring containing public keys") f.StringVarP(&fch.destdir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this") + f.StringVar(&fch.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") return cmd } func (f *fetchCmd) run() error { - pname := f.chartRef + var client *http.Client + var err error + if f.certFile != "" && f.keyFile != "" && f.caFile != "" { + client, err = util.NewHTTPClientTLS(f.certFile, f.keyFile, f.caFile) + if err != nil { + return err + } + } else { + client = http.DefaultClient + } c := downloader.ChartDownloader{ HelmHome: helmpath.Home(homePath()), Out: f.out, Keyring: f.keyring, Verify: downloader.VerifyNever, + Client: client, } if f.verify { @@ -114,7 +133,7 @@ func (f *fetchCmd) run() error { defer os.RemoveAll(dest) } - saved, v, err := c.DownloadTo(pname, f.version, dest) + saved, v, err := c.DownloadTo(f.chartRef, f.version, dest) if err != nil { return err } diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 8bce43384..6920b51d0 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "io" + "net/http" "os" "github.com/spf13/cobra" @@ -172,7 +173,7 @@ func ensureHome(home helmpath.Home, out io.Writer) error { return err } cif := home.CacheIndex(stableRepository) - if err := repo.DownloadIndexFile(stableRepository, stableRepositoryURL, cif); err != nil { + if err := repo.DownloadIndexFile(stableRepository, stableRepositoryURL, cif, http.DefaultClient); err != nil { fmt.Fprintf(out, "WARNING: Failed to download %s: %s (run 'helm repo update')\n", stableRepository, err) } } else if fi.IsDir() { diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 169e8a805..5dbceb3c0 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" "path/filepath" "strconv" @@ -360,6 +361,8 @@ func locateChartPath(name, version string, verify bool, keyring string) (string, HelmHome: helmpath.Home(homePath()), Out: os.Stdout, Keyring: keyring, + // TODO: this should be configurable + Client: http.DefaultClient, } if verify { dl.Verify = downloader.VerifyAlways diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 414e962fa..a6b1fac3a 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -19,20 +19,27 @@ package main import ( "fmt" "io" + "net/http" "path/filepath" "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/helmpath" "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/util" ) type repoAddCmd struct { name string url string home helmpath.Home - out io.Writer noupdate bool + + certFile string + keyFile string + caFile string + + out io.Writer } func newRepoAddCmd(out io.Writer) *cobra.Command { @@ -55,17 +62,32 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { return add.run() }, } + f := cmd.Flags() f.BoolVar(&add.noupdate, "no-update", false, "raise error if repo is already registered") + f.StringVar(&add.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&add.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.StringVar(&add.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + return cmd } func (a *repoAddCmd) run() error { + var client *http.Client var err error + if a.certFile != "" && a.keyFile != "" && a.caFile != "" { + client, err = util.NewHTTPClientTLS(a.certFile, a.keyFile, a.caFile) + if err != nil { + return err + } + } else { + client = http.DefaultClient + } + if a.noupdate { - err = addRepository(a.name, a.url, a.home) + err = addRepository(a.name, a.url, a.home, client) } else { - err = updateRepository(a.name, a.url, a.home) + err = updateRepository(a.name, a.url, a.home, client) } if err != nil { return err @@ -74,9 +96,9 @@ func (a *repoAddCmd) run() error { return nil } -func addRepository(name, url string, home helmpath.Home) error { +func addRepository(name, url string, home helmpath.Home, client *http.Client) error { cif := home.CacheIndex(name) - if err := repo.DownloadIndexFile(name, url, cif); err != nil { + if err := repo.DownloadIndexFile(name, url, cif, client); err != nil { return fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", url, err.Error()) } @@ -101,9 +123,9 @@ func insertRepoLine(name, url string, home helmpath.Home) error { return f.WriteFile(home.RepositoryFile(), 0644) } -func updateRepository(name, url string, home helmpath.Home) error { +func updateRepository(name, url string, home helmpath.Home, client *http.Client) error { cif := home.CacheIndex(name) - if err := repo.DownloadIndexFile(name, url, cif); err != nil { + if err := repo.DownloadIndexFile(name, url, cif, client); err != nil { return err } diff --git a/cmd/helm/repo_add_test.go b/cmd/helm/repo_add_test.go index c9c255433..ab46a7993 100644 --- a/cmd/helm/repo_add_test.go +++ b/cmd/helm/repo_add_test.go @@ -18,6 +18,7 @@ package main import ( "bytes" + "net/http" "os" "testing" @@ -80,7 +81,7 @@ func TestRepoAdd(t *testing.T) { t.Fatal(err) } - if err := addRepository(testName, ts.URL(), hh); err != nil { + if err := addRepository(testName, ts.URL(), hh, http.DefaultClient); err != nil { t.Error(err) } @@ -93,11 +94,11 @@ func TestRepoAdd(t *testing.T) { t.Errorf("%s was not successfully inserted into %s", testName, hh.RepositoryFile()) } - if err := updateRepository(testName, ts.URL(), hh); err != nil { + if err := updateRepository(testName, ts.URL(), hh, http.DefaultClient); err != nil { t.Errorf("Repository was not updated: %s", err) } - if err := addRepository(testName, ts.URL(), hh); err == nil { + if err := addRepository(testName, ts.URL(), hh, http.DefaultClient); err == nil { t.Errorf("Duplicate repository name was added") } } diff --git a/cmd/helm/repo_update.go b/cmd/helm/repo_update.go index 7e04b5e27..3b0aa815d 100644 --- a/cmd/helm/repo_update.go +++ b/cmd/helm/repo_update.go @@ -20,12 +20,14 @@ import ( "errors" "fmt" "io" + "net/http" "sync" "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/helmpath" "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/util" ) const updateDesc = ` @@ -37,9 +39,14 @@ future releases. ` type repoUpdateCmd struct { - update func([]*repo.Entry, bool, io.Writer, helmpath.Home) - out io.Writer + update func([]*repo.Entry, bool, io.Writer, helmpath.Home, *http.Client) home helmpath.Home + + certFile string + keyFile string + caFile string + + out io.Writer } func newRepoUpdateCmd(out io.Writer) *cobra.Command { @@ -57,10 +64,27 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command { return u.run() }, } + + f := cmd.Flags() + f.StringVar(&u.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&u.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.StringVar(&u.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + return cmd } func (u *repoUpdateCmd) run() error { + var client *http.Client + var err error + if u.certFile != "" && u.keyFile != "" && u.caFile != "" { + client, err = util.NewHTTPClientTLS(u.certFile, u.keyFile, u.caFile) + if err != nil { + return err + } + } else { + client = http.DefaultClient + } + f, err := repo.LoadRepositoriesFile(u.home.RepositoryFile()) if err != nil { return err @@ -70,11 +94,11 @@ func (u *repoUpdateCmd) run() error { return errors.New("no repositories found. You must add one before updating") } - u.update(f.Repositories, flagDebug, u.out, u.home) + u.update(f.Repositories, flagDebug, u.out, u.home, client) return nil } -func updateCharts(repos []*repo.Entry, verbose bool, out io.Writer, home helmpath.Home) { +func updateCharts(repos []*repo.Entry, verbose bool, out io.Writer, home helmpath.Home, client *http.Client) { fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...") var wg sync.WaitGroup for _, re := range repos { @@ -85,7 +109,7 @@ func updateCharts(repos []*repo.Entry, verbose bool, out io.Writer, home helmpat // We skip local because the indices are symlinked. return } - err := repo.DownloadIndexFile(n, u, home.CacheIndex(n)) + err := repo.DownloadIndexFile(n, u, home.CacheIndex(n), client) if err != nil { fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", n, u, err) } else { diff --git a/cmd/helm/repo_update_test.go b/cmd/helm/repo_update_test.go index 5906bd62d..09544465c 100644 --- a/cmd/helm/repo_update_test.go +++ b/cmd/helm/repo_update_test.go @@ -19,6 +19,7 @@ import ( "bytes" "fmt" "io" + "net/http" "os" "strings" "testing" @@ -43,7 +44,7 @@ func TestUpdateCmd(t *testing.T) { out := bytes.NewBuffer(nil) // Instead of using the HTTP updater, we provide our own for this test. // The TestUpdateCharts test verifies the HTTP behavior independently. - updater := func(repos []*repo.Entry, verbose bool, out io.Writer, home helmpath.Home) { + updater := func(repos []*repo.Entry, verbose bool, out io.Writer, home helmpath.Home, client *http.Client) { for _, re := range repos { fmt.Fprintln(out, re.Name) } @@ -83,7 +84,7 @@ func TestUpdateCharts(t *testing.T) { repos := []*repo.Entry{ {Name: "charts", URL: srv.URL()}, } - updateCharts(repos, false, buf, helmpath.Home(thome)) + updateCharts(repos, false, buf, helmpath.Home(thome), http.DefaultClient) got := buf.String() if strings.Contains(got, "Unable to get an update") { diff --git a/pkg/repo/index.go b/pkg/repo/index.go index f8c27f004..2a03d2188 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -229,11 +229,11 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) { } // DownloadIndexFile fetches the index from a repository. -func DownloadIndexFile(repoName, url, indexFilePath string) error { +func DownloadIndexFile(repoName, url, indexFilePath string, client *http.Client) error { var indexURL string indexURL = strings.TrimSuffix(url, "/") + "/index.yaml" - resp, err := http.Get(indexURL) + resp, err := client.Get(indexURL) if err != nil { return err } diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go index cbeb01bcb..7e10c86d5 100644 --- a/pkg/repo/index_test.go +++ b/pkg/repo/index_test.go @@ -133,7 +133,7 @@ func TestDownloadIndexFile(t *testing.T) { defer os.RemoveAll(dirName) path := filepath.Join(dirName, testRepo+"-index.yaml") - if err := DownloadIndexFile(testRepo, srv.URL, path); err != nil { + if err := DownloadIndexFile(testRepo, srv.URL, path, http.DefaultClient); err != nil { t.Errorf("%#v", err) } diff --git a/pkg/util/crypto.go b/pkg/util/crypto.go new file mode 100644 index 000000000..f6aa4d97b --- /dev/null +++ b/pkg/util/crypto.go @@ -0,0 +1,68 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" +) + +// NewClientTLS returns tls.Config appropriate for client auth. +func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) { + cert, err := CertFromFilePair(certFile, keyFile) + if err != nil { + return nil, err + } + cp, err := CertPoolFromFile(caFile) + if err != nil { + return nil, err + } + return &tls.Config{ + Certificates: []tls.Certificate{*cert}, + RootCAs: cp, + }, nil +} + +// CertPoolFromFile returns an x509.CertPool containing the certificates +// in the given PEM-encoded file. +// Returns an error if the file could not be read, a certificate could not +// be parsed, or if the file does not contain any certificates +func CertPoolFromFile(filename string) (*x509.CertPool, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("can't read CA file: %v", filename) + } + cp := x509.NewCertPool() + if !cp.AppendCertsFromPEM(b) { + return nil, fmt.Errorf("failed to append certificates from file: %s", filename) + } + return cp, nil +} + +// CertFromFilePair returns an tls.Certificate containing the +// certificates public/private key pair from a pair of given PEM-encoded files. +// Returns an error if the file could not be read, a certificate could not +// be parsed, or if the file does not contain any certificates +func CertFromFilePair(certFile, keyFile string) (*tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, fmt.Errorf("can't load key pair from cert %s and key %s", certFile, keyFile) + } + return &cert, err +} diff --git a/pkg/util/http.go b/pkg/util/http.go new file mode 100644 index 000000000..bccf9ca95 --- /dev/null +++ b/pkg/util/http.go @@ -0,0 +1,37 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "net/http" +) + +// NewHTTPClientTLS constructs http.Client with configured TLS for http.Transport +func NewHTTPClientTLS(certFile, keyFile, caFile string) (*http.Client, error) { + tlsConf, err := NewClientTLS(certFile, keyFile, caFile) + if err != nil { + return nil, fmt.Errorf("can't create TLS config for client: %s", err.Error()) + } + tlsConf.BuildNameToCertificate() + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConf, + }, + } + return client, nil +}