From 57567a1c2249d7827f32669c96d5879d92033eea Mon Sep 17 00:00:00 2001 From: Vui Lam Date: Wed, 13 Nov 2024 15:34:41 -0800 Subject: [PATCH] Update kube context with certmap info if necessary Since the cert data and skip verify intent is captured in the certmap for the endpoint that one `tanzu login`s to, subsequent tanzu login to the same endpoint can succeed without providing these values. However the kube context created with the subsequent login attempt does not incorporate these values when they originate from the cert map. This change addresses this inconsistency, by ensuring that these values are incorporated unless command line arguments of alternative ones are provided in the login command. Signed-off-by: Vui Lam --- pkg/auth/tanzu/kubeconfig.go | 34 ++++++++- pkg/auth/tanzu/kubeconfig_test.go | 123 ++++++++++++++++++++++++++++-- 2 files changed, 147 insertions(+), 10 deletions(-) diff --git a/pkg/auth/tanzu/kubeconfig.go b/pkg/auth/tanzu/kubeconfig.go index f598d182e..e575cd0f3 100644 --- a/pkg/auth/tanzu/kubeconfig.go +++ b/pkg/auth/tanzu/kubeconfig.go @@ -5,9 +5,11 @@ package tanzu import ( + "encoding/base64" "encoding/json" "os" "path/filepath" + "strconv" "strings" "github.com/pkg/errors" @@ -17,6 +19,7 @@ import ( kubeutils "github.com/vmware-tanzu/tanzu-cli/pkg/auth/utils/kubeconfig" "github.com/vmware-tanzu/tanzu-plugin-runtime/config" configtypes "github.com/vmware-tanzu/tanzu-plugin-runtime/config/types" + "github.com/vmware-tanzu/tanzu-plugin-runtime/log" ) const ( @@ -27,9 +30,12 @@ const ( tanzuKubeconfigFile = "config" ) -// GetTanzuKubeconfig constructs and returns the kubeconfig that points to Tanzu Org and +// GetTanzuKubeconfig constructs and returns the kubeconfig that points to +// Tanzu Org. The constructed kubeconfig will incorporate any explicitly certdata or +// skip verify flag or rely on the same information captured in the certmap +// otherwise. func GetTanzuKubeconfig(c *configtypes.Context, endpoint, orgID, endpointCACertPath string, skipTLSVerify bool) (string, string, string, error) { - var clusterCACertDataBytes []byte + var endpointCACertBytes []byte var err error clusterAPIServerURL := strings.TrimSpace(endpoint) @@ -39,10 +45,30 @@ func GetTanzuKubeconfig(c *configtypes.Context, endpoint, orgID, endpointCACertP clusterAPIServerURL = clusterAPIServerURL + "/org/" + orgID if endpointCACertPath != "" { - clusterCACertDataBytes, err = os.ReadFile(endpointCACertPath) + endpointCACertBytes, err = os.ReadFile(endpointCACertPath) if err != nil { return "", "", "", errors.Wrapf(err, "error reading CA certificate file %s", endpointCACertPath) } + } else if !skipTLSVerify { + // When there is no explicit provision to use custom cert or skip TLS + // verification, fall back to information captured in the cert map, if + // any + + if val, ok := c.AdditionalMetadata[config.TanzuAuthEndpointKey]; ok { + endpoint := val.(string) + certInfo, _ := config.GetCert(endpoint) + if certInfo != nil { + val, err := strconv.ParseBool(certInfo.SkipCertVerify) + if err == nil { + skipTLSVerify = val + } + decodedCACertData, err := base64.StdEncoding.DecodeString(certInfo.CACertData) + if err == nil { + endpointCACertBytes = decodedCACertData + log.V(7).Infof("Using cert data for endpoint: %s", endpoint) + } + } + } } contextName := kubeconfigContextName(c.Name) @@ -53,7 +79,7 @@ func GetTanzuKubeconfig(c *configtypes.Context, endpoint, orgID, endpointCACertP Kind: "Config", APIVersion: clientcmdapi.SchemeGroupVersion.Version, Clusters: map[string]*clientcmdapi.Cluster{clusterName: { - CertificateAuthorityData: clusterCACertDataBytes, + CertificateAuthorityData: endpointCACertBytes, InsecureSkipTLSVerify: skipTLSVerify, Server: clusterAPIServerURL, }}, diff --git a/pkg/auth/tanzu/kubeconfig_test.go b/pkg/auth/tanzu/kubeconfig_test.go index 758d80bf4..0d92c56a8 100644 --- a/pkg/auth/tanzu/kubeconfig_test.go +++ b/pkg/auth/tanzu/kubeconfig_test.go @@ -4,6 +4,7 @@ package tanzu import ( + "encoding/base64" "fmt" "os" "path/filepath" @@ -11,6 +12,7 @@ import ( "k8s.io/client-go/tools/clientcmd" + configlib "github.com/vmware-tanzu/tanzu-plugin-runtime/config" configtypes "github.com/vmware-tanzu/tanzu-plugin-runtime/config/types" . "github.com/onsi/ginkgo/v2" @@ -31,21 +33,22 @@ const ( var _ = Describe("Unit tests for tanzu auth", func() { var ( err error - endpoint string tanzuContext *configtypes.Context oldHomeDir string tmpHomeDir string ) const ( - fakeContextName = "fake-tanzu-context" - fakeAccessToken = "fake-access-token" - fakeOrgID = "fake-org-id" - fakeEndpoint = "fake.tanzu.cloud.vmware.com" + fakeContextName = "fake-tanzu-context" + fakeAccessToken = "fake-access-token" + fakeOrgID = "fake-org-id" + fakeEndpoint = "fake.tanzu.cloud.vmware.com" + fakeCACertContent = "-----BEGIN CERTIFICATE-----\nfake\n---" ) Describe("GetTanzuKubeconfig()", func() { var kubeConfigPath, kubeContext, clusterAPIServerURL string + BeforeEach(func() { err = createTempDirectory("kubeconfig-test") Expect(err).ToNot(HaveOccurred()) @@ -109,7 +112,7 @@ var _ = Describe("Unit tests for tanzu auth", func() { }) Context("When endpointCACertPath is not provided and skipTLSVerify is set to true", func() { BeforeEach(func() { - kubeConfigPath, kubeContext, clusterAPIServerURL, err = GetTanzuKubeconfig(tanzuContext, endpoint, fakeOrgID, "", true) + kubeConfigPath, kubeContext, clusterAPIServerURL, err = GetTanzuKubeconfig(tanzuContext, fakeEndpoint, fakeOrgID, "", true) }) It("should not set the 'certificate-authority-data' in kubeconfig and 'insecure-skip-tls-verify' should be set", func() { Expect(err).ToNot(HaveOccurred()) @@ -130,7 +133,115 @@ var _ = Describe("Unit tests for tanzu auth", func() { Expect(user.Exec).To(Equal(getExecConfig(tanzuContext))) }) }) + Context("When endpointCACertPath is not provided and skipTLSVerify is set to false, but ca cert found in cert map", func() { + BeforeEach(func() { + certInfo := configtypes.Cert{ + Host: fakeEndpoint, + CACertData: base64.StdEncoding.EncodeToString([]byte(fakeCACertContent)), + SkipCertVerify: "false", + } + + err = configlib.SetCert(&certInfo) + Expect(err).ToNot(HaveOccurred()) + + tanzuContext.AdditionalMetadata = map[string]interface{}{ + configlib.TanzuAuthEndpointKey: "https://" + fakeEndpoint + "/auth", + } + kubeConfigPath, kubeContext, clusterAPIServerURL, err = GetTanzuKubeconfig(tanzuContext, fakeEndpoint, fakeOrgID, "", false) + }) + It("should set the 'certificate-authority-data' in kubeconfig base on the cert map contents", func() { + Expect(err).ToNot(HaveOccurred()) + Expect(kubeConfigPath).Should(Equal(filepath.Join(tmpHomeDir, ".config", "tanzu", "kube", "config"))) + Expect(kubeContext).Should(Equal("tanzu-cli-" + tanzuContext.Name)) + config, err := clientcmd.LoadFromFile(kubeConfigPath) + Expect(err).ToNot(HaveOccurred()) + + gotClusterName := config.Contexts[kubeContext].Cluster + cluster := config.Clusters[config.Contexts[kubeContext].Cluster] + user := config.AuthInfos[config.Contexts[kubeContext].AuthInfo] + + Expect(cluster.Server).To(Equal(clusterAPIServerURL)) + Expect(config.Contexts[kubeContext].AuthInfo).To(Equal("tanzu-cli-" + tanzuContext.Name + "-user")) + Expect(gotClusterName).To(Equal("tanzu-cli-" + tanzuContext.Name)) + Expect([]byte(fakeCACertContent)).To(Equal(cluster.CertificateAuthorityData)) + Expect(cluster.InsecureSkipTLSVerify).To(Equal(false)) + Expect(user.Exec).To(Equal(getExecConfig(tanzuContext))) + }) + }) + Context("When endpointCACertPath is not provided and skipTLSVerify is set to false, but skipVerify is true in cert map", func() { + BeforeEach(func() { + certInfo := configtypes.Cert{ + Host: fakeEndpoint, + SkipCertVerify: "true", + } + + err = configlib.SetCert(&certInfo) + Expect(err).ToNot(HaveOccurred()) + + tanzuContext.AdditionalMetadata = map[string]interface{}{ + configlib.TanzuAuthEndpointKey: "https://" + fakeEndpoint + "/auth", + } + + kubeConfigPath, kubeContext, clusterAPIServerURL, err = GetTanzuKubeconfig(tanzuContext, fakeEndpoint, fakeOrgID, "", false) + }) + It("should set the 'certificate-authority-data' in kubeconfig base on the cert map contents", func() { + Expect(err).ToNot(HaveOccurred()) + Expect(kubeConfigPath).Should(Equal(filepath.Join(tmpHomeDir, ".config", "tanzu", "kube", "config"))) + Expect(kubeContext).Should(Equal("tanzu-cli-" + tanzuContext.Name)) + config, err := clientcmd.LoadFromFile(kubeConfigPath) + Expect(err).ToNot(HaveOccurred()) + + gotClusterName := config.Contexts[kubeContext].Cluster + cluster := config.Clusters[config.Contexts[kubeContext].Cluster] + user := config.AuthInfos[config.Contexts[kubeContext].AuthInfo] + + Expect(cluster.Server).To(Equal(clusterAPIServerURL)) + Expect(config.Contexts[kubeContext].AuthInfo).To(Equal("tanzu-cli-" + tanzuContext.Name + "-user")) + Expect(gotClusterName).To(Equal("tanzu-cli-" + tanzuContext.Name)) + Expect(len(cluster.CertificateAuthorityData)).To(Equal(0)) + Expect(cluster.InsecureSkipTLSVerify).To(Equal(true)) + Expect(user.Exec).To(Equal(getExecConfig(tanzuContext))) + }) + }) + Context("When the endpoint caCertPath provided exists and skipTLSVerify is set to false and there is valid certmap data", func() { + BeforeEach(func() { + certInfo := configtypes.Cert{ + Host: fakeEndpoint, + CACertData: base64.StdEncoding.EncodeToString([]byte(fakeCACertContent)), + SkipCertVerify: "false", + } + + err = configlib.SetCert(&certInfo) + Expect(err).ToNot(HaveOccurred()) + + tanzuContext.AdditionalMetadata = map[string]interface{}{ + configlib.TanzuAuthEndpointKey: "https://" + fakeEndpoint + "/auth", + } + + kubeConfigPath, kubeContext, clusterAPIServerURL, err = GetTanzuKubeconfig(tanzuContext, fakeEndpoint, fakeOrgID, fakeCAcertPath, false) + }) + It("should set the 'certificate-authority-data' in kubeconfig based on contents of provided ca cert path", func() { + Expect(err).ToNot(HaveOccurred()) + Expect(kubeConfigPath).Should(Equal(filepath.Join(tmpHomeDir, ".config", "tanzu", "kube", "config"))) + Expect(kubeContext).Should(Equal(kubeconfigContextName(tanzuContext.Name))) + config, err := clientcmd.LoadFromFile(kubeConfigPath) + Expect(err).ToNot(HaveOccurred()) + + gotClusterName := config.Contexts[kubeContext].Cluster + cluster := config.Clusters[config.Contexts[kubeContext].Cluster] + user := config.AuthInfos[config.Contexts[kubeContext].AuthInfo] + + Expect(cluster.Server).To(Equal(clusterAPIServerURL)) + Expect(config.Contexts[kubeContext].AuthInfo).To(Equal(kubeconfigUserName(tanzuContext.Name))) + Expect(gotClusterName).To(Equal(kubeconfigClusterName(tanzuContext.Name))) + Expect(user.Exec).To(Equal(getExecConfig(tanzuContext))) + + caCertBytes, err := os.ReadFile(fakeCAcertPath) + Expect(err).ToNot(HaveOccurred()) + Expect(caCertBytes).To(Equal(cluster.CertificateAuthorityData)) + }) + }) }) })