Fix chart pull/push TLS options

* Add more TLS options to registry client
* Make TLS option of helm pull work with oci repo
* Make helm install work with oci repo

Signed-off-by: Chen Winston <winston.chen@nokia-sbell.com>
pull/9564/head
Chen Winston 5 years ago
parent f171c33dc7
commit d57af976d7

@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
) )
@ -33,6 +34,7 @@ This will store the chart in the local registry cache to be used later.
func newChartPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newChartPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var insecureOpt, plainHTTPOpt bool var insecureOpt, plainHTTPOpt bool
var caFile, certFile, keyFile string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "pull [ref]", Use: "pull [ref]",
Short: "pull a chart from remote", Short: "pull a chart from remote",
@ -41,13 +43,30 @@ func newChartPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Hidden: !FeatureGateOCI.IsEnabled(), Hidden: !FeatureGateOCI.IsEnabled(),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ref := args[0] ref := args[0]
return action.NewChartPull(cfg).Run(out, ref, insecureOpt, plainHTTPOpt) registryClient, err := registry.NewClient(
registry.ClientOptDebug(settings.Debug),
registry.ClientOptWriter(out),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
registry.ClientOptPlainHTTP(plainHTTPOpt),
registry.ClientOptInsecureSkipVerifyTLS(insecureOpt),
registry.ClientOptCAFile(caFile),
registry.ClientOptCertKeyFiles(certFile, keyFile),
)
if err != nil {
return err
}
cfg.RegistryClient = registryClient
return action.NewChartPull(cfg).Run(out, ref)
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVarP(&insecureOpt, "insecure", "", false, "allow connections to TLS registry without certs") f.BoolVarP(&insecureOpt, "insecure-skip-tls-verify", "", false, "skip registry tls certificate checks")
f.BoolVarP(&plainHTTPOpt, "plain-http", "", false, "use plain http and not https") f.BoolVarP(&plainHTTPOpt, "plain-http", "", false, "use plain http to connect to the registry instead of https")
f.StringVar(&certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&caFile, "ca-file", "", "verify certificates of HTTPS-enabled registry using this CA bundle")
return cmd return cmd
} }

@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
) )
@ -35,6 +36,7 @@ Must first run "helm chart save" or "helm chart pull".
func newChartPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newChartPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var insecureOpt, plainHTTPOpt bool var insecureOpt, plainHTTPOpt bool
var caFile, certFile, keyFile string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "push [ref]", Use: "push [ref]",
Short: "push a chart to remote", Short: "push a chart to remote",
@ -43,13 +45,29 @@ func newChartPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Hidden: !FeatureGateOCI.IsEnabled(), Hidden: !FeatureGateOCI.IsEnabled(),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ref := args[0] ref := args[0]
return action.NewChartPush(cfg).Run(out, ref, insecureOpt, plainHTTPOpt) registryClient, err := registry.NewClient(
registry.ClientOptDebug(settings.Debug),
registry.ClientOptWriter(out),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
registry.ClientOptPlainHTTP(plainHTTPOpt),
registry.ClientOptInsecureSkipVerifyTLS(insecureOpt),
registry.ClientOptCAFile(caFile),
registry.ClientOptCertKeyFiles(certFile, keyFile),
)
if err != nil {
return err
}
cfg.RegistryClient = registryClient
return action.NewChartPush(cfg).Run(out, ref)
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVarP(&insecureOpt, "insecure", "", false, "allow connections to TLS registry without certs") f.BoolVarP(&insecureOpt, "insecure-skip-tls-verify", "", false, "skip registry tls certificate checks")
f.BoolVarP(&plainHTTPOpt, "plain-http", "", false, "use plain http and not https") f.BoolVarP(&plainHTTPOpt, "plain-http", "", false, "use plain http to connect to the registry instead of https")
f.StringVar(&certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&caFile, "ca-file", "", "verify certificates of HTTPS-enabled registry using this CA bundle")
return cmd return cmd
} }

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -53,6 +54,13 @@ type (
authorizer *Authorizer authorizer *Authorizer
resolver *Resolver resolver *Resolver
cache *Cache cache *Cache
// registry setting
certFile string
keyFile string
caFile string
insecureSkipVerifyTLS bool
plainHTTP bool
} }
) )
@ -78,7 +86,7 @@ func NewClient(opts ...ClientOption) (*Client, error) {
} }
} }
if client.resolver == nil { if client.resolver == nil {
resolver, err := client.newResolver(false, false) resolver, err := client.newResolver()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -121,17 +129,7 @@ func (c *Client) Logout(hostname string) error {
} }
// PushChart uploads a chart to a registry // PushChart uploads a chart to a registry
func (c *Client) PushChart(ref *Reference, insecure bool, plainHTTP bool) error { func (c *Client) PushChart(ref *Reference) error {
if insecure || plainHTTP {
resolver, err := c.newResolver(insecure, plainHTTP)
if err != nil {
return err
}
c.resolver = &Resolver{
Resolver: resolver,
}
}
r, err := c.cache.FetchReference(ref) r, err := c.cache.FetchReference(ref)
if err != nil { if err != nil {
return err return err
@ -211,17 +209,7 @@ func (c *Client) PullChart(ref *Reference) (*bytes.Buffer, error) {
// PullChartToCache pulls a chart from an OCI Registry to the Registry Cache. // PullChartToCache pulls a chart from an OCI Registry to the Registry Cache.
// This function is needed for `helm chart pull`, which is experimental and will be deprecated soon. // This function is needed for `helm chart pull`, which is experimental and will be deprecated soon.
// Likewise, the Registry cache will soon be deprecated as will this function. // Likewise, the Registry cache will soon be deprecated as will this function.
func (c *Client) PullChartToCache(ref *Reference, insecure bool, plainHTTP bool) error { func (c *Client) PullChartToCache(ref *Reference) error {
if insecure || plainHTTP {
resolver, err := c.newResolver(insecure, plainHTTP)
if err != nil {
return err
}
c.resolver = &Resolver{
Resolver: resolver,
}
}
if ref.Tag == "" { if ref.Tag == "" {
return errors.New("tag explicitly required") return errors.New("tag explicitly required")
} }
@ -357,16 +345,40 @@ func (c *Client) getChartTableRows() ([][]interface{}, error) {
return rows, nil return rows, nil
} }
func (c *Client) newResolver(insecure bool, plainHTTP bool) (resolver remotes.Resolver, err error) { func (c *Client) newResolver() (resolver remotes.Resolver, err error) {
client := http.DefaultClient config := &tls.Config{}
if insecure {
client.Transport = &http.Transport{ if c.caFile != "" {
TLSClientConfig: &tls.Config{ caCert, err := ioutil.ReadFile(c.caFile)
InsecureSkipVerify: true, if err != nil {
}, return nil, err
} }
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
config.RootCAs = caCertPool
} }
resolver, err = c.authorizer.Resolver(context.Background(), client, plainHTTP) if c.certFile != "" && c.keyFile != "" {
return cert, err := tls.LoadX509KeyPair(c.certFile, c.keyFile)
if err != nil {
return nil, err
}
config.Certificates = []tls.Certificate{cert}
}
if c.insecureSkipVerifyTLS {
config.InsecureSkipVerify = true
}
return c.authorizer.Resolver(
context.Background(),
&http.Client{
Transport: &http.Transport{
TLSClientConfig: config,
},
},
c.plainHTTP,
)
} }

@ -61,9 +61,38 @@ func ClientOptCache(cache *Cache) ClientOption {
} }
} }
// ClientOptCredentialsFile returns a function that sets the cache setting on a client options set // ClientOptCredentialsFile returns a function that sets the credentials file setting on a client options set
func ClientOptCredentialsFile(credentialsFile string) ClientOption { func ClientOptCredentialsFile(credentialsFile string) ClientOption {
return func(client *Client) { return func(client *Client) {
client.credentialsFile = credentialsFile client.credentialsFile = credentialsFile
} }
} }
// ClientOptCaFile returns a function that sets the CA file setting on a client options set
func ClientOptCAFile(caFile string) ClientOption {
return func(client *Client) {
client.caFile = caFile
}
}
// ClientOptCaFile returns a function that sets the cert/key file setting on a client options set
func ClientOptCertKeyFiles(certFile, keyFile string) ClientOption {
return func(client *Client) {
client.certFile = certFile
client.keyFile = keyFile
}
}
// ClientOptCaFile returns a function that sets the insecure setting on a client options set
func ClientOptInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) ClientOption {
return func(client *Client) {
client.insecureSkipVerifyTLS = insecureSkipVerifyTLS
}
}
// ClientOptCaFile returns a function that sets the plain http setting on a client options set
func ClientOptPlainHTTP(plainHTTP bool) ClientOption {
return func(client *Client) {
client.plainHTTP = plainHTTP
}
}

@ -186,13 +186,13 @@ func (suite *RegistryClientTestSuite) Test_3_PushChart() {
// non-existent ref // non-existent ref
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost)) ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
suite.Nil(err) suite.Nil(err)
err = suite.RegistryClient.PushChart(ref, false, false) err = suite.RegistryClient.PushChart(ref)
suite.NotNil(err) suite.NotNil(err)
// existing ref // existing ref
ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost)) ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
suite.Nil(err) suite.Nil(err)
err = suite.RegistryClient.PushChart(ref, false, false) err = suite.RegistryClient.PushChart(ref)
suite.Nil(err) suite.Nil(err)
} }

@ -35,10 +35,10 @@ func NewChartPull(cfg *Configuration) *ChartPull {
} }
// Run executes the chart pull operation // Run executes the chart pull operation
func (a *ChartPull) Run(out io.Writer, ref string, insecure bool, plainHTTP bool) error { func (a *ChartPull) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref) r, err := registry.ParseReference(ref)
if err != nil { if err != nil {
return err return err
} }
return a.cfg.RegistryClient.PullChartToCache(r, insecure, plainHTTP) return a.cfg.RegistryClient.PullChartToCache(r)
} }

@ -35,10 +35,10 @@ func NewChartPush(cfg *Configuration) *ChartPush {
} }
// Run executes the chart push operation // Run executes the chart push operation
func (a *ChartPush) Run(out io.Writer, ref string, insecure bool, plainHTTP bool) error { func (a *ChartPush) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref) r, err := registry.ParseReference(ref)
if err != nil { if err != nil {
return err return err
} }
return a.cfg.RegistryClient.PushChart(r, insecure, plainHTTP) return a.cfg.RegistryClient.PushChart(r)
} }

@ -656,6 +656,15 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
RepositoryConfig: settings.RepositoryConfig, RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache, RepositoryCache: settings.RepositoryCache,
} }
if strings.HasPrefix(name, "oci://") {
if version == "" {
return "", errors.Errorf("--version flag is explicitly required for OCI registries")
}
dl.Options = append(dl.Options, getter.WithTagName(version))
}
if c.Verify { if c.Verify {
dl.Verify = downloader.VerifyAlways dl.Verify = downloader.VerifyAlways
} }

@ -49,6 +49,28 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
return nil, err return nil, err
} }
// In case any TLS setting is set, recreate the registry client
opts := []registry.ClientOption{}
if g.opts.caFile != "" {
opts = append(opts, registry.ClientOptCAFile(g.opts.caFile))
}
if g.opts.certFile != "" && g.opts.keyFile != "" {
opts = append(opts, registry.ClientOptCertKeyFiles(g.opts.certFile, g.opts.keyFile))
}
if g.opts.insecureSkipVerifyTLS {
opts = append(opts, registry.ClientOptInsecureSkipVerifyTLS(g.opts.insecureSkipVerifyTLS))
}
if len(opts) > 0 {
client, err = registry.NewClient(opts...)
if err != nil {
return nil, err
}
}
buf, err := client.PullChart(r) buf, err := client.PullChart(r)
if err != nil { if err != nil {
return nil, err return nil, err

Loading…
Cancel
Save