diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go index 0cc0564e2..99c114d84 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..72e988d1b 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -117,7 +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 registryClient *registry.Client diff --git a/pkg/action/pull.go b/pkg/action/pull.go index b4018869e..07f30053a 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..8e2374b57 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -12,6 +12,7 @@ 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. +"helm.sh/helm/v3/internal/tlsutil" */ package registry // import "helm.sh/helm/v3/pkg/registry" @@ -22,9 +23,11 @@ import ( "fmt" "io" "io/ioutil" + "net" "net/http" "sort" "strings" + "time" "github.com/Masterminds/semver/v3" "github.com/containerd/containerd/remotes" @@ -38,6 +41,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 +65,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,14 +94,40 @@ 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 @@ -145,6 +178,12 @@ func ClientOptDebug(debug bool) ClientOption { } } +func ClientOptChartRef(chartRef string) ClientOption { + return func(client *Client) { + client.chartRef = chartRef + } +} + // ClientOptEnableCache returns a function that sets the enableCache setting on a client options set func ClientOptEnableCache(enableCache bool) ClientOption { return func(client *Client) { @@ -166,6 +205,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)