From f5842d0e51d845f9ef188440479266c17d05e6bb Mon Sep 17 00:00:00 2001 From: Anuj Chaudhari Date: Fri, 30 Aug 2024 09:27:21 -0700 Subject: [PATCH] Support Self-Signed CA Cert and skip-cert-verify with Hub Client & Update to GetCert API (#210) * Support self-signed CA Cert and skip-cert-verify with Hub Client * GetCert API now accepts URI along with hostname --- client/hub/client.go | 40 ++++++++++++++++++++++++++++++++++++++-- config/certs.go | 13 +++++++++++-- config/certs_test.go | 40 ++++++++++++++++++++++++++++++++-------- docs/config.md | 7 +++++++ 4 files changed, 88 insertions(+), 12 deletions(-) diff --git a/client/hub/client.go b/client/hub/client.go index a7b49ea2e..3ce9f00a3 100644 --- a/client/hub/client.go +++ b/client/hub/client.go @@ -6,8 +6,11 @@ package hub import ( "context" + "crypto/tls" + "crypto/x509" "net/http" "os" + "strconv" "github.com/pkg/errors" @@ -112,12 +115,15 @@ func (c *hubClient) initializeClient(contextName string) error { } } + transport := http.DefaultTransport.(*http.Transport) + transport.TLSClientConfig = c.getTLSConfig() + // Set httpClient if it is not already set if c.httpClient == nil { c.httpClient = &http.Client{ Transport: &authTransport{ accessToken: c.accessToken, - wrapped: http.DefaultTransport, + wrapped: transport, }, Timeout: 0, } @@ -145,10 +151,40 @@ func getTanzuHubEndpointFromContext(contextName string) (string, error) { return tanzuHubEndpoint.(string), nil } +func (c *hubClient) getTLSConfig() *tls.Config { + // If the certificate information is found for the hub endpoint + // then configure TLSConfig and return it + certData, err := config.GetCert(c.tanzuHubEndpoint) + if err != nil || certData == nil { + return nil + } + + // If CACertData is present use it + if certData.CACertData != "" { + var pool *x509.CertPool + var err error + pool, err = x509.SystemCertPool() + if err != nil || pool == nil { + pool = x509.NewCertPool() + } + pool.AppendCertsFromPEM([]byte(certData.CACertData)) + return &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12} + } + + skipCertVerify, _ := strconv.ParseBool(certData.SkipCertVerify) + if skipCertVerify { + //nolint:gosec + // skipTLSVerify: true is only possible if the user has explicitly enabled it + return &tls.Config{InsecureSkipVerify: skipCertVerify, MinVersion: tls.VersionTLS12} + } + + return nil +} + // Configure the auth Transport to include authorization token when invoking GraphQL requests type authTransport struct { accessToken string - wrapped http.RoundTripper + wrapped *http.Transport } // Sets authentication bearer token to each http request diff --git a/config/certs.go b/config/certs.go index 0c05da899..9e22b1199 100644 --- a/config/certs.go +++ b/config/certs.go @@ -5,6 +5,7 @@ package config import ( "fmt" + "net/url" "github.com/pkg/errors" "gopkg.in/yaml.v3" @@ -25,11 +26,19 @@ func GetCerts() ([]*configtypes.Cert, error) { return getCerts(node) } -// GetCert retrieves the cert configuration by host +// GetCert retrieves the cert configuration by host or URI func GetCert(host string) (*configtypes.Cert, error) { if host == "" { return nil, errors.New("host is empty") } + u, err := url.Parse(host) + if err != nil { + return nil, err + } + if u.Hostname() != "" { + host = u.Hostname() + } + // Retrieve client config node node, err := getClientConfigNode() if err != nil { @@ -90,7 +99,7 @@ func DeleteCert(host string) error { return persistConfig(node) } -// CertExists checks if cert config by host already exists +// CertExists checks if cert config by host or URI already exists func CertExists(host string) (bool, error) { if host == "" { return false, errors.New("host is empty") diff --git a/config/certs_test.go b/config/certs_test.go index a224fe548..3d3f1198b 100644 --- a/config/certs_test.go +++ b/config/certs_test.go @@ -32,23 +32,47 @@ func TestSetGetDeleteCerts(t *testing.T) { Insecure: "false", } - ctx, err := GetCert("test1") + cert, err := GetCert("test1") assert.Equal(t, "cert configuration for test1 not found", err.Error()) - assert.Nil(t, ctx) + assert.Nil(t, cert) err = SetCert(cert1) assert.NoError(t, err) - ctx, err = GetCert("test1") + cert, err = GetCert("test1") assert.Nil(t, err) - assert.Equal(t, cert1, ctx) + assert.Equal(t, cert1, cert) + + cert, err = GetCert("https://test1") + assert.Nil(t, err) + assert.Equal(t, cert1, cert) + + cert, err = GetCert("https://test1/fake") + assert.Nil(t, err) + assert.Equal(t, cert1, cert) + + cert, err = GetCert("https://test1:1234/fake") + assert.Nil(t, err) + assert.Equal(t, cert1, cert) + + cert, err = GetCert("ws://test1/fake") + assert.Nil(t, err) + assert.Equal(t, cert1, cert) + + cert, err = GetCert("wss://test1/fake") + assert.Nil(t, err) + assert.Equal(t, cert1, cert) err = SetCert(cert2) assert.NoError(t, err) - ctx, err = GetCert("test2") + cert, err = GetCert("test2") assert.Nil(t, err) - assert.Equal(t, cert2, ctx) + assert.Equal(t, cert2, cert) + + cert, err = GetCert("https://tes t1/fak e") + assert.Nil(t, cert) + assert.Equal(t, "parse \"https://tes t1/fak e\": invalid character \" \" in host name", err.Error()) _, err = GetCert("") assert.Equal(t, "host is empty", err.Error()) @@ -62,8 +86,8 @@ func TestSetGetDeleteCerts(t *testing.T) { err = DeleteCert("test1") assert.Nil(t, err) - ctx, err = GetCert("test1") - assert.Nil(t, ctx) + cert, err = GetCert("test1") + assert.Nil(t, cert) assert.Equal(t, "cert configuration for test1 not found", err.Error()) } diff --git a/docs/config.md b/docs/config.md index 826c15d2b..0dba60d3e 100644 --- a/docs/config.md +++ b/docs/config.md @@ -89,6 +89,13 @@ func GetEnvConfigurations() map[string]string func GetEdition() (string, error) func SetEdition(val string) (err error) +// Cert APIs +func GetCert(host string) (*configtypes.Cert, error) +func GetCerts() ([]*configtypes.Cert, error) +func SetCert(c *configtypes.Cert) error +func DeleteCert(host string) error +func CertExists(host string) (bool, error) + // Telemetry APIs func GetCEIPOptIn() (string, error) func SetCEIPOptIn(val string) (err error)