Improper access control allows admin privilege escalation in Argo CD
Description
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. All unpatched versions of Argo CD starting with 1.0.0 are vulnerable to an improper access control bug, allowing a malicious user to potentially escalate their privileges to admin-level. Versions starting with 0.8.0 and 0.5.0 contain limited versions of this issue. To perform exploits, an authorized Argo CD user must have push access to an Application's source git or Helm repository or sync and override access to an Application. Once a user has that access, different exploitation levels are possible depending on their other RBAC privileges. A patch for this vulnerability has been released in Argo CD versions 2.3.2, 2.2.8, and 2.1.14. Some mitigation measures are available but do not serve as a substitute for upgrading. To avoid privilege escalation, limit who has push access to Application source repositories or sync + override access to Applications; and limit which repositories are available in projects where users have update access to Applications. To avoid unauthorized resource inspection/tampering, limit who has delete, get, or action access to Applications.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/argoproj/argo-cdGo | >= 0.5.0, < 2.1.14 | 2.1.14 |
github.com/argoproj/argo-cdGo | >= 2.2.0, < 2.2.8 | 2.2.8 |
github.com/argoproj/argo-cdGo | >= 2.3.0, < 2.3.2 | 2.3.2 |
Affected products
1Patches
1af03b291d4b7Merge pull request from GHSA-2f5v-8r3f-8pww
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 by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-2f5v-8r3f-8pwwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-24768ghsaADVISORY
- github.com/argoproj/argo-cd/commit/af03b291d4b7e9d3ce9a6580ae9c8141af0e05cfghsax_refsource_MISCWEB
- github.com/argoproj/argo-cd/releases/tag/v2.1.14ghsax_refsource_MISCWEB
- github.com/argoproj/argo-cd/releases/tag/v2.2.8ghsax_refsource_MISCWEB
- github.com/argoproj/argo-cd/releases/tag/v2.3.2ghsax_refsource_MISCWEB
- github.com/argoproj/argo-cd/security/advisories/GHSA-2f5v-8r3f-8pwwghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.