Skip to content

Commit

Permalink
feat(helm): Support Bearer token auth when get chart
Browse files Browse the repository at this point in the history
Signed-off-by: lemonli <liwenjun0323@gmail.com>
  • Loading branch information
lemonli committed Jul 13, 2020
1 parent 97774ee commit 258f537
Show file tree
Hide file tree
Showing 15 changed files with 185 additions and 8 deletions.
1 change: 1 addition & 0 deletions cmd/helm/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.BoolVar(&c.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download")
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.StringVar(&c.Token, "token", "", "chart repository bearer token where to locate the requested chart")
}

// bindOutputFlag will add the output flag to the given command and bind the
Expand Down
4 changes: 4 additions & 0 deletions cmd/helm/repo_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ type repoAddOptions struct {

repoFile string
repoCache string

token string
}

func newRepoAddCmd(out io.Writer) *cobra.Command {
Expand Down Expand Up @@ -77,6 +79,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository")
f.StringVar(&o.token, "token", "", "chart repository bearer token")

return cmd
}
Expand Down Expand Up @@ -123,6 +126,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
KeyFile: o.keyFile,
CAFile: o.caFile,
InsecureSkipTLSverify: o.insecureSkipTLSverify,
Token: o.token,
}

r, err := repo.NewChartRepository(&c, getter.All(settings))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
apiVersion: v1
entries: {}
generated: "2020-06-23T10:01:59.2530763-07:00"
generated: "2020-07-09T14:52:16.150791+08:00"
4 changes: 3 additions & 1 deletion pkg/action/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ type ChartPathOptions struct {
Username string // --username
Verify bool // --verify
Version string // --version
Token string // --token
}

// NewInstall creates a new Install object with the given configuration.
Expand Down Expand Up @@ -646,6 +647,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
getter.WithBasicAuth(c.Username, c.Password),
getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile),
getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify),
getter.WithBearerToken(c.Token),
},
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
Expand All @@ -655,7 +657,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
}
if c.RepoURL != "" {
chartURL, err := repo.FindChartInAuthRepoURL(c.RepoURL, c.Username, c.Password, name, version,
c.CertFile, c.KeyFile, c.CaFile, getter.All(settings))
c.CertFile, c.KeyFile, c.CaFile, c.Token, getter.All(settings))
if err != nil {
return "", err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/action/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
getter.WithBasicAuth(p.Username, p.Password),
getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile),
getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify),
getter.WithBearerToken(p.Token),
},
RepositoryConfig: p.Settings.RepositoryConfig,
RepositoryCache: p.Settings.RepositoryCache,
Expand All @@ -89,7 +90,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
}

if p.RepoURL != "" {
chartURL, err := repo.FindChartInAuthRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, getter.All(p.Settings))
chartURL, err := repo.FindChartInAuthRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.Token, getter.All(p.Settings))
if err != nil {
return out.String(), err
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/downloader/chart_downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
getter.WithBasicAuth(rc.Username, rc.Password),
)
}
if rc.Token != "" {
c.Options = append(
c.Options,
getter.WithBearerToken(rc.Token),
)
}

return u, nil
}

Expand Down Expand Up @@ -220,6 +227,9 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
if r.Config.Username != "" && r.Config.Password != "" {
c.Options = append(c.Options, getter.WithBasicAuth(r.Config.Username, r.Config.Password))
}
if r.Config.Token != "" {
c.Options = append(c.Options, getter.WithBearerToken(r.Config.Token))
}
}

// Next, we need to load the index, and actually look up the chart.
Expand Down
58 changes: 58 additions & 0 deletions pkg/downloader/chart_downloader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"testing"

"helm.sh/helm/v3/internal/test/ensure"
Expand Down Expand Up @@ -322,6 +323,63 @@ func TestDownloadTo_VerifyLater(t *testing.T) {
}
}

func TestDownloadTo_Token(t *testing.T) {
// Set up a fake repo with bearer auth enabled
srv, err := repotest.NewTempServer("testdata/*.tgz*")
srv.Stop()
if err != nil {
t.Fatal(err)
}
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := strings.Split(r.Header.Get("Authorization"), "Bearer ")
if len(token) != 2 || strings.TrimSpace(token[1]) != "JWT" {
t.Errorf("Expected request to use bearer token and for token == 'JWT' got '%s'", token)
}
}))
srv.Start()
defer srv.Stop()
if err := srv.CreateIndex(); err != nil {
t.Fatal(err)
}

if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}

c := ChartDownloader{
Out: os.Stderr,
Verify: VerifyAlways,
Keyring: "testdata/helm-test-key.pub",
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
Getters: getter.All(&cli.EnvSettings{
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
}),
Options: []getter.Option{
getter.WithBearerToken("JWT"),
},
}
cname := "/signtest-0.1.0.tgz"
dest := srv.Root()
where, v, err := c.DownloadTo(srv.URL()+cname, "", dest)
if err != nil {
t.Fatal(err)
}

if expect := filepath.Join(dest, cname); where != expect {
t.Errorf("Expected download to %s, got %s", expect, where)
}

if v.FileHash == "" {
t.Error("File hash was empty, but verification is required.")
}

if _, err := os.Stat(filepath.Join(dest, cname)); err != nil {
t.Error(err)
}
}

func TestScanReposForURL(t *testing.T) {
c := ChartDownloader{
Out: os.Stderr,
Expand Down
6 changes: 4 additions & 2 deletions pkg/downloader/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {

// Any failure to resolve/download a chart should fail:
// https://github.com/helm/helm/issues/1439
churl, username, password, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos)
churl, username, password, token, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos)
if err != nil {
saveError = errors.Wrapf(err, "could not find %s", churl)
break
Expand All @@ -303,6 +303,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
Getters: m.Getters,
Options: []getter.Option{
getter.WithBasicAuth(username, password),
getter.WithBearerToken(token),
},
}

Expand Down Expand Up @@ -545,7 +546,7 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
// repoURL is the repository to search
//
// If it finds a URL that is "relative", it will prepend the repoURL.
func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, err error) {
func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password, token string, err error) {
for _, cr := range repos {
if urlutil.Equal(repoURL, cr.Config.URL) {
var entry repo.ChartVersions
Expand All @@ -564,6 +565,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
}
username = cr.Config.Username
password = cr.Config.Password
token = cr.Config.Token
return
}
}
Expand Down
5 changes: 4 additions & 1 deletion pkg/downloader/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestFindChartURL(t *testing.T) {
version := "0.1.0"
repoURL := "http://example.com/charts"

churl, username, password, err := m.findChartURL(name, version, repoURL, repos)
churl, username, password, token, err := m.findChartURL(name, version, repoURL, repos)
if err != nil {
t.Fatal(err)
}
Expand All @@ -94,6 +94,9 @@ func TestFindChartURL(t *testing.T) {
if password != "" {
t.Errorf("Unexpected password %q", password)
}
if token != "" {
t.Errorf("Unexpected token %q", token)
}
}

func TestGetRepoNames(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/downloader/testdata/repositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ repositories:
certFile: "cert"
keyFile: "key"
caFile: "ca"
- name: testing-token
url: "http://example.com"
token: "JWT"
14 changes: 14 additions & 0 deletions pkg/downloader/testdata/repository/testing-token-index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v1
entries:
foo:
- name: foo
description: Foo Chart
home: https://helm.sh/helm
keywords: []
maintainers: []
sources:
- https://github.com/helm/charts
urls:
- http://example.com/foo-1.2.3.tgz
version: 1.2.3
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
8 changes: 8 additions & 0 deletions pkg/getter/getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type options struct {
password string
userAgent string
timeout time.Duration
token string
}

// Option allows specifying various settings configurable by the user for overriding the defaults
Expand Down Expand Up @@ -83,6 +84,13 @@ func WithTLSClientConfig(certFile, keyFile, caFile string) Option {
}
}

// WithBearerToken sets the request's Authorization header to use the provided token
func WithBearerToken(token string) Option {
return func(opts *options) {
opts.token = token
}
}

// WithTimeout sets the timeout for requests
func WithTimeout(timeout time.Duration) Option {
return func(opts *options) {
Expand Down
5 changes: 5 additions & 0 deletions pkg/getter/httpgetter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package getter
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"net/http"

Expand Down Expand Up @@ -60,6 +61,10 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
req.SetBasicAuth(g.opts.username, g.opts.password)
}

if g.opts.token != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", g.opts.token))
}

client, err := g.httpClient()
if err != nil {
return nil, err
Expand Down
63 changes: 63 additions & 0 deletions pkg/getter/httpgetter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func TestHTTPGetter(t *testing.T) {
WithTLSClientConfig(pub, priv, ca),
WithInsecureSkipVerifyTLS(insecure),
WithTimeout(timeout),
WithBearerToken("JWT"),
)
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -100,6 +101,10 @@ func TestHTTPGetter(t *testing.T) {
t.Errorf("Expected NewHTTPGetter to contain %s as Timeout flag, got %s", timeout, hg.opts.timeout)
}

if hg.opts.token != "JWT" {
t.Errorf("Expected NewHTTPGetter to contain %s as the token, got %s", "JWT", hg.opts.token)
}

// Test if setting insecureSkipVerifyTLS is being passed to the ops
insecure = true

Expand Down Expand Up @@ -259,6 +264,64 @@ func TestDownloadInsecureSkipTLSVerify(t *testing.T) {

}

func TestDownloadToken(t *testing.T) {
expect := "Call me Ishmael"
expectedUserAgent := "I am Groot"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defaultUserAgent := "Helm/" + strings.TrimPrefix(version.GetVersion(), "v")
if r.UserAgent() != defaultUserAgent {
t.Errorf("Expected '%s', got '%s'", defaultUserAgent, r.UserAgent())
}
fmt.Fprint(w, expect)
}))
defer srv.Close()

g, err := All(cli.New()).ByScheme("http")
if err != nil {
t.Fatal(err)
}
got, err := g.Get(srv.URL, WithURL(srv.URL))
if err != nil {
t.Fatal(err)
}

if got.String() != expect {
t.Errorf("Expected %q, got %q", expect, got.String())
}

// test with http server
tokenSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := strings.Split(r.Header.Get("Authorization"), "Bearer ")
if len(token) != 2 || strings.TrimSpace(token[1]) != "JWT" {
t.Errorf("Expected request to use bearer token and for token == 'JWT' got '%s'", token)
}
if r.UserAgent() != expectedUserAgent {
t.Errorf("Expected '%s', got '%s'", expectedUserAgent, r.UserAgent())
}
fmt.Fprint(w, expect)
}))

defer tokenSrv.Close()

u, _ := url.ParseRequestURI(tokenSrv.URL)
httpgetter, err := NewHTTPGetter(
WithURL(u.String()),
WithBearerToken("JWT"),
WithUserAgent(expectedUserAgent),
)
if err != nil {
t.Fatal(err)
}
got, err = httpgetter.Get(u.String())
if err != nil {
t.Fatal(err)
}

if got.String() != expect {
t.Errorf("Expected %q, got %q", expect, got.String())
}
}

func TestHTTPGetterTarDownload(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
f, _ := os.Open("testdata/empty-0.0.1.tgz")
Expand Down

0 comments on commit 258f537

Please sign in to comment.