Add client with TLS transport (#1)

add client with TLS transport
pull/1670/head
Anton Galitsyn 9 years ago committed by GitHub
parent 5517d00a48
commit f97f0fd1e0

@ -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

@ -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

@ -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
}

@ -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)

@ -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)

@ -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
}

@ -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() {

@ -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

@ -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
}

@ -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")
}
}

@ -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 {

@ -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") {

@ -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
}

@ -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)
}

@ -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
}

@ -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
}
Loading…
Cancel
Save