VYPR
Moderate severityNVD Advisory· Published Apr 15, 2024· Updated Aug 2, 2024

Argo CD' API server does not enforce project sourceNamespaces

CVE-2024-31990

Description

Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. The API server does not enforce project sourceNamespaces which allows attackers to use the UI to edit resources which should only be mutable via gitops. This vulenrability is fixed in 2.10.7, 2.9.12, and 2.8.16.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/argoproj/argo-cd/v2Go
>= 2.4.0, < 2.8.162.8.16
github.com/argoproj/argo-cd/v2Go
>= 2.9.0, < 2.9.122.9.12
github.com/argoproj/argo-cd/v2Go
>= 2.10.0, < 2.10.72.10.7

Affected products

1

Patches

3
e0ff56d89fbd

Merge pull request from GHSA-2gvw-w6fj-7m3c

https://github.com/argoproj/argo-cdpasha-codefreshApr 15, 2024via ghsa
5 files changed · +223 103
  • pkg/apis/application/v1alpha1/app_project_types.go+18 0 modified
    @@ -17,6 +17,24 @@ import (
     	"k8s.io/apimachinery/pkg/runtime/schema"
     )
     
    +type ErrApplicationNotAllowedToUseProject struct {
    +	application string
    +	namespace   string
    +	project     string
    +}
    +
    +func NewErrApplicationNotAllowedToUseProject(application, namespace, project string) error {
    +	return &ErrApplicationNotAllowedToUseProject{
    +		application: application,
    +		namespace:   namespace,
    +		project:     project,
    +	}
    +}
    +
    +func (err *ErrApplicationNotAllowedToUseProject) Error() string {
    +	return fmt.Sprintf("application '%s' in namespace '%s' is not allowed to use project %s", err.application, err.namespace, err.project)
    +}
    +
     // AppProjectList is list of AppProject resources
     // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
     type AppProjectList struct {
    
  • server/application/application.go+89 96 modified
    @@ -148,7 +148,7 @@ func NewServer(
     //
     // If the user does provide a "project," we can respond more specifically. If the user does not have access to the given
     // app name in the given project, we return "permission denied." If the app exists, but the project is different from
    -func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, error) {
    +func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, *appv1.AppProject, error) {
     	logCtx := log.WithFields(map[string]interface{}{
     		"application": name,
     		"namespace":   namespace,
    @@ -165,23 +165,23 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
     			// but the app is in a different project" response. We don't want the user inferring the existence of the
     			// app from response time.
     			_, _ = getApp()
    -			return nil, permissionDeniedErr
    +			return nil, nil, permissionDeniedErr
     		}
     	}
     	a, err := getApp()
     	if err != nil {
     		if apierr.IsNotFound(err) {
     			if project != "" {
     				// We know that the user was allowed to get the Application, but the Application does not exist. Return 404.
    -				return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
    +				return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
     			}
     			// We don't know if the user was allowed to get the Application, and we don't want to leak information about
     			// the Application's existence. Return 403.
     			logCtx.Warn("application does not exist")
    -			return nil, permissionDeniedErr
    +			return nil, nil, permissionDeniedErr
     		}
     		logCtx.Errorf("failed to get application: %s", err)
    -		return nil, permissionDeniedErr
    +		return nil, nil, permissionDeniedErr
     	}
     	// Even if we performed an initial RBAC check (because the request was fully parameterized), we still need to
     	// perform a second RBAC check to ensure that the user has access to the actual Application's project (not just the
    @@ -195,11 +195,11 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
     			// The user specified a project. We would have returned a 404 if the user had access to the app, but the app
     			// did not exist. So we have to return a 404 when the app does exist, but the user does not have access.
     			// Otherwise, they could infer that the app exists based on the error code.
    -			return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
    +			return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
     		}
     		// The user didn't specify a project. We always return permission denied for both lack of access and lack of
     		// existence.
    -		return nil, permissionDeniedErr
    +		return nil, nil, permissionDeniedErr
     	}
     	effectiveProject := "default"
     	if a.Spec.Project != "" {
    @@ -212,15 +212,20 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
     		}).Warnf("user tried to %s application in project %s, but the application is in project %s", action, project, effectiveProject)
     		// The user has access to the app, but the app is in a different project. Return 404, meaning "app doesn't
     		// exist in that project".
    -		return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
    +		return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
     	}
    -	return a, nil
    +	// Get the app's associated project, and make sure all project restrictions are enforced.
    +	proj, err := s.getAppProject(ctx, a, logCtx)
    +	if err != nil {
    +		return a, nil, err
    +	}
    +	return a, proj, nil
     }
     
     // getApplicationEnforceRBACInformer uses an informer to get an Application. If the app does not exist, permission is
     // denied, or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
     // information.
    -func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, error) {
    +func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, *appv1.AppProject, error) {
     	namespaceOrDefault := s.appNamespaceOrDefault(namespace)
     	return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
     		return s.appLister.Applications(namespaceOrDefault).Get(name)
    @@ -230,7 +235,7 @@ func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action,
     // getApplicationEnforceRBACClient uses a client to get an Application. If the app does not exist, permission is denied,
     // or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
     // information.
    -func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, error) {
    +func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, *appv1.AppProject, error) {
     	namespaceOrDefault := s.appNamespaceOrDefault(namespace)
     	return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
     		if !s.isNamespaceEnabled(namespaceOrDefault) {
    @@ -314,7 +319,13 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
     	if q.Validate != nil {
     		validate = *q.Validate
     	}
    -	err := s.validateAndNormalizeApp(ctx, a, validate)
    +
    +	proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	err = s.validateAndNormalizeApp(ctx, a, proj, validate)
     	if err != nil {
     		return nil, fmt.Errorf("error while validating and normalizing app: %w", err)
     	}
    @@ -370,7 +381,7 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
     	return updated, nil
     }
     
    -func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, action func(
    +func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, proj *appv1.AppProject, action func(
     	client apiclient.RepoServerServiceClient,
     	repo *appv1.Repository,
     	helmRepos []*appv1.Repository,
    @@ -397,13 +408,6 @@ func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, acti
     	if err != nil {
     		return fmt.Errorf("error getting kustomize settings options: %w", err)
     	}
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
    -		}
    -		return fmt.Errorf("error getting application's project: %w", err)
    -	}
     
     	helmRepos, err := s.db.ListHelmRepositories(ctx)
     	if err != nil {
    @@ -438,7 +442,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
     	if q.Name == nil || *q.Name == "" {
     		return nil, fmt.Errorf("invalid request: application name is missing")
     	}
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -450,7 +454,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
     	}
     
     	var manifestInfo *apiclient.ManifestResponse
    -	err = s.queryRepoServer(ctx, a, func(
    +	err = s.queryRepoServer(ctx, a, proj, func(
     		client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
     		revision := source.TargetRevision
     		if q.GetRevision() != "" {
    @@ -476,11 +480,6 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
     			return fmt.Errorf("error getting API resources: %w", err)
     		}
     
    -		proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -		if err != nil {
    -			return fmt.Errorf("error getting app project: %w", err)
    -		}
    -
     		manifestInfo, err = client.GenerateManifest(ctx, &apiclient.ManifestRequest{
     			Repo:               repo,
     			Revision:           revision,
    @@ -543,13 +542,13 @@ func (s *Server) GetManifestsWithFiles(stream application.ApplicationService_Get
     		return fmt.Errorf("invalid request: application name is missing")
     	}
     
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
    +	a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
     	if err != nil {
     		return err
     	}
     
     	var manifestInfo *apiclient.ManifestResponse
    -	err = s.queryRepoServer(ctx, a, func(
    +	err = s.queryRepoServer(ctx, a, proj, func(
     		client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
     
     		appInstanceLabelKey, err := s.settingsMgr.GetAppInstanceLabelKey()
    @@ -660,7 +659,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
     	// We must use a client Get instead of an informer Get, because it's common to call Get immediately
     	// following a Watch (which is not yet powered by an informer), and the Get must reflect what was
     	// previously seen by the client.
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
     	if err != nil {
     		return nil, err
     	}
    @@ -691,7 +690,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
     
     	if refreshType == appv1.RefreshTypeHard {
     		// force refresh cached application details
    -		if err := s.queryRepoServer(ctx, a, func(
    +		if err := s.queryRepoServer(ctx, a, proj, func(
     			client apiclient.RepoServerServiceClient,
     			repo *appv1.Repository,
     			helmRepos []*appv1.Repository,
    @@ -743,7 +742,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
     
     // ListResourceEvents returns a list of event resources
     func (s *Server) ListResourceEvents(ctx context.Context, q *application.ApplicationResourceEventsQuery) (*v1.EventList, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -811,12 +810,12 @@ func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Applica
     	s.projectLock.RLock(newApp.Spec.GetProject())
     	defer s.projectLock.RUnlock(newApp.Spec.GetProject())
     
    -	app, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
    +	app, proj, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
     	if err != nil {
     		return nil, err
     	}
     
    -	err = s.validateAndNormalizeApp(ctx, newApp, validate)
    +	err = s.validateAndNormalizeApp(ctx, newApp, proj, validate)
     	if err != nil {
     		return nil, fmt.Errorf("error validating and normalizing app: %w", err)
     	}
    @@ -915,7 +914,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
     	if q.GetSpec() == nil {
     		return nil, fmt.Errorf("error updating application spec: spec is nil in request")
     	}
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -934,7 +933,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
     
     // Patch patches an application
     func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchRequest) (*appv1.Application, error) {
    -	app, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
    +	app, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -977,11 +976,35 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
     	return s.validateAndUpdateApp(ctx, newApp, false, true, rbacpolicy.ActionUpdate, q.GetProject())
     }
     
    +func (s *Server) getAppProject(ctx context.Context, a *appv1.Application, logCtx *log.Entry) (*appv1.AppProject, error) {
    +	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    +	if err == nil {
    +		return proj, nil
    +	}
    +
    +	// If there's a permission issue or the app doesn't exist, return a vague error to avoid letting the user enumerate project names.
    +	vagueError := status.Errorf(codes.InvalidArgument, "app is not allowed in project %q, or the project does not exist", a.Spec.Project)
    +
    +	if apierr.IsNotFound(err) {
    +		return nil, vagueError
    +	}
    +
    +	if _, ok := err.(*appv1.ErrApplicationNotAllowedToUseProject); ok {
    +		logCtx.WithFields(map[string]interface{}{
    +			"project":                a.Spec.Project,
    +			argocommon.SecurityField: argocommon.SecurityMedium,
    +		}).Warnf("error getting app project: %s", err)
    +		return nil, vagueError
    +	}
    +
    +	return nil, vagueError
    +}
    +
     // Delete removes an application and all associated resources
     func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteRequest) (*application.ApplicationResponse, error) {
     	appName := q.GetName()
     	appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
     	if err != nil {
     		return nil, err
     	}
    @@ -1136,16 +1159,7 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
     	}
     }
     
    -func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, validate bool) error {
    -	proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			// Offer no hint that the project does not exist.
    -			log.Warnf("User attempted to create/update application in non-existent project %q", app.Spec.Project)
    -			return permissionDeniedErr
    -		}
    -		return fmt.Errorf("error getting application's project: %w", err)
    -	}
    +func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, validate bool) error {
     	if app.GetName() == "" {
     		return fmt.Errorf("resource name may not be empty")
     	}
    @@ -1249,7 +1263,7 @@ func (s *Server) getAppResources(ctx context.Context, a *appv1.Application) (*ap
     }
     
     func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, nil, nil, err
     	}
    @@ -1386,7 +1400,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
     }
     
     func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery) (*appv1.ApplicationTree, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1395,7 +1409,7 @@ func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery
     }
     
     func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
    -	_, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
    +	_, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
     	if err != nil {
     		return err
     	}
    @@ -1412,7 +1426,7 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
     }
     
     func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.RevisionMetadata, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1422,12 +1436,6 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
     	if err != nil {
     		return nil, fmt.Errorf("error getting repository by URL: %w", err)
     	}
    -	// We need to get some information with the project associated to the app,
    -	// so we'll know whether GPG signatures are enforced.
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		return nil, fmt.Errorf("error getting app project: %w", err)
    -	}
     	conn, repoClient, err := s.repoClientset.NewRepoServerClient()
     	if err != nil {
     		return nil, fmt.Errorf("error creating repo server client: %w", err)
    @@ -1442,7 +1450,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
     
     // RevisionChartDetails returns the helm chart metadata, as fetched from the reposerver
     func (s *Server) RevisionChartDetails(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.ChartDetails, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1473,7 +1481,7 @@ func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) boo
     }
     
     func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQuery) (*application.ManagedResourcesResponse, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1530,7 +1538,7 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
     		}
     	}
     
    -	a, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return err
     	}
    @@ -1722,19 +1730,11 @@ func isTheSelectedOne(currentNode *appv1.ResourceNode, q *application.Applicatio
     
     // Sync syncs an application to its target state
     func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncRequest) (*appv1.Application, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
     
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			return a, status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
    -		}
    -		return a, fmt.Errorf("error getting app project: %w", err)
    -	}
    -
     	s.inferResourcesStatusHealth(a)
     
     	if !proj.Spec.SyncWindows.Matches(a).CanSync(true) {
    @@ -1831,7 +1831,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
     }
     
     func (s *Server) Rollback(ctx context.Context, rollbackReq *application.ApplicationRollbackRequest) (*appv1.Application, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -1890,7 +1890,7 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
     }
     
     func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksRequest) (*application.LinksResponse, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -1905,7 +1905,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
     		return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
     	}
     
    -	clstObj, _, err := s.getObjectsForDeepLinks(ctx, a)
    +	clstObj, _, err := s.getObjectsForDeepLinks(ctx, a, proj)
     	if err != nil {
     		return nil, err
     	}
    @@ -1920,12 +1920,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
     	return finalList, nil
     }
     
    -func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
    -	proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		return nil, nil, fmt.Errorf("error getting app project: %w", err)
    -	}
    -
    +func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application, proj *appv1.AppProject) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
     	// sanitize project jwt tokens
     	proj.Status = appv1.AppProjectStatus{}
     
    @@ -1988,7 +1983,12 @@ func (s *Server) ListResourceLinks(ctx context.Context, req *application.Applica
     		return nil, err
     	}
     
    -	clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app)
    +	proj, err := s.getAppProject(ctx, app, log.WithField("application", app.GetName()))
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app, proj)
     	if err != nil {
     		return nil, err
     	}
    @@ -2044,7 +2044,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
     func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.OperationTerminateRequest) (*application.OperationTerminateResponse, error) {
     	appName := termOpReq.GetName()
     	appNs := s.appNamespaceOrDefault(termOpReq.GetAppNamespace())
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
     	if err != nil {
     		return nil, err
     	}
    @@ -2117,7 +2117,7 @@ func (s *Server) ListResourceActions(ctx context.Context, q *application.Applica
     
     func (s *Server) getUnstructuredLiveResourceOrApp(ctx context.Context, rbacRequest string, q *application.ApplicationResourceRequest) (obj *unstructured.Unstructured, res *appv1.ResourceNode, app *appv1.Application, config *rest.Config, err error) {
     	if q.GetKind() == applicationType.ApplicationKind && q.GetGroup() == applicationType.Group && q.GetName() == q.GetResourceName() {
    -		app, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +		app, _, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
     		if err != nil {
     			return nil, nil, nil, nil, err
     		}
    @@ -2213,14 +2213,19 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
     		}
     	}
     
    +	proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
    +	if err != nil {
    +		return nil, err
    +	}
    +
     	// First, make sure all the returned resources are permitted, for each operation.
     	// Also perform create with dry-runs for all create-operation resources.
     	// This is performed separately to reduce the risk of only some of the resources being successfully created later.
     	// TODO: when apply/delete operations would be supported for custom actions,
     	// the dry-run for relevant apply/delete operation would have to be invoked as well.
     	for _, impactedResource := range newObjects {
     		newObj := impactedResource.UnstructuredObj
    -		err := s.verifyResourcePermitted(ctx, app, newObj)
    +		err := s.verifyResourcePermitted(ctx, app, proj, newObj)
     		if err != nil {
     			return nil, err
     		}
    @@ -2314,14 +2319,7 @@ func (s *Server) patchResource(ctx context.Context, config *rest.Config, liveObj
     	return &application.ApplicationResponse{}, nil
     }
     
    -func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, obj *unstructured.Unstructured) error {
    -	proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			return fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
    -		}
    -		return fmt.Errorf("failed to get project %s: %w", app.Spec.Project, err)
    -	}
    +func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, obj *unstructured.Unstructured) error {
     	permitted, err := proj.IsResourcePermitted(schema.GroupKind{Group: obj.GroupVersionKind().Group, Kind: obj.GroupVersionKind().Kind}, obj.GetNamespace(), app.Spec.Destination, func(project string) ([]*appv1.Cluster, error) {
     		clusters, err := s.db.GetProjectClusters(context.TODO(), project)
     		if err != nil {
    @@ -2381,16 +2379,11 @@ func splitStatusPatch(patch []byte) ([]byte, []byte, error) {
     }
     
     func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.ApplicationSyncWindowsQuery) (*application.ApplicationSyncWindowsResponse, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
     
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		return nil, fmt.Errorf("error getting app project: %w", err)
    -	}
    -
     	windows := proj.Spec.SyncWindows.Matches(a)
     	sync := windows.CanSync(true)
     
    
  • server/application/application_test.go+114 4 modified
    @@ -1818,7 +1818,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
     		appServer := newTestAppServer(t, testApp)
     
     		active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
    -		assert.Contains(t, err.Error(), "not found")
    +		assert.Contains(t, err.Error(), "not exist")
     		assert.Nil(t, active)
     	})
     }
    @@ -2428,7 +2428,16 @@ func TestAppNamespaceRestrictions(t *testing.T) {
     	t.Run("Get application in other namespace when allowed", func(t *testing.T) {
     		testApp := newTestApp()
     		testApp.Namespace = "argocd-1"
    -		appServer := newTestAppServer(t, testApp)
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
     		appServer.enabledNamespaces = []string{"argocd-1"}
     		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
     			Name:         pointer.String("test-app"),
    @@ -2439,6 +2448,28 @@ func TestAppNamespaceRestrictions(t *testing.T) {
     		require.Equal(t, "argocd-1", app.Namespace)
     		require.Equal(t, "test-app", app.Name)
     	})
    +	t.Run("Get application in other namespace when project is not allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-2"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
    +			Name:         pointer.String("test-app"),
    +			AppNamespace: pointer.String("argocd-1"),
    +		})
    +		require.Error(t, err)
    +		require.Nil(t, app)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
     	t.Run("Create application in other namespace when allowed", func(t *testing.T) {
     		testApp := newTestApp()
     		testApp.Namespace = "argocd-1"
    @@ -2481,7 +2512,7 @@ func TestAppNamespaceRestrictions(t *testing.T) {
     		})
     		require.Error(t, err)
     		require.Nil(t, app)
    -		require.ErrorContains(t, err, "not allowed to use project")
    +		require.ErrorContains(t, err, "app is not allowed in project")
     	})
     
     	t.Run("Create application in other namespace when not allowed by configuration", func(t *testing.T) {
    @@ -2505,5 +2536,84 @@ func TestAppNamespaceRestrictions(t *testing.T) {
     		require.Nil(t, app)
     		require.ErrorContains(t, err, "namespace 'argocd-1' is not permitted")
     	})
    -
    +	t.Run("Get application sync window in other namespace when project is allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
    +		assert.NoError(t, err)
    +		assert.Equal(t, 0, len(active.ActiveWindows))
    +	})
    +	t.Run("Get application sync window in other namespace when project is not allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-2"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
    +		require.Error(t, err)
    +		require.Nil(t, active)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
    +	t.Run("Get list of links in other namespace when project is not allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-2"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
    +			Name:      pointer.String("test-app"),
    +			Namespace: pointer.String("argocd-1"),
    +		})
    +		require.Error(t, err)
    +		require.Nil(t, links)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
    +	t.Run("Get list of links in other namespace when project is allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
    +			Name:      pointer.String("test-app"),
    +			Namespace: pointer.String("argocd-1"),
    +		})
    +		require.NoError(t, err)
    +		assert.Equal(t, 0, len(links.Items))
    +	})
     }
    
  • util/argo/argo.go+1 2 modified
    @@ -694,8 +694,7 @@ func GetAppProject(app *argoappv1.Application, projLister applicationsv1.AppProj
     		return nil, err
     	}
     	if !proj.IsAppNamespacePermitted(app, ns) {
    -		return nil, fmt.Errorf("application '%s' in namespace '%s' is not allowed to use project '%s'",
    -			app.Name, app.Namespace, proj.Name)
    +		return nil, argoappv1.NewErrApplicationNotAllowedToUseProject(app.Name, app.Namespace, proj.Name)
     	}
     	return proj, nil
     }
    
  • util/session/sessionmanager.go+1 1 modified
    @@ -312,7 +312,7 @@ func expireOldFailedAttempts(maxAge time.Duration, failures map[string]LoginAtte
     	return expiredCount
     }
     
    -// Protect admin user from login attempt reset caused by attempts to overflow cache in a brute force attack. Instead remove random non-admin to make room in cache. 
    +// Protect admin user from login attempt reset caused by attempts to overflow cache in a brute force attack. Instead remove random non-admin to make room in cache.
     func pickRandomNonAdminLoginFailure(failures map[string]LoginAttempts, username string) *string {
     	idx := rand.Intn(len(failures) - 1)
     	i := 0
    
c514105af739

Merge pull request from GHSA-2gvw-w6fj-7m3c

https://github.com/argoproj/argo-cdpasha-codefreshApr 15, 2024via ghsa
5 files changed · +223 103
  • pkg/apis/application/v1alpha1/app_project_types.go+18 0 modified
    @@ -17,6 +17,24 @@ import (
     	"k8s.io/apimachinery/pkg/runtime/schema"
     )
     
    +type ErrApplicationNotAllowedToUseProject struct {
    +	application string
    +	namespace   string
    +	project     string
    +}
    +
    +func NewErrApplicationNotAllowedToUseProject(application, namespace, project string) error {
    +	return &ErrApplicationNotAllowedToUseProject{
    +		application: application,
    +		namespace:   namespace,
    +		project:     project,
    +	}
    +}
    +
    +func (err *ErrApplicationNotAllowedToUseProject) Error() string {
    +	return fmt.Sprintf("application '%s' in namespace '%s' is not allowed to use project %s", err.application, err.namespace, err.project)
    +}
    +
     // AppProjectList is list of AppProject resources
     // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
     type AppProjectList struct {
    
  • server/application/application.go+89 96 modified
    @@ -151,7 +151,7 @@ func NewServer(
     //
     // If the user does provide a "project," we can respond more specifically. If the user does not have access to the given
     // app name in the given project, we return "permission denied." If the app exists, but the project is different from
    -func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, error) {
    +func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, *appv1.AppProject, error) {
     	user := session.Username(ctx)
     	if user == "" {
     		user = "Unknown user"
    @@ -173,23 +173,23 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
     			// but the app is in a different project" response. We don't want the user inferring the existence of the
     			// app from response time.
     			_, _ = getApp()
    -			return nil, permissionDeniedErr
    +			return nil, nil, permissionDeniedErr
     		}
     	}
     	a, err := getApp()
     	if err != nil {
     		if apierr.IsNotFound(err) {
     			if project != "" {
     				// We know that the user was allowed to get the Application, but the Application does not exist. Return 404.
    -				return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
    +				return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
     			}
     			// We don't know if the user was allowed to get the Application, and we don't want to leak information about
     			// the Application's existence. Return 403.
     			logCtx.Warn("application does not exist")
    -			return nil, permissionDeniedErr
    +			return nil, nil, permissionDeniedErr
     		}
     		logCtx.Errorf("failed to get application: %s", err)
    -		return nil, permissionDeniedErr
    +		return nil, nil, permissionDeniedErr
     	}
     	// Even if we performed an initial RBAC check (because the request was fully parameterized), we still need to
     	// perform a second RBAC check to ensure that the user has access to the actual Application's project (not just the
    @@ -203,11 +203,11 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
     			// The user specified a project. We would have returned a 404 if the user had access to the app, but the app
     			// did not exist. So we have to return a 404 when the app does exist, but the user does not have access.
     			// Otherwise, they could infer that the app exists based on the error code.
    -			return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
    +			return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
     		}
     		// The user didn't specify a project. We always return permission denied for both lack of access and lack of
     		// existence.
    -		return nil, permissionDeniedErr
    +		return nil, nil, permissionDeniedErr
     	}
     	effectiveProject := "default"
     	if a.Spec.Project != "" {
    @@ -220,15 +220,20 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
     		}).Warnf("user tried to %s application in project %s, but the application is in project %s", action, project, effectiveProject)
     		// The user has access to the app, but the app is in a different project. Return 404, meaning "app doesn't
     		// exist in that project".
    -		return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
    +		return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
     	}
    -	return a, nil
    +	// Get the app's associated project, and make sure all project restrictions are enforced.
    +	proj, err := s.getAppProject(ctx, a, logCtx)
    +	if err != nil {
    +		return a, nil, err
    +	}
    +	return a, proj, nil
     }
     
     // getApplicationEnforceRBACInformer uses an informer to get an Application. If the app does not exist, permission is
     // denied, or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
     // information.
    -func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, error) {
    +func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, *appv1.AppProject, error) {
     	namespaceOrDefault := s.appNamespaceOrDefault(namespace)
     	return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
     		return s.appLister.Applications(namespaceOrDefault).Get(name)
    @@ -238,7 +243,7 @@ func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action,
     // getApplicationEnforceRBACClient uses a client to get an Application. If the app does not exist, permission is denied,
     // or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
     // information.
    -func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, error) {
    +func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, *appv1.AppProject, error) {
     	namespaceOrDefault := s.appNamespaceOrDefault(namespace)
     	return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
     		if !s.isNamespaceEnabled(namespaceOrDefault) {
    @@ -322,7 +327,13 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
     	if q.Validate != nil {
     		validate = *q.Validate
     	}
    -	err := s.validateAndNormalizeApp(ctx, a, validate)
    +
    +	proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	err = s.validateAndNormalizeApp(ctx, a, proj, validate)
     	if err != nil {
     		return nil, fmt.Errorf("error while validating and normalizing app: %w", err)
     	}
    @@ -378,7 +389,7 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
     	return updated, nil
     }
     
    -func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, action func(
    +func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, proj *appv1.AppProject, action func(
     	client apiclient.RepoServerServiceClient,
     	repo *appv1.Repository,
     	helmRepos []*appv1.Repository,
    @@ -405,13 +416,6 @@ func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, acti
     	if err != nil {
     		return fmt.Errorf("error getting kustomize settings options: %w", err)
     	}
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
    -		}
    -		return fmt.Errorf("error getting application's project: %w", err)
    -	}
     
     	helmRepos, err := s.db.ListHelmRepositories(ctx)
     	if err != nil {
    @@ -446,7 +450,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
     	if q.Name == nil || *q.Name == "" {
     		return nil, fmt.Errorf("invalid request: application name is missing")
     	}
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -458,7 +462,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
     	}
     
     	var manifestInfo *apiclient.ManifestResponse
    -	err = s.queryRepoServer(ctx, a, func(
    +	err = s.queryRepoServer(ctx, a, proj, func(
     		client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
     		revision := source.TargetRevision
     		if q.GetRevision() != "" {
    @@ -484,11 +488,6 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
     			return fmt.Errorf("error getting API resources: %w", err)
     		}
     
    -		proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -		if err != nil {
    -			return fmt.Errorf("error getting app project: %w", err)
    -		}
    -
     		manifestInfo, err = client.GenerateManifest(ctx, &apiclient.ManifestRequest{
     			Repo:               repo,
     			Revision:           revision,
    @@ -551,13 +550,13 @@ func (s *Server) GetManifestsWithFiles(stream application.ApplicationService_Get
     		return fmt.Errorf("invalid request: application name is missing")
     	}
     
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
    +	a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
     	if err != nil {
     		return err
     	}
     
     	var manifestInfo *apiclient.ManifestResponse
    -	err = s.queryRepoServer(ctx, a, func(
    +	err = s.queryRepoServer(ctx, a, proj, func(
     		client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
     
     		appInstanceLabelKey, err := s.settingsMgr.GetAppInstanceLabelKey()
    @@ -668,7 +667,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
     	// We must use a client Get instead of an informer Get, because it's common to call Get immediately
     	// following a Watch (which is not yet powered by an informer), and the Get must reflect what was
     	// previously seen by the client.
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
     	if err != nil {
     		return nil, err
     	}
    @@ -699,7 +698,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
     
     	if refreshType == appv1.RefreshTypeHard {
     		// force refresh cached application details
    -		if err := s.queryRepoServer(ctx, a, func(
    +		if err := s.queryRepoServer(ctx, a, proj, func(
     			client apiclient.RepoServerServiceClient,
     			repo *appv1.Repository,
     			helmRepos []*appv1.Repository,
    @@ -751,7 +750,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
     
     // ListResourceEvents returns a list of event resources
     func (s *Server) ListResourceEvents(ctx context.Context, q *application.ApplicationResourceEventsQuery) (*v1.EventList, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -819,12 +818,12 @@ func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Applica
     	s.projectLock.RLock(newApp.Spec.GetProject())
     	defer s.projectLock.RUnlock(newApp.Spec.GetProject())
     
    -	app, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
    +	app, proj, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
     	if err != nil {
     		return nil, err
     	}
     
    -	err = s.validateAndNormalizeApp(ctx, newApp, validate)
    +	err = s.validateAndNormalizeApp(ctx, newApp, proj, validate)
     	if err != nil {
     		return nil, fmt.Errorf("error validating and normalizing app: %w", err)
     	}
    @@ -923,7 +922,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
     	if q.GetSpec() == nil {
     		return nil, fmt.Errorf("error updating application spec: spec is nil in request")
     	}
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -942,7 +941,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
     
     // Patch patches an application
     func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchRequest) (*appv1.Application, error) {
    -	app, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
    +	app, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -985,11 +984,35 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
     	return s.validateAndUpdateApp(ctx, newApp, false, true, rbacpolicy.ActionUpdate, q.GetProject())
     }
     
    +func (s *Server) getAppProject(ctx context.Context, a *appv1.Application, logCtx *log.Entry) (*appv1.AppProject, error) {
    +	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    +	if err == nil {
    +		return proj, nil
    +	}
    +
    +	// If there's a permission issue or the app doesn't exist, return a vague error to avoid letting the user enumerate project names.
    +	vagueError := status.Errorf(codes.InvalidArgument, "app is not allowed in project %q, or the project does not exist", a.Spec.Project)
    +
    +	if apierr.IsNotFound(err) {
    +		return nil, vagueError
    +	}
    +
    +	if _, ok := err.(*appv1.ErrApplicationNotAllowedToUseProject); ok {
    +		logCtx.WithFields(map[string]interface{}{
    +			"project":                a.Spec.Project,
    +			argocommon.SecurityField: argocommon.SecurityMedium,
    +		}).Warnf("error getting app project: %s", err)
    +		return nil, vagueError
    +	}
    +
    +	return nil, vagueError
    +}
    +
     // Delete removes an application and all associated resources
     func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteRequest) (*application.ApplicationResponse, error) {
     	appName := q.GetName()
     	appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
     	if err != nil {
     		return nil, err
     	}
    @@ -1144,16 +1167,7 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
     	}
     }
     
    -func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, validate bool) error {
    -	proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			// Offer no hint that the project does not exist.
    -			log.Warnf("User attempted to create/update application in non-existent project %q", app.Spec.Project)
    -			return permissionDeniedErr
    -		}
    -		return fmt.Errorf("error getting application's project: %w", err)
    -	}
    +func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, validate bool) error {
     	if app.GetName() == "" {
     		return fmt.Errorf("resource name may not be empty")
     	}
    @@ -1257,7 +1271,7 @@ func (s *Server) getAppResources(ctx context.Context, a *appv1.Application) (*ap
     }
     
     func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, nil, nil, err
     	}
    @@ -1394,7 +1408,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
     }
     
     func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery) (*appv1.ApplicationTree, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1403,7 +1417,7 @@ func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery
     }
     
     func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
    -	_, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
    +	_, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
     	if err != nil {
     		return err
     	}
    @@ -1420,7 +1434,7 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
     }
     
     func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.RevisionMetadata, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1430,12 +1444,6 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
     	if err != nil {
     		return nil, fmt.Errorf("error getting repository by URL: %w", err)
     	}
    -	// We need to get some information with the project associated to the app,
    -	// so we'll know whether GPG signatures are enforced.
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		return nil, fmt.Errorf("error getting app project: %w", err)
    -	}
     	conn, repoClient, err := s.repoClientset.NewRepoServerClient()
     	if err != nil {
     		return nil, fmt.Errorf("error creating repo server client: %w", err)
    @@ -1450,7 +1458,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
     
     // RevisionChartDetails returns the helm chart metadata, as fetched from the reposerver
     func (s *Server) RevisionChartDetails(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.ChartDetails, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1481,7 +1489,7 @@ func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) boo
     }
     
     func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQuery) (*application.ManagedResourcesResponse, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1538,7 +1546,7 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
     		}
     	}
     
    -	a, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return err
     	}
    @@ -1730,19 +1738,11 @@ func isTheSelectedOne(currentNode *appv1.ResourceNode, q *application.Applicatio
     
     // Sync syncs an application to its target state
     func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncRequest) (*appv1.Application, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
     
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			return a, status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
    -		}
    -		return a, fmt.Errorf("error getting app project: %w", err)
    -	}
    -
     	s.inferResourcesStatusHealth(a)
     
     	if !proj.Spec.SyncWindows.Matches(a).CanSync(true) {
    @@ -1839,7 +1839,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
     }
     
     func (s *Server) Rollback(ctx context.Context, rollbackReq *application.ApplicationRollbackRequest) (*appv1.Application, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -1898,7 +1898,7 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
     }
     
     func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksRequest) (*application.LinksResponse, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -1913,7 +1913,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
     		return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
     	}
     
    -	clstObj, _, err := s.getObjectsForDeepLinks(ctx, a)
    +	clstObj, _, err := s.getObjectsForDeepLinks(ctx, a, proj)
     	if err != nil {
     		return nil, err
     	}
    @@ -1928,12 +1928,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
     	return finalList, nil
     }
     
    -func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
    -	proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		return nil, nil, fmt.Errorf("error getting app project: %w", err)
    -	}
    -
    +func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application, proj *appv1.AppProject) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
     	// sanitize project jwt tokens
     	proj.Status = appv1.AppProjectStatus{}
     
    @@ -1996,7 +1991,12 @@ func (s *Server) ListResourceLinks(ctx context.Context, req *application.Applica
     		return nil, err
     	}
     
    -	clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app)
    +	proj, err := s.getAppProject(ctx, app, log.WithField("application", app.GetName()))
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app, proj)
     	if err != nil {
     		return nil, err
     	}
    @@ -2052,7 +2052,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
     func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.OperationTerminateRequest) (*application.OperationTerminateResponse, error) {
     	appName := termOpReq.GetName()
     	appNs := s.appNamespaceOrDefault(termOpReq.GetAppNamespace())
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
     	if err != nil {
     		return nil, err
     	}
    @@ -2125,7 +2125,7 @@ func (s *Server) ListResourceActions(ctx context.Context, q *application.Applica
     
     func (s *Server) getUnstructuredLiveResourceOrApp(ctx context.Context, rbacRequest string, q *application.ApplicationResourceRequest) (obj *unstructured.Unstructured, res *appv1.ResourceNode, app *appv1.Application, config *rest.Config, err error) {
     	if q.GetKind() == applicationType.ApplicationKind && q.GetGroup() == applicationType.Group && q.GetName() == q.GetResourceName() {
    -		app, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +		app, _, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
     		if err != nil {
     			return nil, nil, nil, nil, err
     		}
    @@ -2221,14 +2221,19 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
     		}
     	}
     
    +	proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
    +	if err != nil {
    +		return nil, err
    +	}
    +
     	// First, make sure all the returned resources are permitted, for each operation.
     	// Also perform create with dry-runs for all create-operation resources.
     	// This is performed separately to reduce the risk of only some of the resources being successfully created later.
     	// TODO: when apply/delete operations would be supported for custom actions,
     	// the dry-run for relevant apply/delete operation would have to be invoked as well.
     	for _, impactedResource := range newObjects {
     		newObj := impactedResource.UnstructuredObj
    -		err := s.verifyResourcePermitted(ctx, app, newObj)
    +		err := s.verifyResourcePermitted(ctx, app, proj, newObj)
     		if err != nil {
     			return nil, err
     		}
    @@ -2322,14 +2327,7 @@ func (s *Server) patchResource(ctx context.Context, config *rest.Config, liveObj
     	return &application.ApplicationResponse{}, nil
     }
     
    -func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, obj *unstructured.Unstructured) error {
    -	proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			return fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
    -		}
    -		return fmt.Errorf("failed to get project %s: %w", app.Spec.Project, err)
    -	}
    +func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, obj *unstructured.Unstructured) error {
     	permitted, err := proj.IsResourcePermitted(schema.GroupKind{Group: obj.GroupVersionKind().Group, Kind: obj.GroupVersionKind().Kind}, obj.GetNamespace(), app.Spec.Destination, func(project string) ([]*appv1.Cluster, error) {
     		clusters, err := s.db.GetProjectClusters(context.TODO(), project)
     		if err != nil {
    @@ -2389,16 +2387,11 @@ func splitStatusPatch(patch []byte) ([]byte, []byte, error) {
     }
     
     func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.ApplicationSyncWindowsQuery) (*application.ApplicationSyncWindowsResponse, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
     
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		return nil, fmt.Errorf("error getting app project: %w", err)
    -	}
    -
     	windows := proj.Spec.SyncWindows.Matches(a)
     	sync := windows.CanSync(true)
     
    
  • server/application/application_test.go+114 4 modified
    @@ -1818,7 +1818,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
     		appServer := newTestAppServer(t, testApp)
     
     		active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
    -		assert.Contains(t, err.Error(), "not found")
    +		assert.Contains(t, err.Error(), "not exist")
     		assert.Nil(t, active)
     	})
     }
    @@ -2428,7 +2428,16 @@ func TestAppNamespaceRestrictions(t *testing.T) {
     	t.Run("Get application in other namespace when allowed", func(t *testing.T) {
     		testApp := newTestApp()
     		testApp.Namespace = "argocd-1"
    -		appServer := newTestAppServer(t, testApp)
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
     		appServer.enabledNamespaces = []string{"argocd-1"}
     		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
     			Name:         pointer.String("test-app"),
    @@ -2439,6 +2448,28 @@ func TestAppNamespaceRestrictions(t *testing.T) {
     		require.Equal(t, "argocd-1", app.Namespace)
     		require.Equal(t, "test-app", app.Name)
     	})
    +	t.Run("Get application in other namespace when project is not allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-2"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
    +			Name:         pointer.String("test-app"),
    +			AppNamespace: pointer.String("argocd-1"),
    +		})
    +		require.Error(t, err)
    +		require.Nil(t, app)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
     	t.Run("Create application in other namespace when allowed", func(t *testing.T) {
     		testApp := newTestApp()
     		testApp.Namespace = "argocd-1"
    @@ -2481,7 +2512,7 @@ func TestAppNamespaceRestrictions(t *testing.T) {
     		})
     		require.Error(t, err)
     		require.Nil(t, app)
    -		require.ErrorContains(t, err, "not allowed to use project")
    +		require.ErrorContains(t, err, "app is not allowed in project")
     	})
     
     	t.Run("Create application in other namespace when not allowed by configuration", func(t *testing.T) {
    @@ -2505,5 +2536,84 @@ func TestAppNamespaceRestrictions(t *testing.T) {
     		require.Nil(t, app)
     		require.ErrorContains(t, err, "namespace 'argocd-1' is not permitted")
     	})
    -
    +	t.Run("Get application sync window in other namespace when project is allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
    +		assert.NoError(t, err)
    +		assert.Equal(t, 0, len(active.ActiveWindows))
    +	})
    +	t.Run("Get application sync window in other namespace when project is not allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-2"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
    +		require.Error(t, err)
    +		require.Nil(t, active)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
    +	t.Run("Get list of links in other namespace when project is not allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-2"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
    +			Name:      pointer.String("test-app"),
    +			Namespace: pointer.String("argocd-1"),
    +		})
    +		require.Error(t, err)
    +		require.Nil(t, links)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
    +	t.Run("Get list of links in other namespace when project is allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
    +			Name:      pointer.String("test-app"),
    +			Namespace: pointer.String("argocd-1"),
    +		})
    +		require.NoError(t, err)
    +		assert.Equal(t, 0, len(links.Items))
    +	})
     }
    
  • util/argo/argo.go+1 2 modified
    @@ -694,8 +694,7 @@ func GetAppProject(app *argoappv1.Application, projLister applicationsv1.AppProj
     		return nil, err
     	}
     	if !proj.IsAppNamespacePermitted(app, ns) {
    -		return nil, fmt.Errorf("application '%s' in namespace '%s' is not allowed to use project '%s'",
    -			app.Name, app.Namespace, proj.Name)
    +		return nil, argoappv1.NewErrApplicationNotAllowedToUseProject(app.Name, app.Namespace, proj.Name)
     	}
     	return proj, nil
     }
    
  • util/session/sessionmanager.go+1 1 modified
    @@ -312,7 +312,7 @@ func expireOldFailedAttempts(maxAge time.Duration, failures map[string]LoginAtte
     	return expiredCount
     }
     
    -// Protect admin user from login attempt reset caused by attempts to overflow cache in a brute force attack. Instead remove random non-admin to make room in cache. 
    +// Protect admin user from login attempt reset caused by attempts to overflow cache in a brute force attack. Instead remove random non-admin to make room in cache.
     func pickRandomNonAdminLoginFailure(failures map[string]LoginAttempts, username string) *string {
     	idx := rand.Intn(len(failures) - 1)
     	i := 0
    
c5a252c4cc26

Merge pull request from GHSA-2gvw-w6fj-7m3c

https://github.com/argoproj/argo-cdpasha-codefreshApr 15, 2024via ghsa
4 files changed · +364 95
  • pkg/apis/application/v1alpha1/app_project_types.go+18 0 modified
    @@ -17,6 +17,24 @@ import (
     	"k8s.io/apimachinery/pkg/runtime/schema"
     )
     
    +type ErrApplicationNotAllowedToUseProject struct {
    +	application string
    +	namespace   string
    +	project     string
    +}
    +
    +func NewErrApplicationNotAllowedToUseProject(application, namespace, project string) error {
    +	return &ErrApplicationNotAllowedToUseProject{
    +		application: application,
    +		namespace:   namespace,
    +		project:     project,
    +	}
    +}
    +
    +func (err *ErrApplicationNotAllowedToUseProject) Error() string {
    +	return fmt.Sprintf("application '%s' in namespace '%s' is not allowed to use project %s", err.application, err.namespace, err.project)
    +}
    +
     // AppProjectList is list of AppProject resources
     // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
     type AppProjectList struct {
    
  • server/application/application.go+92 92 modified
    @@ -147,7 +147,7 @@ func NewServer(
     //
     // If the user does provide a "project," we can respond more specifically. If the user does not have access to the given
     // app name in the given project, we return "permission denied." If the app exists, but the project is different from
    -func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, error) {
    +func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, *appv1.AppProject, error) {
     	logCtx := log.WithFields(map[string]interface{}{
     		"application": name,
     		"namespace":   namespace,
    @@ -164,23 +164,23 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
     			// but the app is in a different project" response. We don't want the user inferring the existence of the
     			// app from response time.
     			_, _ = getApp()
    -			return nil, permissionDeniedErr
    +			return nil, nil, permissionDeniedErr
     		}
     	}
     	a, err := getApp()
     	if err != nil {
     		if apierr.IsNotFound(err) {
     			if project != "" {
     				// We know that the user was allowed to get the Application, but the Application does not exist. Return 404.
    -				return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
    +				return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
     			}
     			// We don't know if the user was allowed to get the Application, and we don't want to leak information about
     			// the Application's existence. Return 403.
     			logCtx.Warn("application does not exist")
    -			return nil, permissionDeniedErr
    +			return nil, nil, permissionDeniedErr
     		}
     		logCtx.Errorf("failed to get application: %s", err)
    -		return nil, permissionDeniedErr
    +		return nil, nil, permissionDeniedErr
     	}
     	// Even if we performed an initial RBAC check (because the request was fully parameterized), we still need to
     	// perform a second RBAC check to ensure that the user has access to the actual Application's project (not just the
    @@ -194,11 +194,11 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
     			// The user specified a project. We would have returned a 404 if the user had access to the app, but the app
     			// did not exist. So we have to return a 404 when the app does exist, but the user does not have access.
     			// Otherwise, they could infer that the app exists based on the error code.
    -			return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
    +			return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
     		}
     		// The user didn't specify a project. We always return permission denied for both lack of access and lack of
     		// existence.
    -		return nil, permissionDeniedErr
    +		return nil, nil, permissionDeniedErr
     	}
     	effectiveProject := "default"
     	if a.Spec.Project != "" {
    @@ -211,15 +211,20 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
     		}).Warnf("user tried to %s application in project %s, but the application is in project %s", action, project, effectiveProject)
     		// The user has access to the app, but the app is in a different project. Return 404, meaning "app doesn't
     		// exist in that project".
    -		return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
    +		return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
     	}
    -	return a, nil
    +	// Get the app's associated project, and make sure all project restrictions are enforced.
    +	proj, err := s.getAppProject(ctx, a, logCtx)
    +	if err != nil {
    +		return a, nil, err
    +	}
    +	return a, proj, nil
     }
     
     // getApplicationEnforceRBACInformer uses an informer to get an Application. If the app does not exist, permission is
     // denied, or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
     // information.
    -func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, error) {
    +func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, *appv1.AppProject, error) {
     	namespaceOrDefault := s.appNamespaceOrDefault(namespace)
     	return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
     		return s.appLister.Applications(namespaceOrDefault).Get(name)
    @@ -229,9 +234,12 @@ func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action,
     // getApplicationEnforceRBACClient uses a client to get an Application. If the app does not exist, permission is denied,
     // or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
     // information.
    -func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, error) {
    +func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, *appv1.AppProject, error) {
     	namespaceOrDefault := s.appNamespaceOrDefault(namespace)
     	return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
    +		if !s.isNamespaceEnabled(namespaceOrDefault) {
    +			return nil, security.NamespaceNotPermittedError(namespaceOrDefault)
    +		}
     		return s.appclientset.ArgoprojV1alpha1().Applications(namespaceOrDefault).Get(ctx, name, metav1.GetOptions{
     			ResourceVersion: resourceVersion,
     		})
    @@ -310,7 +318,13 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
     	if q.Validate != nil {
     		validate = *q.Validate
     	}
    -	err := s.validateAndNormalizeApp(ctx, a, validate)
    +
    +	proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	err = s.validateAndNormalizeApp(ctx, a, proj, validate)
     	if err != nil {
     		return nil, fmt.Errorf("error while validating and normalizing app: %w", err)
     	}
    @@ -366,7 +380,7 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
     	return updated, nil
     }
     
    -func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, action func(
    +func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, proj *appv1.AppProject, action func(
     	client apiclient.RepoServerServiceClient,
     	repo *appv1.Repository,
     	helmRepos []*appv1.Repository,
    @@ -393,14 +407,6 @@ func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, acti
     	if err != nil {
     		return fmt.Errorf("error getting kustomize settings options: %w", err)
     	}
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
    -		}
    -		return fmt.Errorf("error getting application's project: %w", err)
    -	}
    -
     	helmRepos, err := s.db.ListHelmRepositories(ctx)
     	if err != nil {
     		return fmt.Errorf("error listing helm repositories: %w", err)
    @@ -434,7 +440,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
     	if q.Name == nil || *q.Name == "" {
     		return nil, fmt.Errorf("invalid request: application name is missing")
     	}
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -446,7 +452,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
     	}
     
     	var manifestInfo *apiclient.ManifestResponse
    -	err = s.queryRepoServer(ctx, a, func(
    +	err = s.queryRepoServer(ctx, a, proj, func(
     		client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
     		revision := source.TargetRevision
     		if q.GetRevision() != "" {
    @@ -532,13 +538,13 @@ func (s *Server) GetManifestsWithFiles(stream application.ApplicationService_Get
     		return fmt.Errorf("invalid request: application name is missing")
     	}
     
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
    +	a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
     	if err != nil {
     		return err
     	}
     
     	var manifestInfo *apiclient.ManifestResponse
    -	err = s.queryRepoServer(ctx, a, func(
    +	err = s.queryRepoServer(ctx, a, proj, func(
     		client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
     
     		appInstanceLabelKey, err := s.settingsMgr.GetAppInstanceLabelKey()
    @@ -640,7 +646,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
     	// We must use a client Get instead of an informer Get, because it's common to call Get immediately
     	// following a Watch (which is not yet powered by an informer), and the Get must reflect what was
     	// previously seen by the client.
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
     	if err != nil {
     		return nil, err
     	}
    @@ -671,7 +677,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
     
     	if refreshType == appv1.RefreshTypeHard {
     		// force refresh cached application details
    -		if err := s.queryRepoServer(ctx, a, func(
    +		if err := s.queryRepoServer(ctx, a, proj, func(
     			client apiclient.RepoServerServiceClient,
     			repo *appv1.Repository,
     			helmRepos []*appv1.Repository,
    @@ -723,7 +729,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
     
     // ListResourceEvents returns a list of event resources
     func (s *Server) ListResourceEvents(ctx context.Context, q *application.ApplicationResourceEventsQuery) (*v1.EventList, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -791,12 +797,12 @@ func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Applica
     	s.projectLock.RLock(newApp.Spec.GetProject())
     	defer s.projectLock.RUnlock(newApp.Spec.GetProject())
     
    -	app, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
    +	app, proj, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
     	if err != nil {
     		return nil, err
     	}
     
    -	err = s.validateAndNormalizeApp(ctx, newApp, validate)
    +	err = s.validateAndNormalizeApp(ctx, newApp, proj, validate)
     	if err != nil {
     		return nil, fmt.Errorf("error validating and normalizing app: %w", err)
     	}
    @@ -908,7 +914,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
     	if q.GetSpec() == nil {
     		return nil, fmt.Errorf("error updating application spec: spec is nil in request")
     	}
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -927,7 +933,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
     
     // Patch patches an application
     func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchRequest) (*appv1.Application, error) {
    -	app, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
    +	app, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -970,11 +976,35 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
     	return s.validateAndUpdateApp(ctx, newApp, false, true, rbacpolicy.ActionUpdate, q.GetProject())
     }
     
    +func (s *Server) getAppProject(ctx context.Context, a *appv1.Application, logCtx *log.Entry) (*appv1.AppProject, error) {
    +	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    +	if err == nil {
    +		return proj, nil
    +	}
    +
    +	// If there's a permission issue or the app doesn't exist, return a vague error to avoid letting the user enumerate project names.
    +	vagueError := status.Errorf(codes.InvalidArgument, "app is not allowed in project %q, or the project does not exist", a.Spec.Project)
    +
    +	if apierr.IsNotFound(err) {
    +		return nil, vagueError
    +	}
    +
    +	if _, ok := err.(*appv1.ErrApplicationNotAllowedToUseProject); ok {
    +		logCtx.WithFields(map[string]interface{}{
    +			"project":                a.Spec.Project,
    +			argocommon.SecurityField: argocommon.SecurityMedium,
    +		}).Warnf("error getting app project: %s", err)
    +		return nil, vagueError
    +	}
    +
    +	return nil, vagueError
    +}
    +
     // Delete removes an application and all associated resources
     func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteRequest) (*application.ApplicationResponse, error) {
     	appName := q.GetName()
     	appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
     	if err != nil {
     		return nil, err
     	}
    @@ -1129,16 +1159,7 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
     	}
     }
     
    -func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, validate bool) error {
    -	proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			// Offer no hint that the project does not exist.
    -			log.Warnf("User attempted to create/update application in non-existent project %q", app.Spec.Project)
    -			return permissionDeniedErr
    -		}
    -		return fmt.Errorf("error getting application's project: %w", err)
    -	}
    +func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, validate bool) error {
     	if app.GetName() == "" {
     		return fmt.Errorf("resource name may not be empty")
     	}
    @@ -1241,7 +1262,7 @@ func (s *Server) getAppResources(ctx context.Context, a *appv1.Application) (*ap
     }
     
     func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, nil, nil, err
     	}
    @@ -1378,7 +1399,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
     }
     
     func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery) (*appv1.ApplicationTree, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1387,7 +1408,7 @@ func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery
     }
     
     func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
    -	_, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
    +	_, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
     	if err != nil {
     		return err
     	}
    @@ -1404,7 +1425,7 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
     }
     
     func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.RevisionMetadata, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1414,12 +1435,6 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
     	if err != nil {
     		return nil, fmt.Errorf("error getting repository by URL: %w", err)
     	}
    -	// We need to get some information with the project associated to the app,
    -	// so we'll know whether GPG signatures are enforced.
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		return nil, fmt.Errorf("error getting app project: %w", err)
    -	}
     	conn, repoClient, err := s.repoClientset.NewRepoServerClient()
     	if err != nil {
     		return nil, fmt.Errorf("error creating repo server client: %w", err)
    @@ -1434,7 +1449,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
     
     // RevisionChartDetails returns the helm chart metadata, as fetched from the reposerver
     func (s *Server) RevisionChartDetails(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.ChartDetails, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1465,7 +1480,7 @@ func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) boo
     }
     
     func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQuery) (*application.ManagedResourcesResponse, error) {
    -	a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
     	if err != nil {
     		return nil, err
     	}
    @@ -1522,7 +1537,7 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
     		}
     	}
     
    -	a, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +	a, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
     	if err != nil {
     		return err
     	}
    @@ -1714,19 +1729,11 @@ func isTheSelectedOne(currentNode *appv1.ResourceNode, q *application.Applicatio
     
     // Sync syncs an application to its target state
     func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncRequest) (*appv1.Application, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
     
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			return a, status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
    -		}
    -		return a, fmt.Errorf("error getting app project: %w", err)
    -	}
    -
     	s.inferResourcesStatusHealth(a)
     
     	if !proj.Spec.SyncWindows.Matches(a).CanSync(true) {
    @@ -1823,7 +1830,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
     }
     
     func (s *Server) Rollback(ctx context.Context, rollbackReq *application.ApplicationRollbackRequest) (*appv1.Application, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -1882,7 +1889,7 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
     }
     
     func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksRequest) (*application.LinksResponse, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
    @@ -1897,7 +1904,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
     		return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
     	}
     
    -	clstObj, _, err := s.getObjectsForDeepLinks(ctx, a)
    +	clstObj, _, err := s.getObjectsForDeepLinks(ctx, a, proj)
     	if err != nil {
     		return nil, err
     	}
    @@ -1912,12 +1919,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
     	return finalList, nil
     }
     
    -func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
    -	proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		return nil, nil, fmt.Errorf("error getting app project: %w", err)
    -	}
    -
    +func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application, proj *appv1.AppProject) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
     	// sanitize project jwt tokens
     	proj.Status = appv1.AppProjectStatus{}
     
    @@ -1980,7 +1982,12 @@ func (s *Server) ListResourceLinks(ctx context.Context, req *application.Applica
     		return nil, err
     	}
     
    -	clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app)
    +	proj, err := s.getAppProject(ctx, app, log.WithField("application", app.GetName()))
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app, proj)
     	if err != nil {
     		return nil, err
     	}
    @@ -2036,7 +2043,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
     func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.OperationTerminateRequest) (*application.OperationTerminateResponse, error) {
     	appName := termOpReq.GetName()
     	appNs := s.appNamespaceOrDefault(termOpReq.GetAppNamespace())
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
    +	a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
     	if err != nil {
     		return nil, err
     	}
    @@ -2109,7 +2116,7 @@ func (s *Server) ListResourceActions(ctx context.Context, q *application.Applica
     
     func (s *Server) getUnstructuredLiveResourceOrApp(ctx context.Context, rbacRequest string, q *application.ApplicationResourceRequest) (obj *unstructured.Unstructured, res *appv1.ResourceNode, app *appv1.Application, config *rest.Config, err error) {
     	if q.GetKind() == applicationType.ApplicationKind && q.GetGroup() == applicationType.Group && q.GetName() == q.GetResourceName() {
    -		app, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
    +		app, _, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
     		if err != nil {
     			return nil, nil, nil, nil, err
     		}
    @@ -2205,14 +2212,19 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
     		}
     	}
     
    +	proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
    +	if err != nil {
    +		return nil, err
    +	}
    +
     	// First, make sure all the returned resources are permitted, for each operation.
     	// Also perform create with dry-runs for all create-operation resources.
     	// This is performed separately to reduce the risk of only some of the resources being successfully created later.
     	// TODO: when apply/delete operations would be supported for custom actions,
     	// the dry-run for relevant apply/delete operation would have to be invoked as well.
     	for _, impactedResource := range newObjects {
     		newObj := impactedResource.UnstructuredObj
    -		err := s.verifyResourcePermitted(ctx, app, newObj)
    +		err := s.verifyResourcePermitted(ctx, app, proj, newObj)
     		if err != nil {
     			return nil, err
     		}
    @@ -2306,14 +2318,7 @@ func (s *Server) patchResource(ctx context.Context, config *rest.Config, liveObj
     	return &application.ApplicationResponse{}, nil
     }
     
    -func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, obj *unstructured.Unstructured) error {
    -	proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		if apierr.IsNotFound(err) {
    -			return fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
    -		}
    -		return fmt.Errorf("failed to get project %s: %w", app.Spec.Project, err)
    -	}
    +func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, obj *unstructured.Unstructured) error {
     	permitted, err := proj.IsResourcePermitted(schema.GroupKind{Group: obj.GroupVersionKind().Group, Kind: obj.GroupVersionKind().Kind}, obj.GetNamespace(), app.Spec.Destination, func(project string) ([]*appv1.Cluster, error) {
     		clusters, err := s.db.GetProjectClusters(context.TODO(), project)
     		if err != nil {
    @@ -2373,16 +2378,11 @@ func splitStatusPatch(patch []byte) ([]byte, []byte, error) {
     }
     
     func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.ApplicationSyncWindowsQuery) (*application.ApplicationSyncWindowsResponse, error) {
    -	a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
    +	a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
     	if err != nil {
     		return nil, err
     	}
     
    -	proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
    -	if err != nil {
    -		return nil, fmt.Errorf("error getting app project: %w", err)
    -	}
    -
     	windows := proj.Spec.SyncWindows.Matches(a)
     	sync := windows.CanSync(true)
     
    
  • server/application/application_test.go+253 1 modified
    @@ -1817,7 +1817,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
     		appServer := newTestAppServer(t, testApp)
     
     		active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
    -		assert.Contains(t, err.Error(), "not found")
    +		assert.Contains(t, err.Error(), "not exist")
     		assert.Nil(t, active)
     	})
     }
    @@ -2364,3 +2364,255 @@ func TestIsApplicationPermitted(t *testing.T) {
     		assert.True(t, permitted)
     	})
     }
    +
    +func TestAppNamespaceRestrictions(t *testing.T) {
    +	t.Run("List applications in controller namespace", func(t *testing.T) {
    +		testApp := newTestApp()
    +		appServer := newTestAppServer(t, testApp)
    +		apps, err := appServer.List(context.TODO(), &application.ApplicationQuery{})
    +		require.NoError(t, err)
    +		require.Len(t, apps.Items, 1)
    +	})
    +
    +	t.Run("List applications with non-allowed apps existing", func(t *testing.T) {
    +		testApp1 := newTestApp()
    +		testApp1.Namespace = "argocd-1"
    +		appServer := newTestAppServer(t, testApp1)
    +		apps, err := appServer.List(context.TODO(), &application.ApplicationQuery{})
    +		require.NoError(t, err)
    +		require.Len(t, apps.Items, 0)
    +	})
    +
    +	t.Run("List applications with non-allowed apps existing and explicit ns request", func(t *testing.T) {
    +		testApp1 := newTestApp()
    +		testApp2 := newTestApp()
    +		testApp2.Namespace = "argocd-1"
    +		appServer := newTestAppServer(t, testApp1, testApp2)
    +		apps, err := appServer.List(context.TODO(), &application.ApplicationQuery{AppNamespace: pointer.String("argocd-1")})
    +		require.NoError(t, err)
    +		require.Len(t, apps.Items, 0)
    +	})
    +
    +	t.Run("List applications with allowed apps in other namespaces", func(t *testing.T) {
    +		testApp1 := newTestApp()
    +		testApp1.Namespace = "argocd-1"
    +		appServer := newTestAppServer(t, testApp1)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		apps, err := appServer.List(context.TODO(), &application.ApplicationQuery{})
    +		require.NoError(t, err)
    +		require.Len(t, apps.Items, 1)
    +	})
    +
    +	t.Run("Get application in control plane namespace", func(t *testing.T) {
    +		testApp := newTestApp()
    +		appServer := newTestAppServer(t, testApp)
    +		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
    +			Name: pointer.String("test-app"),
    +		})
    +		require.NoError(t, err)
    +		assert.Equal(t, "test-app", app.GetName())
    +	})
    +	t.Run("Get application in other namespace when forbidden", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		appServer := newTestAppServer(t, testApp)
    +		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
    +			Name:         pointer.String("test-app"),
    +			AppNamespace: pointer.String("argocd-1"),
    +		})
    +		require.Error(t, err)
    +		require.ErrorContains(t, err, "permission denied")
    +		require.Nil(t, app)
    +	})
    +	t.Run("Get application in other namespace when allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
    +			Name:         pointer.String("test-app"),
    +			AppNamespace: pointer.String("argocd-1"),
    +		})
    +		require.NoError(t, err)
    +		require.NotNil(t, app)
    +		require.Equal(t, "argocd-1", app.Namespace)
    +		require.Equal(t, "test-app", app.Name)
    +	})
    +	t.Run("Get application in other namespace when project is not allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-2"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
    +			Name:         pointer.String("test-app"),
    +			AppNamespace: pointer.String("argocd-1"),
    +		})
    +		require.Error(t, err)
    +		require.Nil(t, app)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
    +	t.Run("Create application in other namespace when allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		app, err := appServer.Create(context.TODO(), &application.ApplicationCreateRequest{
    +			Application: testApp,
    +		})
    +		require.NoError(t, err)
    +		require.NotNil(t, app)
    +		assert.Equal(t, "test-app", app.Name)
    +		assert.Equal(t, "argocd-1", app.Namespace)
    +	})
    +
    +	t.Run("Create application in other namespace when not allowed by project", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{},
    +			},
    +		}
    +		appServer := newTestAppServer(t, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		app, err := appServer.Create(context.TODO(), &application.ApplicationCreateRequest{
    +			Application: testApp,
    +		})
    +		require.Error(t, err)
    +		require.Nil(t, app)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
    +
    +	t.Run("Create application in other namespace when not allowed by configuration", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-2"}
    +		app, err := appServer.Create(context.TODO(), &application.ApplicationCreateRequest{
    +			Application: testApp,
    +		})
    +		require.Error(t, err)
    +		require.Nil(t, app)
    +		require.ErrorContains(t, err, "namespace 'argocd-1' is not permitted")
    +	})
    +	t.Run("Get application sync window in other namespace when project is allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
    +		assert.NoError(t, err)
    +		assert.Equal(t, 0, len(active.ActiveWindows))
    +	})
    +	t.Run("Get application sync window in other namespace when project is not allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-2"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
    +		require.Error(t, err)
    +		require.Nil(t, active)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
    +	t.Run("Get list of links in other namespace when project is not allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-2"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
    +			Name:      pointer.String("test-app"),
    +			Namespace: pointer.String("argocd-1"),
    +		})
    +		require.Error(t, err)
    +		require.Nil(t, links)
    +		require.ErrorContains(t, err, "app is not allowed in project")
    +	})
    +	t.Run("Get list of links in other namespace when project is allowed", func(t *testing.T) {
    +		testApp := newTestApp()
    +		testApp.Namespace = "argocd-1"
    +		testApp.Spec.Project = "other-ns"
    +		otherNsProj := &appsv1.AppProject{
    +			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
    +			Spec: appsv1.AppProjectSpec{
    +				SourceRepos:      []string{"*"},
    +				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    +				SourceNamespaces: []string{"argocd-1"},
    +			},
    +		}
    +		appServer := newTestAppServer(t, testApp, otherNsProj)
    +		appServer.enabledNamespaces = []string{"argocd-1"}
    +		links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
    +			Name:      pointer.String("test-app"),
    +			Namespace: pointer.String("argocd-1"),
    +		})
    +		require.NoError(t, err)
    +		assert.Equal(t, 0, len(links.Items))
    +	})
    +}
    
  • util/argo/argo.go+1 2 modified
    @@ -689,8 +689,7 @@ func GetAppProject(app *argoappv1.Application, projLister applicationsv1.AppProj
     		return nil, err
     	}
     	if !proj.IsAppNamespacePermitted(app, ns) {
    -		return nil, fmt.Errorf("application '%s' in namespace '%s' is not allowed to use project '%s'",
    -			app.Name, app.Namespace, proj.Name)
    +		return nil, argoappv1.NewErrApplicationNotAllowedToUseProject(app.Name, app.Namespace, proj.Name)
     	}
     	return proj, nil
     }
    

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

News mentions

0

No linked articles in our index yet.