Repository Credentials Race Condition Crashes Argo CD Server
Description
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. Versions between 2.1.0 and 2.14.19, 3.2.0-rc1, 3.1.0-rc1 through 3.1.7, and 3.0.0-rc1 through 3.0.18 contain a race condition in the repository credentials handler that can cause the Argo CD server to panic and crash when concurrent operations are performed on the same repository URL. The vulnerability is located in numerous repository related handlers in the util/db/repository_secrets.go file. A valid API token with repositories resource permissions (create, update, or delete actions) is required to trigger the race condition. This vulnerability causes the entire Argo CD server to crash and become unavailable. Attackers can repeatedly and continuously trigger the race condition to maintain a denial-of-service state, disrupting all GitOps operations. This issue is fixed in versions 2.14.20, 3.2.0-rc2, 3.1.8 and 3.0.19.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/argoproj/argo-cd/v2Go | >= 2.1.0, < 2.14.20 | 2.14.20 |
github.com/argoproj/argo-cd/v3Go | >= 3.2.0-rc1, < 3.2.0-rc2 | 3.2.0-rc2 |
github.com/argoproj/argo-cd/v3Go | >= 3.1.0-rc1, < 3.1.8 | 3.1.8 |
github.com/argoproj/argo-cd/v3Go | >= 3.0.0-rc1, < 3.0.19 | 3.0.19 |
Affected products
1Patches
1701bc50d01c7Merge commit from fork
2 files changed · +294 −114
util/db/repository_secrets.go+118 −106 modified@@ -34,9 +34,9 @@ func (s *secretsRepositoryBackend) CreateRepository(ctx context.Context, reposit }, } - s.repositoryToSecret(repository, repositorySecret) + updatedSecret := s.repositoryToSecret(repository, repositorySecret) - _, err := s.db.createSecret(ctx, repositorySecret) + _, err := s.db.createSecret(ctx, updatedSecret) if err != nil { if apierrors.IsAlreadyExists(err) { hasLabel, err := s.hasRepoTypeLabel(secName) @@ -142,9 +142,9 @@ func (s *secretsRepositoryBackend) UpdateRepository(ctx context.Context, reposit return nil, err } - s.repositoryToSecret(repository, repositorySecret) + updatedSecret := s.repositoryToSecret(repository, repositorySecret) - _, err = s.db.kubeclientset.CoreV1().Secrets(s.db.ns).Update(ctx, repositorySecret, metav1.UpdateOptions{}) + _, err = s.db.kubeclientset.CoreV1().Secrets(s.db.ns).Update(ctx, updatedSecret, metav1.UpdateOptions{}) if err != nil { return nil, err } @@ -187,9 +187,9 @@ func (s *secretsRepositoryBackend) CreateRepoCreds(ctx context.Context, repoCred }, } - s.repoCredsToSecret(repoCreds, repoCredsSecret) + updatedSecret := s.repoCredsToSecret(repoCreds, repoCredsSecret) - _, err := s.db.createSecret(ctx, repoCredsSecret) + _, err := s.db.createSecret(ctx, updatedSecret) if err != nil { if apierrors.IsAlreadyExists(err) { return nil, status.Errorf(codes.AlreadyExists, "repository credentials %q already exists", repoCreds.URL) @@ -237,9 +237,9 @@ func (s *secretsRepositoryBackend) UpdateRepoCreds(ctx context.Context, repoCred return nil, err } - s.repoCredsToSecret(repoCreds, repoCredsSecret) + updatedSecret := s.repoCredsToSecret(repoCreds, repoCredsSecret) - repoCredsSecret, err = s.db.kubeclientset.CoreV1().Secrets(s.db.ns).Update(ctx, repoCredsSecret, metav1.UpdateOptions{}) + repoCredsSecret, err = s.db.kubeclientset.CoreV1().Secrets(s.db.ns).Update(ctx, updatedSecret, metav1.UpdateOptions{}) if err != nil { return nil, err } @@ -323,43 +323,45 @@ func (s *secretsRepositoryBackend) GetAllOCIRepoCreds(_ context.Context) ([]*app } func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) { + secretCopy := secret.DeepCopy() + repository := &appsv1.Repository{ - Name: string(secret.Data["name"]), - Repo: string(secret.Data["url"]), - Username: string(secret.Data["username"]), - Password: string(secret.Data["password"]), - BearerToken: string(secret.Data["bearerToken"]), - SSHPrivateKey: string(secret.Data["sshPrivateKey"]), - TLSClientCertData: string(secret.Data["tlsClientCertData"]), - TLSClientCertKey: string(secret.Data["tlsClientCertKey"]), - Type: string(secret.Data["type"]), - GithubAppPrivateKey: string(secret.Data["githubAppPrivateKey"]), - GitHubAppEnterpriseBaseURL: string(secret.Data["githubAppEnterpriseBaseUrl"]), - Proxy: string(secret.Data["proxy"]), - NoProxy: string(secret.Data["noProxy"]), - Project: string(secret.Data["project"]), - GCPServiceAccountKey: string(secret.Data["gcpServiceAccountKey"]), - } - - insecureIgnoreHostKey, err := boolOrFalse(secret, "insecureIgnoreHostKey") + Name: string(secretCopy.Data["name"]), + Repo: string(secretCopy.Data["url"]), + Username: string(secretCopy.Data["username"]), + Password: string(secretCopy.Data["password"]), + BearerToken: string(secretCopy.Data["bearerToken"]), + SSHPrivateKey: string(secretCopy.Data["sshPrivateKey"]), + TLSClientCertData: string(secretCopy.Data["tlsClientCertData"]), + TLSClientCertKey: string(secretCopy.Data["tlsClientCertKey"]), + Type: string(secretCopy.Data["type"]), + GithubAppPrivateKey: string(secretCopy.Data["githubAppPrivateKey"]), + GitHubAppEnterpriseBaseURL: string(secretCopy.Data["githubAppEnterpriseBaseUrl"]), + Proxy: string(secretCopy.Data["proxy"]), + NoProxy: string(secretCopy.Data["noProxy"]), + Project: string(secretCopy.Data["project"]), + GCPServiceAccountKey: string(secretCopy.Data["gcpServiceAccountKey"]), + } + + insecureIgnoreHostKey, err := boolOrFalse(secretCopy, "insecureIgnoreHostKey") if err != nil { return repository, err } repository.InsecureIgnoreHostKey = insecureIgnoreHostKey - insecure, err := boolOrFalse(secret, "insecure") + insecure, err := boolOrFalse(secretCopy, "insecure") if err != nil { return repository, err } repository.Insecure = insecure - enableLfs, err := boolOrFalse(secret, "enableLfs") + enableLfs, err := boolOrFalse(secretCopy, "enableLfs") if err != nil { return repository, err } repository.EnableLFS = enableLfs - enableOCI, err := boolOrFalse(secret, "enableOCI") + enableOCI, err := boolOrFalse(secretCopy, "enableOCI") if err != nil { return repository, err } @@ -371,19 +373,19 @@ func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) { } repository.InsecureOCIForceHttp = insecureOCIForceHTTP - githubAppID, err := intOrZero(secret, "githubAppID") + githubAppID, err := intOrZero(secretCopy, "githubAppID") if err != nil { return repository, err } repository.GithubAppId = githubAppID - githubAppInstallationID, err := intOrZero(secret, "githubAppInstallationID") + githubAppInstallationID, err := intOrZero(secretCopy, "githubAppInstallationID") if err != nil { return repository, err } repository.GithubAppInstallationId = githubAppInstallationID - forceBasicAuth, err := boolOrFalse(secret, "forceHttpBasicAuth") + forceBasicAuth, err := boolOrFalse(secretCopy, "forceHttpBasicAuth") if err != nil { return repository, err } @@ -398,56 +400,62 @@ func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) { return repository, nil } -func (s *secretsRepositoryBackend) repositoryToSecret(repository *appsv1.Repository, secret *corev1.Secret) { - if secret.Data == nil { - secret.Data = make(map[string][]byte) - } - - updateSecretString(secret, "name", repository.Name) - updateSecretString(secret, "project", repository.Project) - updateSecretString(secret, "url", repository.Repo) - updateSecretString(secret, "username", repository.Username) - updateSecretString(secret, "password", repository.Password) - updateSecretString(secret, "bearerToken", repository.BearerToken) - updateSecretString(secret, "sshPrivateKey", repository.SSHPrivateKey) - updateSecretBool(secret, "enableOCI", repository.EnableOCI) - updateSecretBool(secret, "insecureOCIForceHttp", repository.InsecureOCIForceHttp) - updateSecretString(secret, "tlsClientCertData", repository.TLSClientCertData) - updateSecretString(secret, "tlsClientCertKey", repository.TLSClientCertKey) - updateSecretString(secret, "type", repository.Type) - updateSecretString(secret, "githubAppPrivateKey", repository.GithubAppPrivateKey) - updateSecretInt(secret, "githubAppID", repository.GithubAppId) - updateSecretInt(secret, "githubAppInstallationID", repository.GithubAppInstallationId) - updateSecretString(secret, "githubAppEnterpriseBaseUrl", repository.GitHubAppEnterpriseBaseURL) - updateSecretBool(secret, "insecureIgnoreHostKey", repository.InsecureIgnoreHostKey) - updateSecretBool(secret, "insecure", repository.Insecure) - updateSecretBool(secret, "enableLfs", repository.EnableLFS) - updateSecretString(secret, "proxy", repository.Proxy) - updateSecretString(secret, "noProxy", repository.NoProxy) - updateSecretString(secret, "gcpServiceAccountKey", repository.GCPServiceAccountKey) - updateSecretBool(secret, "forceHttpBasicAuth", repository.ForceHttpBasicAuth) - updateSecretBool(secret, "useAzureWorkloadIdentity", repository.UseAzureWorkloadIdentity) - addSecretMetadata(secret, s.getSecretType()) +func (s *secretsRepositoryBackend) repositoryToSecret(repository *appsv1.Repository, secret *corev1.Secret) *corev1.Secret { + secretCopy := secret.DeepCopy() + + if secretCopy.Data == nil { + secretCopy.Data = make(map[string][]byte) + } + + updateSecretString(secretCopy, "name", repository.Name) + updateSecretString(secretCopy, "project", repository.Project) + updateSecretString(secretCopy, "url", repository.Repo) + updateSecretString(secretCopy, "username", repository.Username) + updateSecretString(secretCopy, "password", repository.Password) + updateSecretString(secretCopy, "bearerToken", repository.BearerToken) + updateSecretString(secretCopy, "sshPrivateKey", repository.SSHPrivateKey) + updateSecretBool(secretCopy, "enableOCI", repository.EnableOCI) + updateSecretBool(secretCopy, "insecureOCIForceHttp", repository.InsecureOCIForceHttp) + updateSecretString(secretCopy, "tlsClientCertData", repository.TLSClientCertData) + updateSecretString(secretCopy, "tlsClientCertKey", repository.TLSClientCertKey) + updateSecretString(secretCopy, "type", repository.Type) + updateSecretString(secretCopy, "githubAppPrivateKey", repository.GithubAppPrivateKey) + updateSecretInt(secretCopy, "githubAppID", repository.GithubAppId) + updateSecretInt(secretCopy, "githubAppInstallationID", repository.GithubAppInstallationId) + updateSecretString(secretCopy, "githubAppEnterpriseBaseUrl", repository.GitHubAppEnterpriseBaseURL) + updateSecretBool(secretCopy, "insecureIgnoreHostKey", repository.InsecureIgnoreHostKey) + updateSecretBool(secretCopy, "insecure", repository.Insecure) + updateSecretBool(secretCopy, "enableLfs", repository.EnableLFS) + updateSecretString(secretCopy, "proxy", repository.Proxy) + updateSecretString(secretCopy, "noProxy", repository.NoProxy) + updateSecretString(secretCopy, "gcpServiceAccountKey", repository.GCPServiceAccountKey) + updateSecretBool(secretCopy, "forceHttpBasicAuth", repository.ForceHttpBasicAuth) + updateSecretBool(secretCopy, "useAzureWorkloadIdentity", repository.UseAzureWorkloadIdentity) + addSecretMetadata(secretCopy, s.getSecretType()) + + return secretCopy } func (s *secretsRepositoryBackend) secretToRepoCred(secret *corev1.Secret) (*appsv1.RepoCreds, error) { + secretCopy := secret.DeepCopy() + repository := &appsv1.RepoCreds{ - URL: string(secret.Data["url"]), - Username: string(secret.Data["username"]), - Password: string(secret.Data["password"]), - BearerToken: string(secret.Data["bearerToken"]), - SSHPrivateKey: string(secret.Data["sshPrivateKey"]), - TLSClientCertData: string(secret.Data["tlsClientCertData"]), - TLSClientCertKey: string(secret.Data["tlsClientCertKey"]), - Type: string(secret.Data["type"]), - GithubAppPrivateKey: string(secret.Data["githubAppPrivateKey"]), - GitHubAppEnterpriseBaseURL: string(secret.Data["githubAppEnterpriseBaseUrl"]), - GCPServiceAccountKey: string(secret.Data["gcpServiceAccountKey"]), - Proxy: string(secret.Data["proxy"]), - NoProxy: string(secret.Data["noProxy"]), - } - - enableOCI, err := boolOrFalse(secret, "enableOCI") + URL: string(secretCopy.Data["url"]), + Username: string(secretCopy.Data["username"]), + Password: string(secretCopy.Data["password"]), + BearerToken: string(secretCopy.Data["bearerToken"]), + SSHPrivateKey: string(secretCopy.Data["sshPrivateKey"]), + TLSClientCertData: string(secretCopy.Data["tlsClientCertData"]), + TLSClientCertKey: string(secretCopy.Data["tlsClientCertKey"]), + Type: string(secretCopy.Data["type"]), + GithubAppPrivateKey: string(secretCopy.Data["githubAppPrivateKey"]), + GitHubAppEnterpriseBaseURL: string(secretCopy.Data["githubAppEnterpriseBaseUrl"]), + GCPServiceAccountKey: string(secretCopy.Data["gcpServiceAccountKey"]), + Proxy: string(secretCopy.Data["proxy"]), + NoProxy: string(secretCopy.Data["noProxy"]), + } + + enableOCI, err := boolOrFalse(secretCopy, "enableOCI") if err != nil { return repository, err } @@ -459,19 +467,19 @@ func (s *secretsRepositoryBackend) secretToRepoCred(secret *corev1.Secret) (*app } repository.InsecureOCIForceHttp = insecureOCIForceHTTP - githubAppID, err := intOrZero(secret, "githubAppID") + githubAppID, err := intOrZero(secretCopy, "githubAppID") if err != nil { return repository, err } repository.GithubAppId = githubAppID - githubAppInstallationID, err := intOrZero(secret, "githubAppInstallationID") + githubAppInstallationID, err := intOrZero(secretCopy, "githubAppInstallationID") if err != nil { return repository, err } repository.GithubAppInstallationId = githubAppInstallationID - forceBasicAuth, err := boolOrFalse(secret, "forceHttpBasicAuth") + forceBasicAuth, err := boolOrFalse(secretCopy, "forceHttpBasicAuth") if err != nil { return repository, err } @@ -486,31 +494,35 @@ func (s *secretsRepositoryBackend) secretToRepoCred(secret *corev1.Secret) (*app return repository, nil } -func (s *secretsRepositoryBackend) repoCredsToSecret(repoCreds *appsv1.RepoCreds, secret *corev1.Secret) { - if secret.Data == nil { - secret.Data = make(map[string][]byte) - } - - updateSecretString(secret, "url", repoCreds.URL) - updateSecretString(secret, "username", repoCreds.Username) - updateSecretString(secret, "password", repoCreds.Password) - updateSecretString(secret, "bearerToken", repoCreds.BearerToken) - updateSecretString(secret, "sshPrivateKey", repoCreds.SSHPrivateKey) - updateSecretBool(secret, "enableOCI", repoCreds.EnableOCI) - updateSecretBool(secret, "insecureOCIForceHttp", repoCreds.InsecureOCIForceHttp) - updateSecretString(secret, "tlsClientCertData", repoCreds.TLSClientCertData) - updateSecretString(secret, "tlsClientCertKey", repoCreds.TLSClientCertKey) - updateSecretString(secret, "type", repoCreds.Type) - updateSecretString(secret, "githubAppPrivateKey", repoCreds.GithubAppPrivateKey) - updateSecretInt(secret, "githubAppID", repoCreds.GithubAppId) - updateSecretInt(secret, "githubAppInstallationID", repoCreds.GithubAppInstallationId) - updateSecretString(secret, "githubAppEnterpriseBaseUrl", repoCreds.GitHubAppEnterpriseBaseURL) - updateSecretString(secret, "gcpServiceAccountKey", repoCreds.GCPServiceAccountKey) - updateSecretString(secret, "proxy", repoCreds.Proxy) - updateSecretString(secret, "noProxy", repoCreds.NoProxy) - updateSecretBool(secret, "forceHttpBasicAuth", repoCreds.ForceHttpBasicAuth) - updateSecretBool(secret, "useAzureWorkloadIdentity", repoCreds.UseAzureWorkloadIdentity) - addSecretMetadata(secret, s.getRepoCredSecretType()) +func (s *secretsRepositoryBackend) repoCredsToSecret(repoCreds *appsv1.RepoCreds, secret *corev1.Secret) *corev1.Secret { + secretCopy := secret.DeepCopy() + + if secretCopy.Data == nil { + secretCopy.Data = make(map[string][]byte) + } + + updateSecretString(secretCopy, "url", repoCreds.URL) + updateSecretString(secretCopy, "username", repoCreds.Username) + updateSecretString(secretCopy, "password", repoCreds.Password) + updateSecretString(secretCopy, "bearerToken", repoCreds.BearerToken) + updateSecretString(secretCopy, "sshPrivateKey", repoCreds.SSHPrivateKey) + updateSecretBool(secretCopy, "enableOCI", repoCreds.EnableOCI) + updateSecretBool(secretCopy, "insecureOCIForceHttp", repoCreds.InsecureOCIForceHttp) + updateSecretString(secretCopy, "tlsClientCertData", repoCreds.TLSClientCertData) + updateSecretString(secretCopy, "tlsClientCertKey", repoCreds.TLSClientCertKey) + updateSecretString(secretCopy, "type", repoCreds.Type) + updateSecretString(secretCopy, "githubAppPrivateKey", repoCreds.GithubAppPrivateKey) + updateSecretInt(secretCopy, "githubAppID", repoCreds.GithubAppId) + updateSecretInt(secretCopy, "githubAppInstallationID", repoCreds.GithubAppInstallationId) + updateSecretString(secretCopy, "githubAppEnterpriseBaseUrl", repoCreds.GitHubAppEnterpriseBaseURL) + updateSecretString(secretCopy, "gcpServiceAccountKey", repoCreds.GCPServiceAccountKey) + updateSecretString(secretCopy, "proxy", repoCreds.Proxy) + updateSecretString(secretCopy, "noProxy", repoCreds.NoProxy) + updateSecretBool(secretCopy, "forceHttpBasicAuth", repoCreds.ForceHttpBasicAuth) + updateSecretBool(secretCopy, "useAzureWorkloadIdentity", repoCreds.UseAzureWorkloadIdentity) + addSecretMetadata(secretCopy, s.getRepoCredSecretType()) + + return secretCopy } func (s *secretsRepositoryBackend) getRepositorySecret(repoURL, project string, allowFallback bool) (*corev1.Secret, error) {
util/db/repository_secrets_test.go+176 −8 modified@@ -1,7 +1,9 @@ package db import ( + "fmt" "strconv" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -85,9 +87,9 @@ func TestSecretsRepositoryBackend_CreateRepository(t *testing.T) { t.Parallel() secret := &corev1.Secret{} s := secretsRepositoryBackend{} - s.repositoryToSecret(repo, secret) - delete(secret.Labels, common.LabelKeySecretType) - f := setupWithK8sObjects(secret) + updatedSecret := s.repositoryToSecret(repo, secret) + delete(updatedSecret.Labels, common.LabelKeySecretType) + f := setupWithK8sObjects(updatedSecret) f.clientSet.ReactionChain = nil f.clientSet.AddReactor("create", "secrets", func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) { gr := schema.GroupResource{ @@ -122,8 +124,8 @@ func TestSecretsRepositoryBackend_CreateRepository(t *testing.T) { }, } s := secretsRepositoryBackend{} - s.repositoryToSecret(repo, secret) - f := setupWithK8sObjects(secret) + updatedSecret := s.repositoryToSecret(repo, secret) + f := setupWithK8sObjects(updatedSecret) f.clientSet.ReactionChain = nil f.clientSet.WatchReactionChain = nil f.clientSet.AddReactor("create", "secrets", func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) { @@ -134,7 +136,7 @@ func TestSecretsRepositoryBackend_CreateRepository(t *testing.T) { return true, nil, apierrors.NewAlreadyExists(gr, "already exists") }) watcher := watch.NewFakeWithChanSize(1, true) - watcher.Add(secret) + watcher.Add(updatedSecret) f.clientSet.AddWatchReactor("secrets", func(_ k8stesting.Action) (handled bool, ret watch.Interface, err error) { return true, watcher, nil }) @@ -952,7 +954,7 @@ func TestRepoCredsToSecret(t *testing.T) { GithubAppInstallationId: 456, GitHubAppEnterpriseBaseURL: "GitHubAppEnterpriseBaseURL", } - testee.repoCredsToSecret(creds, s) + s = testee.repoCredsToSecret(creds, s) assert.Equal(t, []byte(creds.URL), s.Data["url"]) assert.Equal(t, []byte(creds.Username), s.Data["username"]) assert.Equal(t, []byte(creds.Password), s.Data["password"]) @@ -994,7 +996,7 @@ func TestRepoWriteCredsToSecret(t *testing.T) { GithubAppInstallationId: 456, GitHubAppEnterpriseBaseURL: "GitHubAppEnterpriseBaseURL", } - testee.repoCredsToSecret(creds, s) + s = testee.repoCredsToSecret(creds, s) assert.Equal(t, []byte(creds.URL), s.Data["url"]) assert.Equal(t, []byte(creds.Username), s.Data["username"]) assert.Equal(t, []byte(creds.Password), s.Data["password"]) @@ -1010,3 +1012,169 @@ func TestRepoWriteCredsToSecret(t *testing.T) { assert.Equal(t, map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, s.Annotations) assert.Equal(t, map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCredsWrite}, s.Labels) } + +func TestRaceConditionInRepoCredsOperations(t *testing.T) { + // Create a single shared secret that will be accessed concurrently + sharedSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""), + Namespace: testNamespace, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "username": []byte("test-user"), + "password": []byte("test-pass"), + }, + } + + // Create test credentials that we'll use for conversion + repoCreds := &appsv1.RepoCreds{ + URL: "git@github.com:argoproj/argo-cd.git", + Username: "test-user", + Password: "test-pass", + } + + backend := &secretsRepositoryBackend{} + + var wg sync.WaitGroup + concurrentOps := 50 + errChan := make(chan error, concurrentOps*2) // Channel to collect errors + + // Launch goroutines that perform concurrent operations + for i := 0; i < concurrentOps; i++ { + wg.Add(2) + + // One goroutine converts from RepoCreds to Secret + go func() { + defer wg.Done() + defer func() { + if r := recover(); r != nil { + errChan <- fmt.Errorf("panic in repoCredsToSecret: %v", r) + } + }() + _ = backend.repoCredsToSecret(repoCreds, sharedSecret) + }() + + // Another goroutine converts from Secret to RepoCreds + go func() { + defer wg.Done() + defer func() { + if r := recover(); r != nil { + errChan <- fmt.Errorf("panic in secretToRepoCred: %v", r) + } + }() + creds, err := backend.secretToRepoCred(sharedSecret) + if err != nil { + errChan <- fmt.Errorf("error in secretToRepoCred: %w", err) + return + } + // Verify data integrity + if creds.URL != repoCreds.URL || creds.Username != repoCreds.Username || creds.Password != repoCreds.Password { + errChan <- fmt.Errorf("data mismatch in conversion: expected %v, got %v", repoCreds, creds) + } + }() + } + + wg.Wait() + close(errChan) + + // Check for any errors that occurred during concurrent operations + for err := range errChan { + t.Errorf("concurrent operation error: %v", err) + } + + // Verify final state + finalCreds, err := backend.secretToRepoCred(sharedSecret) + require.NoError(t, err) + assert.Equal(t, repoCreds.URL, finalCreds.URL) + assert.Equal(t, repoCreds.Username, finalCreds.Username) + assert.Equal(t, repoCreds.Password, finalCreds.Password) +} + +func TestRaceConditionInRepositoryOperations(t *testing.T) { + // Create a single shared secret that will be accessed concurrently + sharedSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""), + Namespace: testNamespace, + Labels: map[string]string{ + common.LabelKeySecretType: common.LabelValueSecretTypeRepository, + }, + }, + Data: map[string][]byte{ + "url": []byte("git@github.com:argoproj/argo-cd.git"), + "name": []byte("test-repo"), + "username": []byte("test-user"), + "password": []byte("test-pass"), + }, + } + + // Create test repository that we'll use for conversion + repo := &appsv1.Repository{ + Name: "test-repo", + Repo: "git@github.com:argoproj/argo-cd.git", + Username: "test-user", + Password: "test-pass", + } + + backend := &secretsRepositoryBackend{} + + var wg sync.WaitGroup + concurrentOps := 50 + errChan := make(chan error, concurrentOps*2) // Channel to collect errors + + // Launch goroutines that perform concurrent operations + for i := 0; i < concurrentOps; i++ { + wg.Add(2) + + // One goroutine converts from Repository to Secret + go func() { + defer wg.Done() + defer func() { + if r := recover(); r != nil { + errChan <- fmt.Errorf("panic in repositoryToSecret: %v", r) + } + }() + _ = backend.repositoryToSecret(repo, sharedSecret) + }() + + // Another goroutine converts from Secret to Repository + go func() { + defer wg.Done() + defer func() { + if r := recover(); r != nil { + errChan <- fmt.Errorf("panic in secretToRepository: %v", r) + } + }() + repository, err := secretToRepository(sharedSecret) + if err != nil { + errChan <- fmt.Errorf("error in secretToRepository: %w", err) + return + } + // Verify data integrity + if repository.Name != repo.Name || repository.Repo != repo.Repo || + repository.Username != repo.Username || repository.Password != repo.Password { + errChan <- fmt.Errorf("data mismatch in conversion: expected %v, got %v", repo, repository) + } + }() + } + + wg.Wait() + close(errChan) + + // Check for any errors that occurred during concurrent operations + for err := range errChan { + t.Errorf("concurrent operation error: %v", err) + } + + // Verify final state + finalRepo, err := secretToRepository(sharedSecret) + require.NoError(t, err) + assert.Equal(t, repo.Name, finalRepo.Name) + assert.Equal(t, repo.Repo, finalRepo.Repo) + assert.Equal(t, repo.Username, finalRepo.Username) + assert.Equal(t, repo.Password, finalRepo.Password) +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-g88p-r42r-ppp9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-55191ghsaADVISORY
- github.com/argoproj/argo-cd/commit/701bc50d01c752cad96185f848088d287a97c7b7ghsax_refsource_MISCWEB
- github.com/argoproj/argo-cd/pull/6103ghsax_refsource_MISCWEB
- github.com/argoproj/argo-cd/security/advisories/GHSA-g88p-r42r-ppp9ghsax_refsource_CONFIRMWEB
- pkg.go.dev/vuln/GO-2025-3994ghsaWEB
News mentions
0No linked articles in our index yet.