CVE-2023-40029: Insertion of Sensitive Information into Log File (CWE-532)
value: Argo CD is a declarative continuous deployment for Kubernetes. Argo CD Cluster secrets might be managed declaratively using Argo CD / kubectl apply. As a result, the full secret body is stored in`kubectl.kubernetes.io/last-applied-configuration` annotation. pull request #7139 introduced the ability to manage cluster labels and annotations. Since clusters are stored as secrets it also exposes the `kubectl.kubernetes.io/last-applied-configuration` annotation which includes full secret body. In order to view the cluster annotations via the Argo CD API, the user must have `clusters, get` RBAC access. **Note:** In many cases, cluster secrets do not contain any actually-secret information. But sometimes, as in bearer-token auth, the contents might be very sensitive. The bug has been patched in versions 2.8.3, 2.7.14, and 2.6.15. Users are advised to upgrade. Users unable to upgrade should update/deploy cluster secret with `server-side-apply` flag which does not use or rely on `kubectl.kubernetes.io/last-applied-configuration` annotation. Note: annotation for existing secrets will require manual removal.


CWE description: Information written to log files can be of a sensitive nature and give valuable guidance to an attacker or expose sensitive user information.
CVSS v3 base score: 9.6
Published: 2023-09-07T23:15Z
Repository: https://github.com/argoproj/argo-cd
Language: Go
Files changed: util/db/cluster.go, util/db/cluster_test.go
Commit date: 2023-09-07 10:14:57 -0400
Commit message: Merge pull request from GHSA-fwr2-64vr-xv9m

* fix: prevent seeing/editing 'kubectl.kubernetes.io/last-applied-configuration' cluster annotation

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

* fix: failing unit test

Signed-off-by: iam-veeramalla <abhishek.veeramalla@gmail.com>

---------

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Signed-off-by: iam-veeramalla <abhishek.veeramalla@gmail.com>
Co-authored-by: iam-veeramalla <abhishek.veeramalla@gmail.com>
Diff: diff --git a/util/db/cluster.go b/util/db/cluster.go
index 9b405a9ca..69da0a3bd 100644
--- a/util/db/cluster.go
+++ b/util/db/cluster.go
@@ -343,10 +343,13 @@ func clusterToSecret(c *appv1.Cluster, secret *apiv1.Secret) error {
 		data["project"] = []byte(c.Project)
 	}
 	secret.Data = data
 
 	secret.Labels = c.Labels
+	if c.Annotations != nil && c.Annotations[apiv1.LastAppliedConfigAnnotation] != "" {
+		return status.Errorf(codes.InvalidArgument, "annotation %s cannot be set", apiv1.LastAppliedConfigAnnotation)
+	}
 	secret.Annotations = c.Annotations
 
 	if secret.Annotations == nil {
 		secret.Annotations = make(map[string]string)
 	}
@@ -401,10 +404,12 @@ func SecretToCluster(s *apiv1.Secret) (*appv1.Cluster, error) {
 		delete(labels, common.LabelKeySecretType)
 	}
 	annotations := map[string]string{}
 	if s.Annotations != nil {
 		annotations = collections.CopyStringMap(s.Annotations)
+		// delete system annotations
+		delete(annotations, apiv1.LastAppliedConfigAnnotation)
 		delete(annotations, common.AnnotationKeyManagedBy)
 	}
 
 	cluster := appv1.Cluster{
 		ID:                 string(s.UID),
diff --git a/util/db/cluster_test.go b/util/db/cluster_test.go
index 9d60a3073..5d99edf83 100644
--- a/util/db/cluster_test.go
+++ b/util/db/cluster_test.go
@@ -5,10 +5,12 @@ import (
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes/fake"
 
 	"github.com/argoproj/argo-cd/v2/common"
@@ -54,10 +56,28 @@ func Test_secretToCluster(t *testing.T) {
 		Labels:      labels,
 		Annotations: annotations,
 	})
 }
 
+func Test_secretToCluster_LastAppliedConfigurationDropped(t *testing.T) {
+	secret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:        "mycluster",
+			Namespace:   fakeNamespace,
+			Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
+		},
+		Data: map[string][]byte{
+			"name":   []byte("test"),
+			"server": []byte("http://mycluster"),
+			"config": []byte("{\"username\":\"foo\"}"),
+		},
+	}
+	cluster, err := SecretToCluster(secret)
+	require.NoError(t, err)
+	assert.Len(t, cluster.Annotations, 0)
+}
+
 func TestClusterToSecret(t *testing.T) {
 	cluster := &appv1.Cluster{
 		Server:      "server",
 		Labels:      map[string]string{"test": "label"},
 		Annotations: map[string]string{"test": "annotation"},
@@ -76,10 +96,25 @@ func TestClusterToSecret(t *testing.T) {
 	assert.Equal(t, []byte("default"), s.Data["namespaces"])
 	assert.Equal(t, cluster.Annotations, s.Annotations)
 	assert.Equal(t, cluster.Labels, s.Labels)
 }
 
+func TestClusterToSecret_LastAppliedConfigurationRejected(t *testing.T) {
+	cluster := &appv1.Cluster{
+		Server:      "server",
+		Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
+		Name:        "test",
+		Config:      v1alpha1.ClusterConfig{},
+		Project:     "project",
+		Namespaces:  []string{"default"},
+	}
+	s := &v1.Secret{}
+	err := clusterToSecret(cluster, s)
+	require.Error(t, err)
+	require.Equal(t, codes.InvalidArgument, status.Code(err))
+}
+
 func Test_secretToCluster_NoConfig(t *testing.T) {
 	secret := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "mycluster",
 			Namespace: fakeNamespace,

Fixed code: 	if c.Annotations != nil && c.Annotations[apiv1.LastAppliedConfigAnnotation] != "" {
		return status.Errorf(codes.InvalidArgument, "annotation %s cannot be set", apiv1.LastAppliedConfigAnnotation)
	}
		// delete system annotations
		delete(annotations, apiv1.LastAppliedConfigAnnotation)
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
func Test_secretToCluster_LastAppliedConfigurationDropped(t *testing.T) {
	secret := &v1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:        "mycluster",
			Namespace:   fakeNamespace,
			Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
		},
		Data: map[string][]byte{
			"name":   []byte("test"),
			"server": []byte("http://mycluster"),
			"config": []byte("{\"username\":\"foo\"}"),
		},
	}
	cluster, err := SecretToCluster(secret)
	require.NoError(t, err)
	assert.Len(t, cluster.Annotations, 0)
}

func TestClusterToSecret_LastAppliedConfigurationRejected(t *testing.T) {
	cluster := &appv1.Cluster{
		Server:      "server",
		Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
		Name:        "test",
		Config:      v1alpha1.ClusterConfig{},
		Project:     "project",
		Namespaces:  []string{"default"},
	}
	s := &v1.Secret{}
	err := clusterToSecret(cluster, s)
	require.Error(t, err)
	require.Equal(t, codes.InvalidArgument, status.Code(err))
}