VYPR
High severityNVD Advisory· Published Jul 12, 2022· Updated Aug 2, 2024

CVE-2022-1025

CVE-2022-1025

Description

All unpatched versions of Argo CD starting with v1.0.0 are vulnerable to an improper access control bug, allowing a malicious user to potentially escalate their privileges to admin-level.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CVE-2022-1025 is an improper access control vulnerability in Argo CD (since v1.0.0) allowing authorized users to escalate privileges to admin-level by manipulating application resources.

All unpatched versions of Argo CD starting with v1.0.0 are vulnerable to an improper access control bug that allows a malicious user to potentially escalate their privileges to admin-level [1][2][4]. The root cause is insufficient validation of resource-scoping checks when a user with push or sync access to an Application interacts with resources on the destination cluster. Specifically, the ListResourceEvents and getAppResource functions did not verify that the requested resource belonged to the application, enabling access to arbitrary resources [3].

Exploitation requires that an authorized Argo CD user has push access to an Application's source git or Helm repository or sync and override access to the Application [4]. Depending on the user's RBAC privileges, different levels of exploitation are possible: with update access, the user can modify any resource on the destination cluster, and if that cluster is or can be made to be the same as the cluster hosting Argo CD, they can escalate their Argo CD permissions to admin-level. With delete access, they can delete any resource; with get access, they can view resources (except Secrets) and Pod logs, and run actions. Additionally, an events exploit allows a user with get access to access any Event in the Application's destination cluster by knowing the involved object's name, UID, and namespace [4].

The impact of successful exploitation ranges from information disclosure and unauthorized modification/deletion to full admin-level control over Argo CD, potentially compromising the entire continuous delivery pipeline and associated clusters. An attacker who gains admin privileges can manipulate configurations, access sensitive data, and pivot to other systems [2][4].

Mitigations are available in the Argo CD project's fix commit [3] and subsequent releases. The Argo CD security advisory recommends upgrading to the latest patched version. No workarounds are widely documented, so updating is the primary remediation. The vulnerability is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog as of this writing.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/argoproj/argo-cdGo
>= 0.5.0, <= 1.8.7
github.com/argoproj/argo-cd/v2Go
< 2.1.142.1.14
github.com/argoproj/argo-cd/v2Go
>= 2.2.0, < 2.2.82.2.8
github.com/argoproj/argo-cd/v2Go
>= 2.3.0, < 2.3.22.3.2

Affected products

3

Patches

1
af03b291d4b7

Merge pull request from GHSA-2f5v-8r3f-8pww

https://github.com/argoproj/argo-cdAlexander MatyushentsevMar 22, 2022via ghsa
10 files changed · +197 76
  • controller/appcontroller.go+18 4 modified
    @@ -28,6 +28,7 @@ import (
     	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
     	"k8s.io/apimachinery/pkg/labels"
     	apiruntime "k8s.io/apimachinery/pkg/runtime"
    +	"k8s.io/apimachinery/pkg/runtime/schema"
     	"k8s.io/apimachinery/pkg/types"
     	"k8s.io/apimachinery/pkg/util/runtime"
     	"k8s.io/apimachinery/pkg/util/wait"
    @@ -421,8 +422,12 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed
     				},
     			})
     		} else {
    -			err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, kube.GetResourceKey(live), func(child appv1.ResourceNode, appName string) {
    +			err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, kube.GetResourceKey(live), func(child appv1.ResourceNode, appName string) bool {
    +				if !proj.IsResourcePermitted(schema.GroupKind{Group: child.ResourceRef.Group, Kind: child.ResourceRef.Kind}, child.Namespace, a.Spec.Destination) {
    +					return false
    +				}
     				nodes = append(nodes, child)
    +				return true
     			})
     			if err != nil {
     				return nil, err
    @@ -432,16 +437,18 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed
     	orphanedNodes := make([]appv1.ResourceNode, 0)
     	for k := range orphanedNodesMap {
     		if k.Namespace != "" && proj.IsGroupKindPermitted(k.GroupKind(), true) && !isKnownOrphanedResourceExclusion(k, proj) {
    -			err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, k, func(child appv1.ResourceNode, appName string) {
    +			err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, k, func(child appv1.ResourceNode, appName string) bool {
     				belongToAnotherApp := false
     				if appName != "" {
     					if _, exists, err := ctrl.appInformer.GetIndexer().GetByKey(ctrl.namespace + "/" + appName); exists && err == nil {
     						belongToAnotherApp = true
     					}
     				}
    -				if !belongToAnotherApp {
    -					orphanedNodes = append(orphanedNodes, child)
    +				if belongToAnotherApp || !proj.IsResourcePermitted(schema.GroupKind{Group: child.ResourceRef.Group, Kind: child.ResourceRef.Kind}, child.Namespace, a.Spec.Destination) {
    +					return false
     				}
    +				orphanedNodes = append(orphanedNodes, child)
    +				return true
     			})
     			if err != nil {
     				return nil, err
    @@ -1291,6 +1298,13 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
     		app.Status.Sync.Status = appv1.SyncStatusCodeUnknown
     		app.Status.Health.Status = health.HealthStatusUnknown
     		ctrl.persistAppStatus(origApp, &app.Status)
    +
    +		if err := ctrl.cache.SetAppResourcesTree(app.Name, &appv1.ApplicationTree{}); err != nil {
    +			log.Warnf("failed to set app resource tree: %v", err)
    +		}
    +		if err := ctrl.cache.SetAppManagedResources(app.Name, nil); err != nil {
    +			log.Warnf("failed to set app managed resources tree: %v", err)
    +		}
     		return
     	}
     
    
  • controller/appcontroller_test.go+2 2 modified
    @@ -136,12 +136,12 @@ func newFakeController(data *fakeData) *ApplicationController {
     	mockStateCache.On("GetClusterCache", mock.Anything).Return(&clusterCacheMock, nil)
     	mockStateCache.On("IterateHierarchy", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
     		key := args[1].(kube.ResourceKey)
    -		action := args[2].(func(child argoappv1.ResourceNode, appName string))
    +		action := args[2].(func(child argoappv1.ResourceNode, appName string) bool)
     		appName := ""
     		if res, ok := data.namespacedResources[key]; ok {
     			appName = res.AppName
     		}
    -		action(argoappv1.ResourceNode{ResourceRef: argoappv1.ResourceRef{Kind: key.Kind, Group: key.Group, Namespace: key.Namespace, Name: key.Name}}, appName)
    +		_ = action(argoappv1.ResourceNode{ResourceRef: argoappv1.ResourceRef{Kind: key.Kind, Group: key.Group, Namespace: key.Namespace, Name: key.Name}}, appName)
     	}).Return(nil)
     	return ctrl
     }
    
  • controller/cache/cache.go+4 4 modified
    @@ -108,7 +108,7 @@ type LiveStateCache interface {
     	// Returns synced cluster cache
     	GetClusterCache(server string) (clustercache.ClusterCache, error)
     	// Executes give callback against resource specified by the key and all its children
    -	IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string)) error
    +	IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string) bool) error
     	// Returns state of live nodes which correspond for target nodes of specified application.
     	GetManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error)
     	// IterateResources iterates all resource stored in cache
    @@ -481,13 +481,13 @@ func (c *liveStateCache) IsNamespaced(server string, gk schema.GroupKind) (bool,
     	return clusterInfo.IsNamespaced(gk)
     }
     
    -func (c *liveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string)) error {
    +func (c *liveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string) bool) error {
     	clusterInfo, err := c.getSyncedCluster(server)
     	if err != nil {
     		return err
     	}
    -	clusterInfo.IterateHierarchy(key, func(resource *clustercache.Resource, namespaceResources map[kube.ResourceKey]*clustercache.Resource) {
    -		action(asResourceNode(resource), getApp(resource, namespaceResources))
    +	clusterInfo.IterateHierarchy(key, func(resource *clustercache.Resource, namespaceResources map[kube.ResourceKey]*clustercache.Resource) bool {
    +		return action(asResourceNode(resource), getApp(resource, namespaceResources))
     	})
     	return nil
     }
    
  • controller/cache/mocks/LiveStateCache.go+2 2 modified
    @@ -176,11 +176,11 @@ func (_m *LiveStateCache) IsNamespaced(server string, gk schema.GroupKind) (bool
     }
     
     // IterateHierarchy provides a mock function with given fields: server, key, action
    -func (_m *LiveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(v1alpha1.ResourceNode, string)) error {
    +func (_m *LiveStateCache) IterateHierarchy(server string, key kube.ResourceKey, action func(v1alpha1.ResourceNode, string) bool) error {
     	ret := _m.Called(server, key, action)
     
     	var r0 error
    -	if rf, ok := ret.Get(0).(func(string, kube.ResourceKey, func(v1alpha1.ResourceNode, string)) error); ok {
    +	if rf, ok := ret.Get(0).(func(string, kube.ResourceKey, func(v1alpha1.ResourceNode, string) bool) error); ok {
     		r0 = rf(server, key, action)
     	} else {
     		r0 = ret.Error(0)
    
  • go.mod+1 1 modified
    @@ -8,7 +8,7 @@ require (
     	github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
     	github.com/alicebob/miniredis v2.5.0+incompatible
     	github.com/alicebob/miniredis/v2 v2.14.2
    -	github.com/argoproj/gitops-engine v0.6.0
    +	github.com/argoproj/gitops-engine v0.6.1-0.20220316000647-723667dff7d5
     	github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998
     	github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0
     	github.com/bombsimon/logrusr/v2 v2.0.1
    
  • go.sum+2 2 modified
    @@ -130,8 +130,8 @@ github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmH
     github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
     github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
     github.com/appscode/go v0.0.0-20190808133642-1d4ef1f1c1e0/go.mod h1:iy07dV61Z7QQdCKJCIvUoDL21u6AIceRhZzyleh2ymc=
    -github.com/argoproj/gitops-engine v0.6.0 h1:Tnh6kUUVuBV0m3gueYIymAeErWl9XNN9O9JcOoNM0vU=
    -github.com/argoproj/gitops-engine v0.6.0/go.mod h1:pRgVpLW7pZqf7n3COJ7UcDepk4cI61LAcJd64Q3Jq/c=
    +github.com/argoproj/gitops-engine v0.6.1-0.20220316000647-723667dff7d5 h1:QZvSvXHKx/hKTvbw3EK+u9JDVlo6UvC4+IVqNIGbcUo=
    +github.com/argoproj/gitops-engine v0.6.1-0.20220316000647-723667dff7d5/go.mod h1:pRgVpLW7pZqf7n3COJ7UcDepk4cI61LAcJd64Q3Jq/c=
     github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998 h1:V9RDg+IZeebnm3XjkfkbN07VM21Fu1Cy/RJNoHO++VM=
     github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998/go.mod h1:5mKv7zEgI3NO0L+fsuRSwBSY9EIXSuyIsDND8O8TTIw=
     github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc=
    
  • pkg/apis/application/v1alpha1/app_project_types.go+7 3 modified
    @@ -313,11 +313,15 @@ func (proj AppProject) IsGroupKindPermitted(gk schema.GroupKind, namespaced bool
     
     // IsLiveResourcePermitted returns whether a live resource found in the cluster is permitted by an AppProject
     func (proj AppProject) IsLiveResourcePermitted(un *unstructured.Unstructured, server string, name string) bool {
    -	if !proj.IsGroupKindPermitted(un.GroupVersionKind().GroupKind(), un.GetNamespace() != "") {
    +	return proj.IsResourcePermitted(un.GroupVersionKind().GroupKind(), un.GetNamespace(), ApplicationDestination{Server: server, Name: name})
    +}
    +
    +func (proj AppProject) IsResourcePermitted(groupKind schema.GroupKind, namespace string, dest ApplicationDestination) bool {
    +	if !proj.IsGroupKindPermitted(groupKind, namespace != "") {
     		return false
     	}
    -	if un.GetNamespace() != "" {
    -		return proj.IsDestinationPermitted(ApplicationDestination{Server: server, Namespace: un.GetNamespace(), Name: name})
    +	if namespace != "" {
    +		return proj.IsDestinationPermitted(ApplicationDestination{Server: dest.Server, Name: dest.Name, Namespace: namespace})
     	}
     	return true
     }
    
  • server/application/application.go+23 8 modified
    @@ -488,6 +488,21 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *application.Applicat
     			"involvedObject.namespace": a.Namespace,
     		}).String()
     	} else {
    +		tree, err := s.getAppResources(ctx, a)
    +		if err != nil {
    +			return nil, err
    +		}
    +		found := false
    +		for _, n := range append(tree.Nodes, tree.OrphanedNodes...) {
    +			if n.ResourceRef.UID == q.ResourceUID && n.ResourceRef.Name == q.ResourceName && n.ResourceRef.Namespace == q.ResourceNamespace {
    +				found = true
    +				break
    +			}
    +		}
    +		if !found {
    +			return nil, status.Errorf(codes.InvalidArgument, "%s not found as part of application %s", q.ResourceName, *q.Name)
    +		}
    +
     		namespace = q.ResourceNamespace
     		var config *rest.Config
     		config, err = s.getApplicationClusterConfig(ctx, a)
    @@ -937,7 +952,7 @@ func (s *Server) getAppResources(ctx context.Context, a *appv1.Application) (*ap
     	return &tree, err
     }
     
    -func (s *Server) getAppResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
    +func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
     	a, err := s.appLister.Get(*q.Name)
     	if err != nil {
     		return nil, nil, nil, err
    @@ -952,7 +967,7 @@ func (s *Server) getAppResource(ctx context.Context, action string, q *applicati
     	}
     
     	found := tree.FindNode(q.Group, q.Kind, q.Namespace, q.ResourceName)
    -	if found == nil {
    +	if found == nil || found.ResourceRef.UID == "" {
     		return nil, nil, nil, status.Errorf(codes.InvalidArgument, "%s %s %s not found as part of application %s", q.Kind, q.Group, q.ResourceName, *q.Name)
     	}
     	config, err := s.getApplicationClusterConfig(ctx, a)
    @@ -963,7 +978,7 @@ func (s *Server) getAppResource(ctx context.Context, action string, q *applicati
     }
     
     func (s *Server) GetResource(ctx context.Context, q *application.ApplicationResourceRequest) (*application.ApplicationResourceResponse, error) {
    -	res, config, _, err := s.getAppResource(ctx, rbacpolicy.ActionGet, q)
    +	res, config, _, err := s.getAppLiveResource(ctx, rbacpolicy.ActionGet, q)
     	if err != nil {
     		return nil, err
     	}
    @@ -1008,7 +1023,7 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
     		Version:      q.Version,
     		Group:        q.Group,
     	}
    -	res, config, a, err := s.getAppResource(ctx, rbacpolicy.ActionUpdate, resourceRequest)
    +	res, config, a, err := s.getAppLiveResource(ctx, rbacpolicy.ActionUpdate, resourceRequest)
     	if err != nil {
     		return nil, err
     	}
    @@ -1048,7 +1063,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
     		Version:      q.Version,
     		Group:        q.Group,
     	}
    -	res, config, a, err := s.getAppResource(ctx, rbacpolicy.ActionDelete, resourceRequest)
    +	res, config, a, err := s.getAppLiveResource(ctx, rbacpolicy.ActionDelete, resourceRequest)
     	if err != nil {
     		return nil, err
     	}
    @@ -1335,7 +1350,7 @@ func getSelectedPods(treeNodes []appv1.ResourceNode, q *application.ApplicationP
     	var pods []appv1.ResourceNode
     	isTheOneMap := make(map[string]bool)
     	for _, treeNode := range treeNodes {
    -		if treeNode.Kind == kube.PodKind && treeNode.Group == "" {
    +		if treeNode.Kind == kube.PodKind && treeNode.Group == "" && treeNode.UID != "" {
     			if isTheSelectedOne(&treeNode, q, treeNodes, isTheOneMap) {
     				pods = append(pods, treeNode)
     			}
    @@ -1625,7 +1640,7 @@ func (s *Server) logResourceEvent(res *appv1.ResourceNode, ctx context.Context,
     }
     
     func (s *Server) ListResourceActions(ctx context.Context, q *application.ApplicationResourceRequest) (*application.ResourceActionsListResponse, error) {
    -	res, config, _, err := s.getAppResource(ctx, rbacpolicy.ActionGet, q)
    +	res, config, _, err := s.getAppLiveResource(ctx, rbacpolicy.ActionGet, q)
     	if err != nil {
     		return nil, err
     	}
    @@ -1676,7 +1691,7 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
     		Group:        q.Group,
     	}
     	actionRequest := fmt.Sprintf("%s/%s/%s/%s", rbacpolicy.ActionAction, q.Group, q.Kind, q.Action)
    -	res, config, a, err := s.getAppResource(ctx, actionRequest, resourceRequest)
    +	res, config, a, err := s.getAppLiveResource(ctx, actionRequest, resourceRequest)
     	if err != nil {
     		return nil, err
     	}
    
  • test/e2e/app_management_test.go+111 50 modified
    @@ -1046,64 +1046,125 @@ func TestSyncAsync(t *testing.T) {
     		Expect(SyncStatusIs(SyncStatusCodeSynced))
     }
     
    -func TestPermissions(t *testing.T) {
    -	EnsureCleanState(t)
    -	appName := Name()
    -	_, err := RunCli("proj", "create", "test")
    -	assert.NoError(t, err)
    +// assertResourceActions verifies if view/modify resource actions are successful/failing for given application
    +func assertResourceActions(t *testing.T, appName string, successful bool) {
    +	assertError := func(err error, message string) {
    +		if successful {
    +			assert.NoError(t, err)
    +		} else {
    +			if assert.Error(t, err) {
    +				assert.Contains(t, err.Error(), message)
    +			}
    +		}
    +	}
     
    -	// make sure app cannot be created without permissions in project
    -	_, err = RunCli("app", "create", appName, "--repo", RepoURL(RepoURLTypeFile),
    -		"--path", guestbookPath, "--project", "test", "--dest-server", KubernetesInternalAPIServerAddr, "--dest-namespace", DeploymentNamespace())
    -	assert.Error(t, err)
    -	sourceError := fmt.Sprintf("application repo %s is not permitted in project 'test'", RepoURL(RepoURLTypeFile))
    -	destinationError := fmt.Sprintf("application destination {%s %s} is not permitted in project 'test'", KubernetesInternalAPIServerAddr, DeploymentNamespace())
    +	closer, cdClient := ArgoCDClientset.NewApplicationClientOrDie()
    +	defer io.Close(closer)
     
    -	assert.Contains(t, err.Error(), sourceError)
    -	assert.Contains(t, err.Error(), destinationError)
    +	deploymentResource, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{})
    +	require.NoError(t, err)
     
    -	proj, err := AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Get(context.Background(), "test", metav1.GetOptions{})
    -	assert.NoError(t, err)
    +	logs, err := cdClient.PodLogs(context.Background(), &applicationpkg.ApplicationPodLogsQuery{
    +		Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Name: &appName, Namespace: DeploymentNamespace(),
    +	})
    +	require.NoError(t, err)
    +	_, err = logs.Recv()
    +	assertError(err, "EOF")
     
    -	proj.Spec.Destinations = []ApplicationDestination{{Server: "*", Namespace: "*"}}
    -	proj.Spec.SourceRepos = []string{"*"}
    -	proj, err = AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Update(context.Background(), proj, metav1.UpdateOptions{})
    -	assert.NoError(t, err)
    +	expectedError := fmt.Sprintf("Deployment apps guestbook-ui not found as part of application %s", appName)
     
    -	// make sure controller report permissions issues in conditions
    -	_, err = RunCli("app", "create", appName, "--repo", RepoURL(RepoURLTypeFile),
    -		"--path", guestbookPath, "--project", "test", "--dest-server", KubernetesInternalAPIServerAddr, "--dest-namespace", DeploymentNamespace())
    -	assert.NoError(t, err)
    -	defer func() {
    -		err = AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).Delete(context.Background(), appName, metav1.DeleteOptions{})
    -		assert.NoError(t, err)
    -	}()
    +	_, err = cdClient.ListResourceEvents(context.Background(), &applicationpkg.ApplicationResourceEventsQuery{
    +		Name: &appName, ResourceName: "guestbook-ui", ResourceNamespace: DeploymentNamespace(), ResourceUID: string(deploymentResource.UID)})
    +	assertError(err, fmt.Sprintf("%s not found as part of application %s", "guestbook-ui", appName))
     
    -	proj.Spec.Destinations = []ApplicationDestination{}
    -	proj.Spec.SourceRepos = []string{}
    -	_, err = AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Update(context.Background(), proj, metav1.UpdateOptions{})
    -	assert.NoError(t, err)
    -	time.Sleep(1 * time.Second)
    -	closer, client, err := ArgoCDClientset.NewApplicationClient()
    -	assert.NoError(t, err)
    -	defer io.Close(closer)
    +	_, err = cdClient.GetResource(context.Background(), &applicationpkg.ApplicationResourceRequest{
    +		Name: &appName, ResourceName: "guestbook-ui", Namespace: DeploymentNamespace(), Version: "v1", Group: "apps", Kind: "Deployment"})
    +	assertError(err, expectedError)
     
    -	refresh := string(RefreshTypeNormal)
    -	app, err := client.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName, Refresh: &refresh})
    -	assert.NoError(t, err)
    +	_, err = cdClient.DeleteResource(context.Background(), &applicationpkg.ApplicationResourceDeleteRequest{
    +		Name: &appName, ResourceName: "guestbook-ui", Namespace: DeploymentNamespace(), Version: "v1", Group: "apps", Kind: "Deployment",
    +	})
    +	assertError(err, expectedError)
     
    -	destinationErrorExist := false
    -	sourceErrorExist := false
    -	for i := range app.Status.Conditions {
    -		if strings.Contains(app.Status.Conditions[i].Message, destinationError) {
    -			destinationErrorExist = true
    -		}
    -		if strings.Contains(app.Status.Conditions[i].Message, sourceError) {
    -			sourceErrorExist = true
    -		}
    -	}
    -	assert.True(t, destinationErrorExist)
    -	assert.True(t, sourceErrorExist)
    +	_, err = cdClient.RunResourceAction(context.Background(), &applicationpkg.ResourceActionRunRequest{
    +		Name: &appName, ResourceName: "guestbook-ui", Namespace: DeploymentNamespace(), Version: "v1", Group: "apps", Kind: "Deployment", Action: "restart",
    +	})
    +	assertError(err, expectedError)
    +}
    +
    +func TestPermissions(t *testing.T) {
    +	appCtx := Given(t)
    +	projName := "argo-project"
    +	projActions := projectFixture.
    +		Given(t).
    +		Name(projName).
    +		When().
    +		Create()
    +
    +	sourceError := fmt.Sprintf("application repo %s is not permitted in project 'argo-project'", RepoURL(RepoURLTypeFile))
    +	destinationError := fmt.Sprintf("application destination {%s %s} is not permitted in project 'argo-project'", KubernetesInternalAPIServerAddr, DeploymentNamespace())
    +
    +	appCtx.
    +		Path("guestbook-logs").
    +		Project(projName).
    +		When().
    +		IgnoreErrors().
    +		// ensure app is not created if project permissions are missing
    +		CreateApp().
    +		Then().
    +		Expect(Error("", sourceError)).
    +		Expect(Error("", destinationError)).
    +		When().
    +		DoNotIgnoreErrors().
    +		// add missing permissions, create and sync app
    +		And(func() {
    +			projActions.AddDestination("*", "*")
    +			projActions.AddSource("*")
    +		}).
    +		CreateApp().
    +		Sync().
    +		Then().
    +		// make sure application resource actiions are successful
    +		And(func(app *Application) {
    +			assertResourceActions(t, app.Name, true)
    +		}).
    +		When().
    +		// remove projet permissions and "refresh" app
    +		And(func() {
    +			projActions.UpdateProject(func(proj *AppProject) {
    +				proj.Spec.Destinations = nil
    +				proj.Spec.SourceRepos = nil
    +			})
    +		}).
    +		Refresh(RefreshTypeNormal).
    +		Then().
    +		// ensure app resource tree is empty when source/destination permissions are missing
    +		Expect(Condition(ApplicationConditionInvalidSpecError, destinationError)).
    +		Expect(Condition(ApplicationConditionInvalidSpecError, sourceError)).
    +		And(func(app *Application) {
    +			closer, cdClient := ArgoCDClientset.NewApplicationClientOrDie()
    +			defer io.Close(closer)
    +			tree, err := cdClient.ResourceTree(context.Background(), &applicationpkg.ResourcesQuery{ApplicationName: &app.Name})
    +			require.NoError(t, err)
    +			assert.Len(t, tree.Nodes, 0)
    +			assert.Len(t, tree.OrphanedNodes, 0)
    +		}).
    +		When().
    +		// add missing permissions but deny management of Deployment kind
    +		And(func() {
    +			projActions.
    +				AddDestination("*", "*").
    +				AddSource("*").
    +				UpdateProject(func(proj *AppProject) {
    +					proj.Spec.NamespaceResourceBlacklist = []metav1.GroupKind{{Group: "*", Kind: "Deployment"}}
    +				})
    +		}).
    +		Refresh(RefreshTypeNormal).
    +		Then().
    +		// make sure application resource actiions are failing
    +		And(func(app *Application) {
    +			assertResourceActions(t, "test-permissions", false)
    +		})
     }
     
     func TestPermissionWithScopedRepo(t *testing.T) {
    
  • test/e2e/fixture/project/actions.go+27 0 modified
    @@ -1,7 +1,12 @@
     package project
     
     import (
    +	"context"
    +
    +	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
     	"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
    +	"github.com/stretchr/testify/require"
    +	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     )
     
     // this implements the "when" part of given/when/then
    @@ -34,6 +39,25 @@ func (a *Actions) Create(args ...string) *Actions {
     	return a
     }
     
    +func (a *Actions) AddDestination(cluster string, namespace string) *Actions {
    +	a.runCli("proj", "add-destination", a.context.name, cluster, namespace)
    +	return a
    +}
    +
    +func (a *Actions) AddSource(repo string) *Actions {
    +	a.runCli("proj", "add-source", a.context.name, repo)
    +	return a
    +}
    +
    +func (a *Actions) UpdateProject(updater func(project *v1alpha1.AppProject)) *Actions {
    +	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Get(context.TODO(), a.context.name, v1.GetOptions{})
    +	require.NoError(a.context.t, err)
    +	updater(proj)
    +	_, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Update(context.TODO(), proj, v1.UpdateOptions{})
    +	require.NoError(a.context.t, err)
    +	return a
    +}
    +
     func (a *Actions) Name(name string) *Actions {
     	a.context.name = name
     	return a
    @@ -72,4 +96,7 @@ func (a *Actions) Then() *Consequences {
     func (a *Actions) runCli(args ...string) {
     	a.context.t.Helper()
     	a.lastOutput, a.lastError = fixture.RunCli(args...)
    +	if !a.ignoreErrors {
    +		require.Empty(a.context.t, a.lastError)
    +	}
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

10

News mentions

0

No linked articles in our index yet.