diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go index 0cc0564e2..25701b38f 100644 --- a/cmd/helm/flags.go +++ b/cmd/helm/flags.go @@ -61,6 +61,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { 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.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains") + f.BoolVar(&c.TLSEnabled, "mtls-enabled", false, "if two-way tls authentication enabled then trying to send client certificate") } // bindOutputFlag will add the output flag to the given command and bind the diff --git a/pkg/action/install.go b/pkg/action/install.go index cd202ccab..9e714f92c 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -117,6 +117,7 @@ type ChartPathOptions struct { Username string // --username Verify bool // --verify Version string // --version + TLSEnabled bool // --mtls-enabled // registryClient provides a registry client but is not added with // options from a flag diff --git a/pkg/action/pull.go b/pkg/action/pull.go index b4018869e..1db863bf9 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -25,6 +25,7 @@ import ( "github.com/pkg/errors" + "helm.sh/helm/v3/internal/tlsutil" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/downloader" @@ -86,6 +87,7 @@ func (p *Pull) Run(chartRef string) (string, error) { getter.WithPassCredentialsAll(p.PassCredentialsAll), getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile), getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify), + getter.WithTwoWayTLSEnable(p.TLSEnabled), }, RegistryClient: p.cfg.RegistryClient, RepositoryConfig: p.Settings.RepositoryConfig, @@ -93,8 +95,24 @@ func (p *Pull) Run(chartRef string) (string, error) { } if registry.IsOCI(chartRef) { - c.Options = append(c.Options, - getter.WithRegistryClient(p.cfg.RegistryClient)) + if !p.TLSEnabled { + c.Options = append(c.Options, + getter.WithRegistryClient(p.cfg.RegistryClient), + ) + } else { + registryClient, err := registry.NewClient( + registry.ClientOptDebug(p.Settings.Debug), + registry.ClientOptCredentialsFile(p.Settings.RegistryConfig), + registry.ClientOptWriter(&out), + registry.ClientOptTwoWayTLSEnable(p.TLSEnabled), + registry.ClientOptChartRef(chartRef), + registry.ClientOptWithTLSOpts(tlsutil.Options{CaCertFile: p.CaFile, KeyFile: p.KeyFile, CertFile: p.CertFile, InsecureSkipVerify: p.InsecureSkipTLSverify}), + ) + if err != nil { + return out.String(), err + } + c.Options = append(c.Options, getter.WithRegistryClient(registryClient)) + } } if p.Verify { diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index 653b032fe..b8b1252cb 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -42,6 +42,7 @@ type options struct { passCredentialsAll bool userAgent string version string + tlsEnabled bool registryClient *registry.Client timeout time.Duration transport *http.Transport @@ -87,6 +88,12 @@ func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) Option { } } +func WithTwoWayTLSEnable(tlsEnabled bool) Option { + return func(opts *options) { + opts.tlsEnabled = tlsEnabled + } +} + // WithTLSClientConfig sets the client auth with the provided credentials. func WithTLSClientConfig(certFile, keyFile, caFile string) Option { return func(opts *options) { diff --git a/pkg/registry/client.go b/pkg/registry/client.go index c1004f956..6cde4568f 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -22,9 +22,11 @@ import ( "fmt" "io" "io/ioutil" + "net" "net/http" "sort" "strings" + "time" "github.com/Masterminds/semver/v3" "github.com/containerd/containerd/remotes" @@ -38,6 +40,7 @@ import ( registryremote "oras.land/oras-go/pkg/registry/remote" registryauth "oras.land/oras-go/pkg/registry/remote/auth" + "helm.sh/helm/v3/internal/tlsutil" "helm.sh/helm/v3/internal/version" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/helmpath" @@ -61,6 +64,9 @@ type ( authorizer auth.Client registryAuthorizer *registryauth.Client resolver remotes.Resolver + tlsEnabled bool + chartRef string + utilOpts tlsutil.Options } // ClientOption allows specifying various settings configurable by the user for overriding the defaults @@ -87,16 +93,41 @@ func NewClient(options ...ClientOption) (*Client, error) { client.authorizer = authClient } if client.resolver == nil { - headers := http.Header{} - headers.Set("User-Agent", version.GetUserAgent()) - opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)} - resolver, err := client.authorizer.ResolverWithOpts(opts...) - if err != nil { - return nil, err + if client.tlsEnabled { + cfgtls, err := tlsutil.ClientConfig(client.utilOpts) + if err != nil { + fmt.Printf("error :%v\n", err) + } + var rt http.RoundTripper = &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 30 * time.Second, + TLSClientConfig: cfgtls, + ResponseHeaderTimeout: time.Duration(30 * time.Second), + DisableKeepAlives: true, + } + sClient := http.Client{Transport: rt, Timeout: 30 * time.Second} + headers := http.Header{} + headers.Set("User-Agent", version.GetUserAgent()) + opts := []auth.ResolverOption{auth.WithResolverHeaders(headers), auth.WithResolverClient(&sClient)} + resolver, err := client.authorizer.ResolverWithOpts(opts...) + if err != nil { + return nil, err + } + client.resolver = resolver + } else { + headers := http.Header{} + headers.Set("User-Agent", version.GetUserAgent()) + opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)} + resolver, err := client.authorizer.ResolverWithOpts(opts...) + if err != nil { + return nil, err + } + client.resolver = resolver } - client.resolver = resolver } - // allocate a cache if option is set var cache registryauth.Cache if client.enableCache { @@ -159,6 +190,12 @@ func ClientOptWriter(out io.Writer) ClientOption { } } +func ClientOptChartRef(chartRef string) ClientOption { + return func(client *Client) { + client.chartRef = chartRef + } +} + // ClientOptCredentialsFile returns a function that sets the credentialsFile setting on a client options set func ClientOptCredentialsFile(credentialsFile string) ClientOption { return func(client *Client) { @@ -166,6 +203,20 @@ func ClientOptCredentialsFile(credentialsFile string) ClientOption { } } +//ClientOptTwoWayTLSEnable returns a function that sets the client certificate when two-way tls authentication enable +func ClientOptTwoWayTLSEnable(tlsEnabled bool) ClientOption { + return func(client *Client) { + client.tlsEnabled = tlsEnabled + } +} + +//ClientOptTwoWayTLSEnable returns a function that sets the client certificate when two-way tls authentication enable +func ClientOptWithTLSOpts(tlsOpts tlsutil.Options) ClientOption { + return func(client *Client) { + client.utilOpts = tlsOpts + } +} + type ( // LoginOption allows specifying various settings on login LoginOption func(*loginOperation)