Repository: argoproj/argo-cd (Go)

Issue:
x509: certificate signed by unknown authority error when adding cluster with kube-public option using cli
Checklist:

* [x] I've searched in the docs and FAQ for my answer: https://bit.ly/argocd-faq.
* [x] I've included steps to reproduce the bug.
* [x] I've pasted the output of `argocd version`.

**Describe the bug**
We want to add a new cluster using ArgoCD cli with `--cluster-endpoint kube-public` option. The problem is, when Argo uses the endpoint specified in _kube-public_ config, it does not use the certificate_authority_data associated with this endpoint. And this lead to problem in adding a new cluster. This problem only happens when the certificate_authority_data are not the same in _kubeconfig_ and _kube-public_.

if we want to change the endpoint according to _kube-public_, it's logical to use the certificate_authority_data in _kube-public_ as well, since certificate_authority_data and endpoint are related.

**To Reproduce**

Adding a cluster using Argocd cli with `--cluster-endpoint kube-public` option. When certificate_authority_data is different in _kubeconfig_ and _kube-public_, raises x509 error.

```
root@runner:/# argocd cluster add --name neda-dog --core --system-namespace argocd --cluster-endpoint kube-public --upsert -y neda-dog
INFO[0000] ServiceAccount "argocd-manager" already exists in namespace "argocd"
INFO[0000] ClusterRole "argocd-manager-role" updated
INFO[0000] ClusterRoleBinding "argocd-manager-role-binding" updated
ERRO[0001] finished unary call with code Unknown         error="Get \"https://api.k8s-dog.***.cloud:443/version?timeout=32s\": tls: failed to verify certificate: x509: certificate signed by unknown authority" grpc.code=Unknown grpc.method=Create grpc.service=cluster.ClusterService grpc.start_time="2024-12-29T17:13:01Z" grpc.time_ms=95.35 span.kind=server system=grpc
FATA[0001] rpc error: code = Unknown desc = Get "https://api.k8s-dog.***.cloud:443/version?timeout=32s": tls: failed to verify certificate: x509: certificate signed by unknown authority
```

**Expected behavior**

Must use the certificate_authority_data in _kube-public_ and added the new cluster successfully.

**Version**

```
root@runner:/# argocd version --core
argocd: v2.13.2+dc43124
  BuildDate: 2024-12-11T19:01:33Z
  GitCommit: dc43124058130db9a747d141d86d7c2f4aac7bf9
  GitTreeState: clean
  GoVersion: go1.22.9
  Compiler: gc
  Platform: linux/amd64
argocd-server: v2.13.2+dc43124
  BuildDate: 2024-12-11T19:01:33Z
  GitCommit: dc43124058130db9a747d141d86d7c2f4aac7bf9
  GitTreeState: clean
  GoVersion: go1.22.9
  Compiler: gc
  Platform: linux/amd64
  Kustomize Version: v5.5.0 2024-10-09T13:10:16Z
  Helm Version: v3.16.3+gcfd0749
  Kubectl Version: v0.31.0
  Jsonnet Version: v0.20.0
```




Discussion:

Patch:
diff --git a/cmd/argocd/commands/cluster.go b/cmd/argocd/commands/cluster.go
index da8447c7f9c02..e4be799f20c95 100644
--- a/cmd/argocd/commands/cluster.go
+++ b/cmd/argocd/commands/cluster.go
@@ -166,13 +166,14 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
 			if clusterOpts.InClusterEndpoint() {
 				clst.Server = argoappv1.KubernetesInternalAPIServerAddr
 			} else if clusterOpts.ClusterEndpoint == string(cmdutil.KubePublicEndpoint) {
-				endpoint, err := cmdutil.GetKubePublicEndpoint(clientset)
+				endpoint, caData, err := cmdutil.GetKubePublicEndpoint(clientset)
 				if err != nil || len(endpoint) == 0 {
 					log.Warnf("Failed to find the cluster endpoint from kube-public data: %v", err)
 					log.Infof("Falling back to the endpoint '%s' as listed in the kubeconfig context", clst.Server)
 					endpoint = clst.Server
 				}
 				clst.Server = endpoint
+				clst.Config.TLSClientConfig.CAData = caData
 			}
 
 			if clusterOpts.Shard >= 0 {
diff --git a/cmd/util/cluster.go b/cmd/util/cluster.go
index 5cd59baa0ea9c..7797409fd92ef 100644
--- a/cmd/util/cluster.go
+++ b/cmd/util/cluster.go
@@ -123,28 +123,30 @@ func NewCluster(name string, namespaces []string, clusterResources bool, conf *r
 	return &clst
 }
 
-// GetKubePublicEndpoint returns the kubernetes apiserver endpoint as published
+// GetKubePublicEndpoint returns the kubernetes apiserver endpoint and certificate authority data as published
 // in the kube-public.
-func GetKubePublicEndpoint(client kubernetes.Interface) (string, error) {
+func GetKubePublicEndpoint(client kubernetes.Interface) (string, []byte, error) {
 	clusterInfo, err := client.CoreV1().ConfigMaps("kube-public").Get(context.TODO(), "cluster-info", metav1.GetOptions{})
 	if err != nil {
-		return "", err
+		return "", nil, err
 	}
 	kubeconfig, ok := clusterInfo.Data["kubeconfig"]
 	if !ok {
-		return "", stderrors.New("cluster-info does not contain a public kubeconfig")
+		return "", nil, stderrors.New("cluster-info does not contain a public kubeconfig")
 	}
 	// Parse Kubeconfig and get server address
 	config := &clientcmdapiv1.Config{}
 	err = yaml.Unmarshal([]byte(kubeconfig), config)
 	if err != nil {
-		return "", fmt.Errorf("failed to parse cluster-info kubeconfig: %w", err)
+		return "", nil, fmt.Errorf("failed to parse cluster-info kubeconfig: %w", err)
 	}
 	if len(config.Clusters) == 0 {
-		return "", stderrors.New("cluster-info kubeconfig does not have any clusters")
+		return "", nil, stderrors.New("cluster-info kubeconfig does not have any clusters")
 	}
 
-	return config.Clusters[0].Cluster.Server, nil
+	endpoint := config.Clusters[0].Cluster.Server
+	certificateAuthorityData := config.Clusters[0].Cluster.CertificateAuthorityData
+	return endpoint, certificateAuthorityData, nil
 }
 
 type ClusterOptions struct {


Test patch:
diff --git a/cmd/util/cluster_test.go b/cmd/util/cluster_test.go
index 12ed3fe5cc3ad..500acad98ec01 100644
--- a/cmd/util/cluster_test.go
+++ b/cmd/util/cluster_test.go
@@ -96,8 +96,23 @@ func TestGetKubePublicEndpoint(t *testing.T) {
 		name             string
 		clusterInfo      *corev1.ConfigMap
 		expectedEndpoint string
+		expectedCAData   []byte
 		expectError      bool
 	}{
+		{
+			name: "has public endpoint and certificate authority data",
+			clusterInfo: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "kube-public",
+					Name:      "cluster-info",
+				},
+				Data: map[string]string{
+					"kubeconfig": kubeconfigFixture("https://test-cluster:6443", []byte("test-ca-data")),
+				},
+			},
+			expectedEndpoint: "https://test-cluster:6443",
+			expectedCAData:   []byte("test-ca-data"),
+		},
 		{
 			name: "has public endpoint",
 			clusterInfo: &corev1.ConfigMap{
@@ -106,10 +121,11 @@ func TestGetKubePublicEndpoint(t *testing.T) {
 					Name:      "cluster-info",
 				},
 				Data: map[string]string{
-					"kubeconfig": kubeconfigFixture("https://test-cluster:6443"),
+					"kubeconfig": kubeconfigFixture("https://test-cluster:6443", nil),
 				},
 			},
 			expectedEndpoint: "https://test-cluster:6443",
+			expectedCAData:   nil,
 		},
 		{
 			name:        "no cluster-info",
@@ -136,7 +152,7 @@ func TestGetKubePublicEndpoint(t *testing.T) {
 					Name:      "cluster-info",
 				},
 				Data: map[string]string{
-					"kubeconfig": kubeconfigFixture(""),
+					"kubeconfig": kubeconfigFixture("", nil),
 				},
 			},
 			expectError: true,
@@ -163,25 +179,27 @@ func TestGetKubePublicEndpoint(t *testing.T) {
 				objects = append(objects, tc.clusterInfo)
 			}
 			clientset := fake.NewClientset(objects...)
-			endpoint, err := GetKubePublicEndpoint(clientset)
+			endpoint, caData, err := GetKubePublicEndpoint(clientset)
 			if tc.expectError {
 				require.Error(t, err)
 			} else {
 				require.NoError(t, err)
 			}
 			require.Equalf(t, tc.expectedEndpoint, endpoint, "expected endpoint %s, got %s", tc.expectedEndpoint, endpoint)
+			require.Equalf(t, tc.expectedCAData, caData, "expected caData %s, got %s", tc.expectedCAData, caData)
 		})
 	}
 }
 
-func kubeconfigFixture(endpoint string) string {
+func kubeconfigFixture(endpoint string, certificateAuthorityData []byte) string {
 	kubeconfig := &clientcmdapiv1.Config{}
 	if len(endpoint) > 0 {
 		kubeconfig.Clusters = []clientcmdapiv1.NamedCluster{
 			{
 				Name: "test-kube",
 				Cluster: clientcmdapiv1.Cluster{
-					Server: endpoint,
+					Server:                   endpoint,
+					CertificateAuthorityData: certificateAuthorityData,
 				},
 			},
 		}