Repository: helm/helm
Pull request #13535 (2024-12-15 05:31:44)

refactor: tlsutil use options pattern
<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/main/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:
Switch `tlsutil.NewClientTLS(...)` -> `tlsutil.NewTLSConfig(...)` using function options. Make this easier to extend to futher in the future for e.g. non file config sources

**Special notes for your reviewer**:
This is for https://github.com/helm/helm/pull/13495, where I want to load [testdata](https://github.com/helm/helm/pull/13495/files#diff-2f330f5649bba3c4bdd94185da1d2ad80ec199b6d2cabd0285e13634f80ef5c0L360-L361) from non-filesystem sources

**If applicable**:
- [ ] this PR contains user facing changes (the `docs needed` label should be applied if so)
- [ ] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility



Patch:
diff --git a/ADOPTERS.md b/ADOPTERS.md
index e5ee1e7a2..f12dbb5cf 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -18,6 +18,5 @@
 - [Syself](https://syself.com)
 - [Ville de Montreal](https://montreal.ca)
 - [Intercept](https://Intercept.cloud)
-- [Oracle](www.oracle.com)
 
 _This file is part of the CNCF official documentation for projects._
diff --git a/cmd/helm/root.go b/cmd/helm/root.go
index 2ba8a882e..02d9b5404 100644
--- a/cmd/helm/root.go
+++ b/cmd/helm/root.go
@@ -297,7 +297,12 @@ func newDefaultRegistryClient(plainHTTP bool, username, password string) (*regis
 func newRegistryClientWithTLS(
 	certFile, keyFile, caFile string, insecureSkipTLSverify bool, username, password string,
 ) (*registry.Client, error) {
-	tlsConf, err := tlsutil.NewClientTLS(certFile, keyFile, caFile, insecureSkipTLSverify)
+	tlsConf, err := tlsutil.NewTLSConfig(
+		tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify),
+		tlsutil.WithCertKeyPairFiles(certFile, keyFile),
+		tlsutil.WithCAFile(caFile),
+	)
+
 	if err != nil {
 		return nil, fmt.Errorf("can't create TLS config for client: %w", err)
 	}
diff --git a/internal/tlsutil/cfg.go b/internal/tlsutil/cfg.go
deleted file mode 100644
index 8b9d4329f..000000000
--- a/internal/tlsutil/cfg.go
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-Copyright The Helm Authors.
-
-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 tlsutil
-
-import (
-	"crypto/tls"
-	"crypto/x509"
-	"os"
-
-	"github.com/pkg/errors"
-)
-
-// Options represents configurable options used to create client and server TLS configurations.
-type Options struct {
-	CaCertFile string
-	// If either the KeyFile or CertFile is empty, ClientConfig() will not load them.
-	KeyFile  string
-	CertFile string
-	// Client-only options
-	InsecureSkipVerify bool
-}
-
-// ClientConfig returns a TLS configuration for use by a Helm client.
-func ClientConfig(opts Options) (cfg *tls.Config, err error) {
-	var cert *tls.Certificate
-	var pool *x509.CertPool
-
-	if opts.CertFile != "" || opts.KeyFile != "" {
-		if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil {
-			if os.IsNotExist(err) {
-				return nil, errors.Wrapf(err, "could not load x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
-			}
-			return nil, errors.Wrapf(err, "could not read x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
-		}
-	}
-	if !opts.InsecureSkipVerify && opts.CaCertFile != "" {
-		if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil {
-			return nil, err
-		}
-	}
-
-	cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool}
-	return cfg, nil
-}
diff --git a/internal/tlsutil/tls.go b/internal/tlsutil/tls.go
index 7cd1dace9..645834c29 100644
--- a/internal/tlsutil/tls.go
+++ b/internal/tlsutil/tls.go
@@ -19,60 +19,104 @@ package tlsutil
 import (
 	"crypto/tls"
 	"crypto/x509"
+	"fmt"
 	"os"
 
-	"github.com/pkg/errors"
+	"errors"
 )
 
-// NewClientTLS returns tls.Config appropriate for client auth.
-func NewClientTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*tls.Config, error) {
-	config := tls.Config{
-		InsecureSkipVerify: insecureSkipTLSverify,
+type TLSConfigOptions struct {
+	insecureSkipTLSverify     bool
+	certPEMBlock, keyPEMBlock []byte
+	caPEMBlock                []byte
+}
+
+type TLSConfigOption func(options *TLSConfigOptions) error
+
+func WithInsecureSkipVerify(insecureSkipTLSverify bool) TLSConfigOption {
+	return func(options *TLSConfigOptions) error {
+		options.insecureSkipTLSverify = insecureSkipTLSverify
+
+		return nil
 	}
+}
+
+func WithCertKeyPairFiles(certFile, keyFile string) TLSConfigOption {
+	return func(options *TLSConfigOptions) error {
+		if certFile == "" && keyFile == "" {
+			return nil
+		}
 
-	if certFile != "" && keyFile != "" {
-		cert, err := CertFromFilePair(certFile, keyFile)
+		certPEMBlock, err := os.ReadFile(certFile)
 		if err != nil {
-			return nil, err
+			return fmt.Errorf("unable to read cert file: %q: %w", certFile, err)
 		}
-		config.Certificates = []tls.Certificate{*cert}
-	}
 
-	if caFile != "" {
-		cp, err := CertPoolFromFile(caFile)
+		keyPEMBlock, err := os.ReadFile(keyFile)
 		if err != nil {
-			return nil, err
+			return fmt.Errorf("unable to read key file: %q: %w", keyFile, err)
 		}
-		config.RootCAs = cp
+
+		options.certPEMBlock = certPEMBlock
+		options.keyPEMBlock = keyPEMBlock
+
+		return nil
 	}
+}
 
-	return &config, nil
+func WithCAFile(caFile string) TLSConfigOption {
+	return func(options *TLSConfigOptions) error {
+		if caFile == "" {
+			return nil
+		}
+
+		caPEMBlock, err := os.ReadFile(caFile)
+		if err != nil {
+			return fmt.Errorf("can't read CA file: %q: %w", caFile, err)
+		}
+
+		options.caPEMBlock = caPEMBlock
+
+		return 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 := os.ReadFile(filename)
-	if err != nil {
-		return nil, errors.Errorf("can't read CA file: %v", filename)
+func NewTLSConfig(options ...TLSConfigOption) (*tls.Config, error) {
+	to := TLSConfigOptions{}
+
+	errs := []error{}
+	for _, option := range options {
+		err := option(&to)
+		if err != nil {
+			errs = append(errs, err)
+		}
 	}
-	cp := x509.NewCertPool()
-	if !cp.AppendCertsFromPEM(b) {
-		return nil, errors.Errorf("failed to append certificates from file: %s", filename)
+
+	if len(errs) > 0 {
+		return nil, errors.Join(errs...)
 	}
-	return cp, nil
-}
 
-// CertFromFilePair returns a 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, errors.Wrapf(err, "can't load key pair from cert %s and key %s", certFile, keyFile)
+	config := tls.Config{
+		InsecureSkipVerify: to.insecureSkipTLSverify,
 	}
-	return &cert, err
+
+	if len(to.certPEMBlock) > 0 && len(to.keyPEMBlock) > 0 {
+		cert, err := tls.X509KeyPair(to.certPEMBlock, to.keyPEMBlock)
+		if err != nil {
+			return nil, fmt.Errorf("unable to load cert from key pair: %w", err)
+		}
+
+		config.Certificates = []tls.Certificate{cert}
+	}
+
+	if len(to.caPEMBlock) > 0 {
+		cp := x509.NewCertPool()
+		if !cp.AppendCertsFromPEM(to.caPEMBlock) {
+			return nil, fmt.Errorf("failed to append certificates from pem block")
+		}
+
+		config.RootCAs = cp
+	}
+
+	return &config, nil
 }
diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go
index df3dcd910..6931e2031 100644
--- a/pkg/getter/httpgetter.go
+++ b/pkg/getter/httpgetter.go
@@ -128,7 +128,11 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
 	})
 
 	if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS {
-		tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile, g.opts.insecureSkipVerifyTLS)
+		tlsConf, err := tlsutil.NewTLSConfig(
+			tlsutil.WithInsecureSkipVerify(g.opts.insecureSkipVerifyTLS),
+			tlsutil.WithCertKeyPairFiles(g.opts.certFile, g.opts.keyFile),
+			tlsutil.WithCAFile(g.opts.caFile),
+		)
 		if err != nil {
 			return nil, errors.Wrap(err, "can't create TLS config for client")
 		}
diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go
index 0547cdcbb..c6e80dc65 100644
--- a/pkg/getter/ocigetter.go
+++ b/pkg/getter/ocigetter.go
@@ -124,7 +124,11 @@ func (g *OCIGetter) newRegistryClient() (*registry.Client, error) {
 	})
 
 	if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS {
-		tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile, g.opts.insecureSkipVerifyTLS)
+		tlsConf, err := tlsutil.NewTLSConfig(
+			tlsutil.WithInsecureSkipVerify(g.opts.insecureSkipVerifyTLS),
+			tlsutil.WithCertKeyPairFiles(g.opts.certFile, g.opts.keyFile),
+			tlsutil.WithCAFile(g.opts.caFile),
+		)
 		if err != nil {
 			return nil, fmt.Errorf("can't create TLS config for client: %w", err)
 		}
diff --git a/pkg/pusher/ocipusher.go b/pkg/pusher/ocipusher.go
index 33296aadd..dad498432 100644
--- a/pkg/pusher/ocipusher.go
+++ b/pkg/pusher/ocipusher.go
@@ -111,7 +111,11 @@ func NewOCIPusher(ops ...Option) (Pusher, error) {
 
 func (pusher *OCIPusher) newRegistryClient() (*registry.Client, error) {
 	if (pusher.opts.certFile != "" && pusher.opts.keyFile != "") || pusher.opts.caFile != "" || pusher.opts.insecureSkipTLSverify {
-		tlsConf, err := tlsutil.NewClientTLS(pusher.opts.certFile, pusher.opts.keyFile, pusher.opts.caFile, pusher.opts.insecureSkipTLSverify)
+		tlsConf, err := tlsutil.NewTLSConfig(
+			tlsutil.WithInsecureSkipVerify(pusher.opts.insecureSkipTLSverify),
+			tlsutil.WithCertKeyPairFiles(pusher.opts.certFile, pusher.opts.keyFile),
+			tlsutil.WithCAFile(pusher.opts.caFile),
+		)
 		if err != nil {
 			return nil, errors.Wrap(err, "can't create TLS config for client")
 		}
diff --git a/pkg/registry/util.go b/pkg/registry/util.go
index 727cdae03..c1a0b4194 100644
--- a/pkg/registry/util.go
+++ b/pkg/registry/util.go
@@ -142,7 +142,11 @@ func parseReference(raw string) (registry.Reference, error) {
 
 // NewRegistryClientWithTLS is a helper function to create a new registry client with TLS enabled.
 func NewRegistryClientWithTLS(out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSverify bool, registryConfig string, debug bool) (*Client, error) {
-	tlsConf, err := tlsutil.NewClientTLS(certFile, keyFile, caFile, insecureSkipTLSverify)
+	tlsConf, err := tlsutil.NewTLSConfig(
+		tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify),
+		tlsutil.WithCertKeyPairFiles(certFile, keyFile),
+		tlsutil.WithCAFile(caFile),
+	)
 	if err != nil {
 		return nil, fmt.Errorf("can't create TLS config for client: %s", err)
 	}


Test patch:
diff --git a/internal/tlsutil/tls_test.go b/internal/tlsutil/tls_test.go
new file mode 100644
index 000000000..eb1cc183e
--- /dev/null
+++ b/internal/tlsutil/tls_test.go
@@ -0,0 +1,105 @@
+/*
+Copyright The Helm Authors.
+
+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 tlsutil
+
+import (
+	"path/filepath"
+	"testing"
+)
+
+const tlsTestDir = "../../testdata"
+
+const (
+	testCaCertFile = "rootca.crt"
+	testCertFile   = "crt.pem"
+	testKeyFile    = "key.pem"
+)
+
+func testfile(t *testing.T, file string) (path string) {
+	var err error
+	if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
+		t.Fatalf("error getting absolute path to test file %q: %v", file, err)
+	}
+	return path
+}
+
+func TestNewTLSConfig(t *testing.T) {
+	certFile := testfile(t, testCertFile)
+	keyFile := testfile(t, testKeyFile)
+	caCertFile := testfile(t, testCaCertFile)
+	insecureSkipTLSverify := false
+
+	{
+		cfg, err := NewTLSConfig(
+			WithInsecureSkipVerify(insecureSkipTLSverify),
+			WithCertKeyPairFiles(certFile, keyFile),
+			WithCAFile(caCertFile),
+		)
+		if err != nil {
+			t.Error(err)
+		}
+
+		if got := len(cfg.Certificates); got != 1 {
+			t.Fatalf("expecting 1 client certificates, got %d", got)
+		}
+		if cfg.InsecureSkipVerify {
+			t.Fatalf("insecure skip verify mismatch, expecting false")
+		}
+		if cfg.RootCAs == nil {
+			t.Fatalf("mismatch tls RootCAs, expecting non-nil")
+		}
+	}
+	{
+		cfg, err := NewTLSConfig(
+			WithInsecureSkipVerify(insecureSkipTLSverify),
+			WithCAFile(caCertFile),
+		)
+		if err != nil {
+			t.Error(err)
+		}
+
+		if got := len(cfg.Certificates); got != 0 {
+			t.Fatalf("expecting 0 client certificates, got %d", got)
+		}
+		if cfg.InsecureSkipVerify {
+			t.Fatalf("insecure skip verify mismatch, expecting false")
+		}
+		if cfg.RootCAs == nil {
+			t.Fatalf("mismatch tls RootCAs, expecting non-nil")
+		}
+	}
+
+	{
+		cfg, err := NewTLSConfig(
+			WithInsecureSkipVerify(insecureSkipTLSverify),
+			WithCertKeyPairFiles(certFile, keyFile),
+		)
+		if err != nil {
+			t.Error(err)
+		}
+
+		if got := len(cfg.Certificates); got != 1 {
+			t.Fatalf("expecting 1 client certificates, got %d", got)
+		}
+		if cfg.InsecureSkipVerify {
+			t.Fatalf("insecure skip verify mismatch, expecting false")
+		}
+		if cfg.RootCAs != nil {
+			t.Fatalf("mismatch tls RootCAs, expecting nil")
+		}
+	}
+}
diff --git a/internal/tlsutil/tlsutil_test.go b/internal/tlsutil/tlsutil_test.go
deleted file mode 100644
index e31a873d3..000000000
--- a/internal/tlsutil/tlsutil_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
-Copyright The Helm Authors.
-
-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 tlsutil
-
-import (
-	"path/filepath"
-	"testing"
-)
-
-const tlsTestDir = "../../testdata"
-
-const (
-	testCaCertFile = "rootca.crt"
-	testCertFile   = "crt.pem"
-	testKeyFile    = "key.pem"
-)
-
-func TestClientConfig(t *testing.T) {
-	opts := Options{
-		CaCertFile:         testfile(t, testCaCertFile),
-		CertFile:           testfile(t, testCertFile),
-		KeyFile:            testfile(t, testKeyFile),
-		InsecureSkipVerify: false,
-	}
-
-	cfg, err := ClientConfig(opts)
-	if err != nil {
-		t.Fatalf("error building tls client config: %v", err)
-	}
-
-	if got := len(cfg.Certificates); got != 1 {
-		t.Fatalf("expecting 1 client certificates, got %d", got)
-	}
-	if cfg.InsecureSkipVerify {
-		t.Fatalf("insecure skip verify mismatch, expecting false")
-	}
-	if cfg.RootCAs == nil {
-		t.Fatalf("mismatch tls RootCAs, expecting non-nil")
-	}
-}
-
-func testfile(t *testing.T, file string) (path string) {
-	var err error
-	if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
-		t.Fatalf("error getting absolute path to test file %q: %v", file, err)
-	}
-	return path
-}
-
-func TestNewClientTLS(t *testing.T) {
-	certFile := testfile(t, testCertFile)
-	keyFile := testfile(t, testKeyFile)
-	caCertFile := testfile(t, testCaCertFile)
-	insecureSkipTLSverify := false
-
-	cfg, err := NewClientTLS(certFile, keyFile, caCertFile, insecureSkipTLSverify)
-	if err != nil {
-		t.Error(err)
-	}
-
-	if got := len(cfg.Certificates); got != 1 {
-		t.Fatalf("expecting 1 client certificates, got %d", got)
-	}
-	if cfg.InsecureSkipVerify {
-		t.Fatalf("insecure skip verify mismatch, expecting false")
-	}
-	if cfg.RootCAs == nil {
-		t.Fatalf("mismatch tls RootCAs, expecting non-nil")
-	}
-
-	cfg, err = NewClientTLS("", "", caCertFile, insecureSkipTLSverify)
-	if err != nil {
-		t.Error(err)
-	}
-
-	if got := len(cfg.Certificates); got != 0 {
-		t.Fatalf("expecting 0 client certificates, got %d", got)
-	}
-	if cfg.InsecureSkipVerify {
-		t.Fatalf("insecure skip verify mismatch, expecting false")
-	}
-	if cfg.RootCAs == nil {
-		t.Fatalf("mismatch tls RootCAs, expecting non-nil")
-	}
-
-	cfg, err = NewClientTLS(certFile, keyFile, "", insecureSkipTLSverify)
-	if err != nil {
-		t.Error(err)
-	}
-
-	if got := len(cfg.Certificates); got != 1 {
-		t.Fatalf("expecting 1 client certificates, got %d", got)
-	}
-	if cfg.InsecureSkipVerify {
-		t.Fatalf("insecure skip verify mismatch, expecting false")
-	}
-	if cfg.RootCAs != nil {
-		t.Fatalf("mismatch tls RootCAs, expecting nil")
-	}
-}
diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go
index 2c38c6154..c38966bff 100644
--- a/pkg/getter/httpgetter_test.go
+++ b/pkg/getter/httpgetter_test.go
@@ -311,7 +311,11 @@ func TestDownloadTLS(t *testing.T) {
 	insecureSkipTLSverify := false
 
 	tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))
-	tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca, insecureSkipTLSverify)
+	tlsConf, err := tlsutil.NewTLSConfig(
+		tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify),
+		tlsutil.WithCertKeyPairFiles(pub, priv),
+		tlsutil.WithCAFile(ca),
+	)
 	if err != nil {
 		t.Fatal(errors.Wrap(err, "can't create TLS config for client"))
 	}
diff --git a/pkg/registry/utils_test.go b/pkg/registry/utils_test.go
index ee78ea76f..e10e5b500 100644
--- a/pkg/registry/utils_test.go
+++ b/pkg/registry/utils_test.go
@@ -95,9 +95,14 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry {
 	if tlsEnabled {
 		var tlsConf *tls.Config
 		if insecure {
-			tlsConf, err = tlsutil.NewClientTLS("", "", "", true)
+			tlsConf, err = tlsutil.NewTLSConfig(
+				tlsutil.WithInsecureSkipVerify(true),
+			)
 		} else {
-			tlsConf, err = tlsutil.NewClientTLS(tlsCert, tlsKey, tlsCA, false)
+			tlsConf, err = tlsutil.NewTLSConfig(
+				tlsutil.WithCertKeyPairFiles(tlsCert, tlsKey),
+				tlsutil.WithCAFile(tlsCA),
+			)
 		}
 		httpClient := &http.Client{
 			Transport: &http.Transport{
diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go
index 4a86707cf..cc7494d54 100644
--- a/pkg/repo/repotest/server.go
+++ b/pkg/repo/repotest/server.go
@@ -367,7 +367,11 @@ func (s *Server) StartTLS() {
 		}
 		http.FileServer(http.Dir(s.Root())).ServeHTTP(w, r)
 	}))
-	tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca, insecure)
+	tlsConf, err := tlsutil.NewTLSConfig(
+		tlsutil.WithInsecureSkipVerify(insecure),
+		tlsutil.WithCertKeyPairFiles(pub, priv),
+		tlsutil.WithCAFile(ca),
+	)
 	if err != nil {
 		panic(err)
 	}