github.com/argoproj/argo-cd/v3@v3.2.1/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/tls"
     7  	"errors"
     8  	"fmt"
     9  	goio "io"
    10  	"io/fs"
    11  	"math"
    12  	"net"
    13  	"net/http"
    14  	"net/url"
    15  	"os"
    16  	"os/exec"
    17  	"os/signal"
    18  	"path"
    19  	"path/filepath"
    20  	"reflect"
    21  	"regexp"
    22  	go_runtime "runtime"
    23  	"runtime/debug"
    24  	"strings"
    25  	gosync "sync"
    26  	"sync/atomic"
    27  	"syscall"
    28  	"time"
    29  
    30  	"github.com/argoproj/notifications-engine/pkg/api"
    31  	"github.com/argoproj/pkg/v2/sync"
    32  	"github.com/golang-jwt/jwt/v5"
    33  	golang_proto "github.com/golang/protobuf/proto" //nolint:staticcheck
    34  	"github.com/gorilla/handlers"
    35  	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
    36  	"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors"
    37  	grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
    38  	"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
    39  	"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery"
    40  	"github.com/grpc-ecosystem/grpc-gateway/runtime"
    41  	"github.com/improbable-eng/grpc-web/go/grpcweb"
    42  	"github.com/prometheus/client_golang/prometheus"
    43  	"github.com/redis/go-redis/v9"
    44  	log "github.com/sirupsen/logrus"
    45  	"github.com/soheilhy/cmux"
    46  	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
    47  	"go.opentelemetry.io/otel"
    48  	"go.opentelemetry.io/otel/propagation"
    49  	"google.golang.org/grpc"
    50  	"google.golang.org/grpc/codes"
    51  	"google.golang.org/grpc/credentials"
    52  	"google.golang.org/grpc/credentials/insecure"
    53  	"google.golang.org/grpc/health"
    54  	"google.golang.org/grpc/health/grpc_health_v1"
    55  	"google.golang.org/grpc/keepalive"
    56  	"google.golang.org/grpc/metadata"
    57  	"google.golang.org/grpc/reflection"
    58  	"google.golang.org/grpc/status"
    59  	"gopkg.in/yaml.v2"
    60  	corev1 "k8s.io/api/core/v1"
    61  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    62  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    63  	"k8s.io/apimachinery/pkg/labels"
    64  	"k8s.io/apimachinery/pkg/selection"
    65  	"k8s.io/apimachinery/pkg/util/wait"
    66  	"k8s.io/client-go/dynamic"
    67  	"k8s.io/client-go/kubernetes"
    68  	"k8s.io/client-go/tools/cache"
    69  	"sigs.k8s.io/controller-runtime/pkg/client"
    70  
    71  	"github.com/argoproj/argo-cd/v3/common"
    72  	"github.com/argoproj/argo-cd/v3/pkg/apiclient"
    73  	accountpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/account"
    74  	applicationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/application"
    75  	applicationsetpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/applicationset"
    76  	certificatepkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/certificate"
    77  	clusterpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster"
    78  	gpgkeypkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/gpgkey"
    79  	notificationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/notification"
    80  	projectpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/project"
    81  	repocredspkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/repocreds"
    82  	repositorypkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/repository"
    83  	sessionpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/session"
    84  	settingspkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/settings"
    85  	versionpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/version"
    86  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    87  	appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned"
    88  	appinformer "github.com/argoproj/argo-cd/v3/pkg/client/informers/externalversions"
    89  	applisters "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
    90  	repoapiclient "github.com/argoproj/argo-cd/v3/reposerver/apiclient"
    91  	repocache "github.com/argoproj/argo-cd/v3/reposerver/cache"
    92  	"github.com/argoproj/argo-cd/v3/server/account"
    93  	"github.com/argoproj/argo-cd/v3/server/application"
    94  	"github.com/argoproj/argo-cd/v3/server/applicationset"
    95  	"github.com/argoproj/argo-cd/v3/server/badge"
    96  	servercache "github.com/argoproj/argo-cd/v3/server/cache"
    97  	"github.com/argoproj/argo-cd/v3/server/certificate"
    98  	"github.com/argoproj/argo-cd/v3/server/cluster"
    99  	"github.com/argoproj/argo-cd/v3/server/extension"
   100  	"github.com/argoproj/argo-cd/v3/server/gpgkey"
   101  	"github.com/argoproj/argo-cd/v3/server/logout"
   102  	"github.com/argoproj/argo-cd/v3/server/metrics"
   103  	"github.com/argoproj/argo-cd/v3/server/notification"
   104  	"github.com/argoproj/argo-cd/v3/server/project"
   105  	"github.com/argoproj/argo-cd/v3/server/rbacpolicy"
   106  	"github.com/argoproj/argo-cd/v3/server/repocreds"
   107  	"github.com/argoproj/argo-cd/v3/server/repository"
   108  	"github.com/argoproj/argo-cd/v3/server/session"
   109  	"github.com/argoproj/argo-cd/v3/server/settings"
   110  	"github.com/argoproj/argo-cd/v3/server/version"
   111  	"github.com/argoproj/argo-cd/v3/ui"
   112  	"github.com/argoproj/argo-cd/v3/util/assets"
   113  	cacheutil "github.com/argoproj/argo-cd/v3/util/cache"
   114  	"github.com/argoproj/argo-cd/v3/util/db"
   115  	dexutil "github.com/argoproj/argo-cd/v3/util/dex"
   116  	"github.com/argoproj/argo-cd/v3/util/env"
   117  	errorsutil "github.com/argoproj/argo-cd/v3/util/errors"
   118  	grpc_util "github.com/argoproj/argo-cd/v3/util/grpc"
   119  	"github.com/argoproj/argo-cd/v3/util/healthz"
   120  	httputil "github.com/argoproj/argo-cd/v3/util/http"
   121  	utilio "github.com/argoproj/argo-cd/v3/util/io"
   122  	"github.com/argoproj/argo-cd/v3/util/io/files"
   123  	jwtutil "github.com/argoproj/argo-cd/v3/util/jwt"
   124  	kubeutil "github.com/argoproj/argo-cd/v3/util/kube"
   125  	service "github.com/argoproj/argo-cd/v3/util/notification/argocd"
   126  	"github.com/argoproj/argo-cd/v3/util/notification/k8s"
   127  	settings_notif "github.com/argoproj/argo-cd/v3/util/notification/settings"
   128  	"github.com/argoproj/argo-cd/v3/util/oidc"
   129  	"github.com/argoproj/argo-cd/v3/util/rbac"
   130  	util_session "github.com/argoproj/argo-cd/v3/util/session"
   131  	settings_util "github.com/argoproj/argo-cd/v3/util/settings"
   132  	"github.com/argoproj/argo-cd/v3/util/swagger"
   133  	tlsutil "github.com/argoproj/argo-cd/v3/util/tls"
   134  	"github.com/argoproj/argo-cd/v3/util/webhook"
   135  )
   136  
   137  const (
   138  	maxConcurrentLoginRequestsCountEnv = "ARGOCD_MAX_CONCURRENT_LOGIN_REQUESTS_COUNT"
   139  	replicasCountEnv                   = "ARGOCD_API_SERVER_REPLICAS"
   140  	renewTokenKey                      = "renew-token"
   141  )
   142  
   143  // ErrNoSession indicates no auth token was supplied as part of a request
   144  var ErrNoSession = status.Errorf(codes.Unauthenticated, "no session information")
   145  
   146  var noCacheHeaders = map[string]string{
   147  	"Expires":         time.Unix(0, 0).Format(time.RFC1123),
   148  	"Cache-Control":   "no-cache, private, max-age=0",
   149  	"Pragma":          "no-cache",
   150  	"X-Accel-Expires": "0",
   151  }
   152  
   153  var backoff = wait.Backoff{
   154  	Steps:    5,
   155  	Duration: 500 * time.Millisecond,
   156  	Factor:   1.0,
   157  	Jitter:   0.1,
   158  }
   159  
   160  var (
   161  	clientConstraint = ">= " + common.MinClientVersion
   162  	baseHRefRegex    = regexp.MustCompile(`<base href="(.*?)">`)
   163  	// limits number of concurrent login requests to prevent password brute forcing. If set to 0 then no limit is enforced.
   164  	maxConcurrentLoginRequestsCount = 50
   165  	replicasCount                   = 1
   166  	enableGRPCTimeHistogram         = true
   167  )
   168  
   169  func init() {
   170  	maxConcurrentLoginRequestsCount = env.ParseNumFromEnv(maxConcurrentLoginRequestsCountEnv, maxConcurrentLoginRequestsCount, 0, math.MaxInt32)
   171  	replicasCount = env.ParseNumFromEnv(replicasCountEnv, replicasCount, 0, math.MaxInt32)
   172  	if replicasCount > 0 {
   173  		maxConcurrentLoginRequestsCount = maxConcurrentLoginRequestsCount / replicasCount
   174  	}
   175  	enableGRPCTimeHistogram = env.ParseBoolFromEnv(common.EnvEnableGRPCTimeHistogramEnv, false)
   176  }
   177  
   178  // ArgoCDServer is the API server for Argo CD
   179  type ArgoCDServer struct {
   180  	ArgoCDServerOpts
   181  	ApplicationSetOpts
   182  
   183  	ssoClientApp   *oidc.ClientApp
   184  	settings       *settings_util.ArgoCDSettings
   185  	log            *log.Entry
   186  	sessionMgr     *util_session.SessionManager
   187  	settingsMgr    *settings_util.SettingsManager
   188  	enf            *rbac.Enforcer
   189  	projInformer   cache.SharedIndexInformer
   190  	policyEnforcer *rbacpolicy.RBACPolicyEnforcer
   191  	appInformer    cache.SharedIndexInformer
   192  	appLister      applisters.ApplicationLister
   193  	appsetInformer cache.SharedIndexInformer
   194  	appsetLister   applisters.ApplicationSetLister
   195  	db             db.ArgoDB
   196  
   197  	// stopCh is the channel which when closed, will shutdown the Argo CD server
   198  	stopCh             chan os.Signal
   199  	userStateStorage   util_session.UserStateStorage
   200  	indexDataInit      gosync.Once
   201  	indexData          []byte
   202  	indexDataErr       error
   203  	staticAssets       http.FileSystem
   204  	apiFactory         api.Factory
   205  	secretInformer     cache.SharedIndexInformer
   206  	configMapInformer  cache.SharedIndexInformer
   207  	serviceSet         *ArgoCDServiceSet
   208  	extensionManager   *extension.Manager
   209  	Shutdown           func()
   210  	terminateRequested atomic.Bool
   211  	available          atomic.Bool
   212  }
   213  
   214  type ArgoCDServerOpts struct {
   215  	DisableAuth             bool
   216  	ContentTypes            []string
   217  	EnableGZip              bool
   218  	Insecure                bool
   219  	StaticAssetsDir         string
   220  	ListenPort              int
   221  	ListenHost              string
   222  	MetricsPort             int
   223  	MetricsHost             string
   224  	Namespace               string
   225  	DexServerAddr           string
   226  	DexTLSConfig            *dexutil.DexTLSConfig
   227  	BaseHRef                string
   228  	RootPath                string
   229  	DynamicClientset        dynamic.Interface
   230  	KubeControllerClientset client.Client
   231  	KubeClientset           kubernetes.Interface
   232  	AppClientset            appclientset.Interface
   233  	RepoClientset           repoapiclient.Clientset
   234  	Cache                   *servercache.Cache
   235  	RepoServerCache         *repocache.Cache
   236  	RedisClient             *redis.Client
   237  	TLSConfigCustomizer     tlsutil.ConfigCustomizer
   238  	XFrameOptions           string
   239  	ContentSecurityPolicy   string
   240  	ApplicationNamespaces   []string
   241  	EnableProxyExtension    bool
   242  	WebhookParallelism      int
   243  	EnableK8sEvent          []string
   244  	HydratorEnabled         bool
   245  	SyncWithReplaceAllowed  bool
   246  }
   247  
   248  type ApplicationSetOpts struct {
   249  	GitSubmoduleEnabled      bool
   250  	EnableNewGitFileGlobbing bool
   251  	ScmRootCAPath            string
   252  	AllowedScmProviders      []string
   253  	EnableScmProviders       bool
   254  	EnableGitHubAPIMetrics   bool
   255  }
   256  
   257  // GracefulRestartSignal implements a signal to be used for a graceful restart trigger.
   258  type GracefulRestartSignal struct{}
   259  
   260  // HTTPMetricsRegistry exposes operations to update http metrics in the Argo CD
   261  // API server.
   262  type HTTPMetricsRegistry interface {
   263  	// IncExtensionRequestCounter will increase the request counter for the given
   264  	// extension with the given status.
   265  	IncExtensionRequestCounter(extension string, status int)
   266  	// ObserveExtensionRequestDuration will register the request roundtrip duration
   267  	// between Argo CD API Server and the extension backend service for the given
   268  	// extension.
   269  	ObserveExtensionRequestDuration(extension string, duration time.Duration)
   270  }
   271  
   272  // String is a part of os.Signal interface to represent a signal as a string.
   273  func (g GracefulRestartSignal) String() string {
   274  	return "GracefulRestartSignal"
   275  }
   276  
   277  // Signal is a part of os.Signal interface doing nothing.
   278  func (g GracefulRestartSignal) Signal() {}
   279  
   280  // initializeDefaultProject creates the default project if it does not already exist
   281  func initializeDefaultProject(opts ArgoCDServerOpts) error {
   282  	defaultProj := &v1alpha1.AppProject{
   283  		ObjectMeta: metav1.ObjectMeta{Name: v1alpha1.DefaultAppProjectName, Namespace: opts.Namespace},
   284  		Spec: v1alpha1.AppProjectSpec{
   285  			SourceRepos:              []string{"*"},
   286  			Destinations:             []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   287  			ClusterResourceWhitelist: []metav1.GroupKind{{Group: "*", Kind: "*"}},
   288  		},
   289  	}
   290  
   291  	_, err := opts.AppClientset.ArgoprojV1alpha1().AppProjects(opts.Namespace).Get(context.Background(), defaultProj.Name, metav1.GetOptions{})
   292  	if apierrors.IsNotFound(err) {
   293  		_, err = opts.AppClientset.ArgoprojV1alpha1().AppProjects(opts.Namespace).Create(context.Background(), defaultProj, metav1.CreateOptions{})
   294  		if apierrors.IsAlreadyExists(err) {
   295  			return nil
   296  		}
   297  	}
   298  	return err
   299  }
   300  
   301  // NewServer returns a new instance of the Argo CD API server
   302  func NewServer(ctx context.Context, opts ArgoCDServerOpts, appsetOpts ApplicationSetOpts) *ArgoCDServer {
   303  	settingsMgr := settings_util.NewSettingsManager(ctx, opts.KubeClientset, opts.Namespace)
   304  	settings, err := settingsMgr.InitializeSettings(opts.Insecure)
   305  	errorsutil.CheckError(err)
   306  	err = initializeDefaultProject(opts)
   307  	errorsutil.CheckError(err)
   308  
   309  	appInformerNs := opts.Namespace
   310  	if len(opts.ApplicationNamespaces) > 0 {
   311  		appInformerNs = ""
   312  	}
   313  	projFactory := appinformer.NewSharedInformerFactoryWithOptions(opts.AppClientset, 0, appinformer.WithNamespace(opts.Namespace), appinformer.WithTweakListOptions(func(_ *metav1.ListOptions) {}))
   314  	appFactory := appinformer.NewSharedInformerFactoryWithOptions(opts.AppClientset, 0, appinformer.WithNamespace(appInformerNs), appinformer.WithTweakListOptions(func(_ *metav1.ListOptions) {}))
   315  
   316  	projInformer := projFactory.Argoproj().V1alpha1().AppProjects().Informer()
   317  	projLister := projFactory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(opts.Namespace)
   318  
   319  	appInformer := appFactory.Argoproj().V1alpha1().Applications().Informer()
   320  	appLister := appFactory.Argoproj().V1alpha1().Applications().Lister()
   321  
   322  	appsetInformer := appFactory.Argoproj().V1alpha1().ApplicationSets().Informer()
   323  	appsetLister := appFactory.Argoproj().V1alpha1().ApplicationSets().Lister()
   324  
   325  	userStateStorage := util_session.NewUserStateStorage(opts.RedisClient)
   326  	sessionMgr := util_session.NewSessionManager(settingsMgr, projLister, opts.DexServerAddr, opts.DexTLSConfig, userStateStorage)
   327  	enf := rbac.NewEnforcer(opts.KubeClientset, opts.Namespace, common.ArgoCDRBACConfigMapName, nil)
   328  	enf.EnableEnforce(!opts.DisableAuth)
   329  	err = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
   330  	errorsutil.CheckError(err)
   331  	enf.EnableLog(os.Getenv(common.EnvVarRBACDebug) == "1")
   332  
   333  	policyEnf := rbacpolicy.NewRBACPolicyEnforcer(enf, projLister)
   334  	enf.SetClaimsEnforcerFunc(policyEnf.EnforceClaims)
   335  
   336  	staticFS, err := fs.Sub(ui.Embedded, "dist/app")
   337  	errorsutil.CheckError(err)
   338  
   339  	root, err := os.OpenRoot(opts.StaticAssetsDir)
   340  	if err != nil {
   341  		if os.IsNotExist(err) {
   342  			log.Warnf("Static assets directory %q does not exist, using only embedded assets", opts.StaticAssetsDir)
   343  		} else {
   344  			errorsutil.CheckError(err)
   345  		}
   346  	} else {
   347  		staticFS = utilio.NewComposableFS(staticFS, root.FS())
   348  	}
   349  
   350  	argocdService, err := service.NewArgoCDService(opts.KubeClientset, opts.Namespace, opts.RepoClientset)
   351  	errorsutil.CheckError(err)
   352  
   353  	secretInformer := k8s.NewSecretInformer(opts.KubeClientset, opts.Namespace, "argocd-notifications-secret")
   354  	configMapInformer := k8s.NewConfigMapInformer(opts.KubeClientset, opts.Namespace, "argocd-notifications-cm")
   355  
   356  	apiFactory := api.NewFactory(settings_notif.GetFactorySettings(argocdService, "argocd-notifications-secret", "argocd-notifications-cm", false), opts.Namespace, secretInformer, configMapInformer)
   357  
   358  	dbInstance := db.NewDB(opts.Namespace, settingsMgr, opts.KubeClientset)
   359  	logger := log.NewEntry(log.StandardLogger())
   360  
   361  	sg := extension.NewDefaultSettingsGetter(settingsMgr)
   362  	ag := extension.NewDefaultApplicationGetter(appLister)
   363  	pg := extension.NewDefaultProjectGetter(projLister, dbInstance)
   364  	ug := extension.NewDefaultUserGetter(policyEnf)
   365  	em := extension.NewManager(logger, opts.Namespace, sg, ag, pg, dbInstance, enf, ug)
   366  	noopShutdown := func() {
   367  		log.Error("API Server Shutdown function called but server is not started yet.")
   368  	}
   369  
   370  	a := &ArgoCDServer{
   371  		ArgoCDServerOpts:   opts,
   372  		ApplicationSetOpts: appsetOpts,
   373  		log:                logger,
   374  		settings:           settings,
   375  		sessionMgr:         sessionMgr,
   376  		settingsMgr:        settingsMgr,
   377  		enf:                enf,
   378  		projInformer:       projInformer,
   379  		appInformer:        appInformer,
   380  		appLister:          appLister,
   381  		appsetInformer:     appsetInformer,
   382  		appsetLister:       appsetLister,
   383  		policyEnforcer:     policyEnf,
   384  		userStateStorage:   userStateStorage,
   385  		staticAssets:       http.FS(staticFS),
   386  		db:                 dbInstance,
   387  		apiFactory:         apiFactory,
   388  		secretInformer:     secretInformer,
   389  		configMapInformer:  configMapInformer,
   390  		extensionManager:   em,
   391  		Shutdown:           noopShutdown,
   392  		stopCh:             make(chan os.Signal, 1),
   393  	}
   394  
   395  	err = a.logInClusterWarnings()
   396  	if err != nil {
   397  		// Just log. It's not critical.
   398  		log.Warnf("Failed to log in-cluster warnings: %v", err)
   399  	}
   400  
   401  	return a
   402  }
   403  
   404  const (
   405  	// catches corrupted informer state; see https://github.com/argoproj/argo-cd/issues/4960 for more information
   406  	notObjectErrMsg = "object does not implement the Object interfaces"
   407  )
   408  
   409  func (server *ArgoCDServer) healthCheck(r *http.Request) error {
   410  	if server.terminateRequested.Load() {
   411  		return errors.New("API Server is terminating and unable to serve requests")
   412  	}
   413  	if !server.available.Load() {
   414  		return errors.New("API Server is not available: it either hasn't started or is restarting")
   415  	}
   416  	if val, ok := r.URL.Query()["full"]; ok && len(val) > 0 && val[0] == "true" {
   417  		argoDB := db.NewDB(server.Namespace, server.settingsMgr, server.KubeClientset)
   418  		_, err := argoDB.ListClusters(r.Context())
   419  		if err != nil && strings.Contains(err.Error(), notObjectErrMsg) {
   420  			return err
   421  		}
   422  	}
   423  	return nil
   424  }
   425  
   426  type Listeners struct {
   427  	Main        net.Listener
   428  	Metrics     net.Listener
   429  	GatewayConn *grpc.ClientConn
   430  }
   431  
   432  func (l *Listeners) Close() error {
   433  	if l.Main != nil {
   434  		if err := l.Main.Close(); err != nil {
   435  			return err
   436  		}
   437  		l.Main = nil
   438  	}
   439  	if l.Metrics != nil {
   440  		if err := l.Metrics.Close(); err != nil {
   441  			return err
   442  		}
   443  		l.Metrics = nil
   444  	}
   445  	if l.GatewayConn != nil {
   446  		if err := l.GatewayConn.Close(); err != nil {
   447  			return err
   448  		}
   449  		l.GatewayConn = nil
   450  	}
   451  	return nil
   452  }
   453  
   454  // logInClusterWarnings checks the in-cluster configuration and prints out any warnings.
   455  func (server *ArgoCDServer) logInClusterWarnings() error {
   456  	labelSelector := labels.NewSelector()
   457  	req, err := labels.NewRequirement(common.LabelKeySecretType, selection.Equals, []string{common.LabelValueSecretTypeCluster})
   458  	if err != nil {
   459  		return fmt.Errorf("failed to construct cluster-type label selector: %w", err)
   460  	}
   461  	labelSelector = labelSelector.Add(*req)
   462  	secretsLister, err := server.settingsMgr.GetSecretsLister()
   463  	if err != nil {
   464  		return fmt.Errorf("failed to get secrets lister: %w", err)
   465  	}
   466  	clusterSecrets, err := secretsLister.Secrets(server.ArgoCDServerOpts.Namespace).List(labelSelector)
   467  	if err != nil {
   468  		return fmt.Errorf("failed to list cluster secrets: %w", err)
   469  	}
   470  	var inClusterSecrets []string
   471  	for _, clusterSecret := range clusterSecrets {
   472  		cluster, err := db.SecretToCluster(clusterSecret)
   473  		if err != nil {
   474  			return fmt.Errorf("could not unmarshal cluster secret %q: %w", clusterSecret.Name, err)
   475  		}
   476  		if cluster.Server == v1alpha1.KubernetesInternalAPIServerAddr {
   477  			inClusterSecrets = append(inClusterSecrets, clusterSecret.Name)
   478  		}
   479  	}
   480  	if len(inClusterSecrets) > 0 {
   481  		// Don't make this call unless we actually have in-cluster secrets, to save time.
   482  		dbSettings, err := server.settingsMgr.GetSettings()
   483  		if err != nil {
   484  			return fmt.Errorf("could not get DB settings: %w", err)
   485  		}
   486  		if !dbSettings.InClusterEnabled {
   487  			for _, clusterName := range inClusterSecrets {
   488  				log.Warnf("cluster %q uses in-cluster server address but it's disabled in Argo CD settings", clusterName)
   489  			}
   490  		}
   491  	}
   492  	return nil
   493  }
   494  
   495  func startListener(host string, port int) (net.Listener, error) {
   496  	var conn net.Listener
   497  	var realErr error
   498  	_ = wait.ExponentialBackoff(backoff, func() (bool, error) {
   499  		conn, realErr = net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
   500  		if realErr != nil {
   501  			return false, nil
   502  		}
   503  		return true, nil
   504  	})
   505  	return conn, realErr
   506  }
   507  
   508  func (server *ArgoCDServer) Listen() (*Listeners, error) {
   509  	mainLn, err := startListener(server.ListenHost, server.ListenPort)
   510  	if err != nil {
   511  		return nil, err
   512  	}
   513  	metricsLn, err := startListener(server.ListenHost, server.MetricsPort)
   514  	if err != nil {
   515  		utilio.Close(mainLn)
   516  		return nil, err
   517  	}
   518  	var dOpts []grpc.DialOption
   519  	dOpts = append(dOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(apiclient.MaxGRPCMessageSize)))
   520  	dOpts = append(dOpts, grpc.WithUserAgent(fmt.Sprintf("%s/%s", common.ArgoCDUserAgentName, common.GetVersion().Version)))
   521  	dOpts = append(dOpts, grpc.WithStatsHandler(otelgrpc.NewClientHandler()))
   522  	if server.useTLS() {
   523  		// The following sets up the dial Options for grpc-gateway to talk to gRPC server over TLS.
   524  		// grpc-gateway is just translating HTTP/HTTPS requests as gRPC requests over localhost,
   525  		// so we need to supply the same certificates to establish the connections that a normal,
   526  		// external gRPC client would need.
   527  		tlsConfig := server.settings.TLSConfig()
   528  		if server.TLSConfigCustomizer != nil {
   529  			server.TLSConfigCustomizer(tlsConfig)
   530  		}
   531  		tlsConfig.InsecureSkipVerify = true
   532  		dCreds := credentials.NewTLS(tlsConfig)
   533  		dOpts = append(dOpts, grpc.WithTransportCredentials(dCreds))
   534  	} else {
   535  		dOpts = append(dOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
   536  	}
   537  
   538  	conn, err := grpc.NewClient(fmt.Sprintf("localhost:%d", server.ListenPort), dOpts...)
   539  	if err != nil {
   540  		utilio.Close(mainLn)
   541  		utilio.Close(metricsLn)
   542  		return nil, err
   543  	}
   544  	return &Listeners{Main: mainLn, Metrics: metricsLn, GatewayConn: conn}, nil
   545  }
   546  
   547  // Init starts informers used by the API server
   548  func (server *ArgoCDServer) Init(ctx context.Context) {
   549  	go server.projInformer.Run(ctx.Done())
   550  	go server.appInformer.Run(ctx.Done())
   551  	go server.appsetInformer.Run(ctx.Done())
   552  	go server.configMapInformer.Run(ctx.Done())
   553  	go server.secretInformer.Run(ctx.Done())
   554  }
   555  
   556  // Run runs the API Server
   557  // We use k8s.io/code-generator/cmd/go-to-protobuf to generate the .proto files from the API types.
   558  // k8s.io/ go-to-protobuf uses protoc-gen-gogo, which comes from gogo/protobuf (a fork of
   559  // golang/protobuf).
   560  func (server *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) {
   561  	defer func() {
   562  		if r := recover(); r != nil {
   563  			log.WithField("trace", string(debug.Stack())).Error("Recovered from panic: ", r)
   564  			server.terminateRequested.Store(true)
   565  			server.Shutdown()
   566  		}
   567  	}()
   568  	metricsServ := metrics.NewMetricsServer(server.MetricsHost, server.MetricsPort)
   569  	if server.RedisClient != nil {
   570  		cacheutil.CollectMetrics(server.RedisClient, metricsServ, server.userStateStorage.GetLockObject())
   571  	}
   572  
   573  	// Don't init storage until after CollectMetrics. CollectMetrics adds hooks to the Redis client, and Init
   574  	// reads those hooks. If this is called first, there may be a data race.
   575  	server.userStateStorage.Init(ctx)
   576  
   577  	svcSet := newArgoCDServiceSet(server)
   578  	if server.sessionMgr != nil {
   579  		server.sessionMgr.CollectMetrics(metricsServ)
   580  	}
   581  	server.serviceSet = svcSet
   582  	grpcS, appResourceTreeFn := server.newGRPCServer(metricsServ.PrometheusRegistry)
   583  	grpcWebS := grpcweb.WrapServer(grpcS)
   584  	var httpS *http.Server
   585  	var httpsS *http.Server
   586  	if server.useTLS() {
   587  		httpS = newRedirectServer(server.ListenPort, server.RootPath)
   588  		httpsS = server.newHTTPServer(ctx, server.ListenPort, grpcWebS, appResourceTreeFn, listeners.GatewayConn, metricsServ)
   589  	} else {
   590  		httpS = server.newHTTPServer(ctx, server.ListenPort, grpcWebS, appResourceTreeFn, listeners.GatewayConn, metricsServ)
   591  	}
   592  	if server.RootPath != "" {
   593  		httpS.Handler = withRootPath(httpS.Handler, server)
   594  
   595  		if httpsS != nil {
   596  			httpsS.Handler = withRootPath(httpsS.Handler, server)
   597  		}
   598  	}
   599  	httpS.Handler = &bug21955Workaround{handler: httpS.Handler}
   600  	if httpsS != nil {
   601  		httpsS.Handler = &bug21955Workaround{handler: httpsS.Handler}
   602  	}
   603  
   604  	// CMux is used to support servicing gRPC and HTTP1.1+JSON on the same port
   605  	tcpm := cmux.New(listeners.Main)
   606  	var tlsm cmux.CMux
   607  	var grpcL net.Listener
   608  	var httpL net.Listener
   609  	var httpsL net.Listener
   610  	if !server.useTLS() {
   611  		httpL = tcpm.Match(cmux.HTTP1Fast("PATCH"))
   612  		grpcL = tcpm.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
   613  	} else {
   614  		// We first match on HTTP 1.1 methods.
   615  		httpL = tcpm.Match(cmux.HTTP1Fast("PATCH"))
   616  
   617  		// If not matched, we assume that its TLS.
   618  		tlsl := tcpm.Match(cmux.Any())
   619  		tlsConfig := tls.Config{
   620  			// Advertise that we support both http/1.1 and http2 for application level communication.
   621  			// By putting http/1.1 first, we ensure that HTTPS clients will use http/1.1, which is the only
   622  			// protocol our server supports for HTTPS clients. By including h2 in the list, we ensure that
   623  			// gRPC clients know we support http2 for their communication.
   624  			NextProtos: []string{"http/1.1", "h2"},
   625  		}
   626  		tlsConfig.GetCertificate = func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
   627  			return server.settings.Certificate, nil
   628  		}
   629  		if server.TLSConfigCustomizer != nil {
   630  			server.TLSConfigCustomizer(&tlsConfig)
   631  		}
   632  		tlsl = tls.NewListener(tlsl, &tlsConfig)
   633  
   634  		// Now, we build another mux recursively to match HTTPS and gRPC.
   635  		tlsm = cmux.New(tlsl)
   636  		httpsL = tlsm.Match(cmux.HTTP1Fast("PATCH"))
   637  		grpcL = tlsm.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
   638  	}
   639  
   640  	// Start the muxed listeners for our servers
   641  	log.Infof("argocd %s serving on port %d (url: %s, tls: %v, namespace: %s, sso: %v)",
   642  		common.GetVersion(), server.ListenPort, server.settings.URL, server.useTLS(), server.Namespace, server.settings.IsSSOConfigured())
   643  	log.Infof("Enabled application namespace patterns: %s", server.allowedApplicationNamespacesAsString())
   644  
   645  	go func() { server.checkServeErr("grpcS", grpcS.Serve(grpcL)) }()
   646  	go func() { server.checkServeErr("httpS", httpS.Serve(httpL)) }()
   647  	if server.useTLS() {
   648  		go func() { server.checkServeErr("httpsS", httpsS.Serve(httpsL)) }()
   649  		go func() { server.checkServeErr("tlsm", tlsm.Serve()) }()
   650  	}
   651  	go server.watchSettings()
   652  	go server.rbacPolicyLoader(ctx)
   653  	go func() { server.checkServeErr("tcpm", tcpm.Serve()) }()
   654  	go func() { server.checkServeErr("metrics", metricsServ.Serve(listeners.Metrics)) }()
   655  	if !cache.WaitForCacheSync(ctx.Done(), server.projInformer.HasSynced, server.appInformer.HasSynced) {
   656  		log.Fatal("Timed out waiting for project cache to sync")
   657  	}
   658  
   659  	shutdownFunc := func() {
   660  		log.Info("API Server shutdown initiated. Shutting down servers...")
   661  		server.available.Store(false)
   662  		shutdownCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
   663  		defer cancel()
   664  		var wg gosync.WaitGroup
   665  
   666  		// Shutdown http server
   667  		wg.Add(1)
   668  		go func() {
   669  			defer wg.Done()
   670  			err := httpS.Shutdown(shutdownCtx)
   671  			if err != nil {
   672  				log.Errorf("Error shutting down http server: %s", err)
   673  			}
   674  		}()
   675  
   676  		if server.useTLS() {
   677  			// Shutdown https server
   678  			wg.Add(1)
   679  			go func() {
   680  				defer wg.Done()
   681  				err := httpsS.Shutdown(shutdownCtx)
   682  				if err != nil {
   683  					log.Errorf("Error shutting down https server: %s", err)
   684  				}
   685  			}()
   686  		}
   687  
   688  		// Shutdown gRPC server
   689  		wg.Add(1)
   690  		go func() {
   691  			defer wg.Done()
   692  			grpcS.GracefulStop()
   693  		}()
   694  
   695  		// Shutdown metrics server
   696  		wg.Add(1)
   697  		go func() {
   698  			defer wg.Done()
   699  			err := metricsServ.Shutdown(shutdownCtx)
   700  			if err != nil {
   701  				log.Errorf("Error shutting down metrics server: %s", err)
   702  			}
   703  		}()
   704  
   705  		if server.useTLS() {
   706  			// Shutdown tls server
   707  			wg.Add(1)
   708  			go func() {
   709  				defer wg.Done()
   710  				tlsm.Close()
   711  			}()
   712  		}
   713  
   714  		// Shutdown tcp server
   715  		wg.Add(1)
   716  		go func() {
   717  			defer wg.Done()
   718  			tcpm.Close()
   719  		}()
   720  
   721  		c := make(chan struct{})
   722  		// This goroutine will wait for all servers to conclude the shutdown
   723  		// process
   724  		go func() {
   725  			defer close(c)
   726  			wg.Wait()
   727  		}()
   728  
   729  		select {
   730  		case <-c:
   731  			log.Info("All servers were gracefully shutdown. Exiting...")
   732  		case <-shutdownCtx.Done():
   733  			log.Warn("Graceful shutdown timeout. Exiting...")
   734  		}
   735  	}
   736  	server.Shutdown = shutdownFunc
   737  	signal.Notify(server.stopCh, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
   738  	server.available.Store(true)
   739  
   740  	select {
   741  	case signal := <-server.stopCh:
   742  		log.Infof("API Server received signal: %s", signal.String())
   743  		gracefulRestartSignal := GracefulRestartSignal{}
   744  		if signal != gracefulRestartSignal {
   745  			server.terminateRequested.Store(true)
   746  		}
   747  		server.Shutdown()
   748  	case <-ctx.Done():
   749  		log.Infof("API Server: %s", ctx.Err())
   750  		server.terminateRequested.Store(true)
   751  		server.Shutdown()
   752  	}
   753  }
   754  
   755  func (server *ArgoCDServer) Initialized() bool {
   756  	return server.projInformer.HasSynced() && server.appInformer.HasSynced()
   757  }
   758  
   759  // TerminateRequested returns whether a shutdown was initiated by a signal or context cancel
   760  // as opposed to a watch.
   761  func (server *ArgoCDServer) TerminateRequested() bool {
   762  	return server.terminateRequested.Load()
   763  }
   764  
   765  // checkServeErr checks the error from a .Serve() call to decide if it was a graceful shutdown
   766  func (server *ArgoCDServer) checkServeErr(name string, err error) {
   767  	if err != nil && !errors.Is(err, http.ErrServerClosed) {
   768  		log.Errorf("Error received from server %s: %v", name, err)
   769  	} else {
   770  		log.Infof("Graceful shutdown of %s initiated", name)
   771  	}
   772  }
   773  
   774  func checkOIDCConfigChange(currentOIDCConfig *settings_util.OIDCConfig, newArgoCDSettings *settings_util.ArgoCDSettings) bool {
   775  	newOIDCConfig := newArgoCDSettings.OIDCConfig()
   776  
   777  	if (currentOIDCConfig != nil && newOIDCConfig == nil) || (currentOIDCConfig == nil && newOIDCConfig != nil) {
   778  		return true
   779  	}
   780  
   781  	if currentOIDCConfig != nil && newOIDCConfig != nil {
   782  		if !reflect.DeepEqual(*currentOIDCConfig, *newOIDCConfig) {
   783  			return true
   784  		}
   785  	}
   786  
   787  	return false
   788  }
   789  
   790  // watchSettings watches the configmap and secret for any setting updates that would warrant a
   791  // restart of the API server.
   792  func (server *ArgoCDServer) watchSettings() {
   793  	updateCh := make(chan *settings_util.ArgoCDSettings, 1)
   794  	server.settingsMgr.Subscribe(updateCh)
   795  
   796  	prevURL := server.settings.URL
   797  	prevAdditionalURLs := server.settings.AdditionalURLs
   798  	prevOIDCConfig := server.settings.OIDCConfig()
   799  	prevDexCfgBytes, err := dexutil.GenerateDexConfigYAML(server.settings, server.DexTLSConfig == nil || server.DexTLSConfig.DisableTLS)
   800  	errorsutil.CheckError(err)
   801  	prevGitHubSecret := server.settings.GetWebhookGitHubSecret()
   802  	prevGitLabSecret := server.settings.GetWebhookGitLabSecret()
   803  	prevBitbucketUUID := server.settings.GetWebhookBitbucketUUID()
   804  	prevBitbucketServerSecret := server.settings.GetWebhookBitbucketServerSecret()
   805  	prevGogsSecret := server.settings.GetWebhookGogsSecret()
   806  	prevExtConfig := server.settings.ExtensionConfig
   807  	var prevCert, prevCertKey string
   808  	if server.settings.Certificate != nil && !server.Insecure {
   809  		prevCert, prevCertKey = tlsutil.EncodeX509KeyPairString(*server.settings.Certificate)
   810  	}
   811  
   812  	for {
   813  		newSettings := <-updateCh
   814  		server.settings = newSettings
   815  		newDexCfgBytes, err := dexutil.GenerateDexConfigYAML(server.settings, server.DexTLSConfig == nil || server.DexTLSConfig.DisableTLS)
   816  		errorsutil.CheckError(err)
   817  		if !bytes.Equal(newDexCfgBytes, prevDexCfgBytes) {
   818  			log.Infof("dex config modified. restarting")
   819  			break
   820  		}
   821  		if checkOIDCConfigChange(prevOIDCConfig, server.settings) {
   822  			log.Infof("oidc config modified. restarting")
   823  			break
   824  		}
   825  		if prevURL != server.settings.URL {
   826  			log.Infof("url modified. restarting")
   827  			break
   828  		}
   829  		if !reflect.DeepEqual(prevAdditionalURLs, server.settings.AdditionalURLs) {
   830  			log.Infof("additionalURLs modified. restarting")
   831  			break
   832  		}
   833  		if prevGitHubSecret != server.settings.GetWebhookGitHubSecret() {
   834  			log.Infof("github secret modified. restarting")
   835  			break
   836  		}
   837  		if prevGitLabSecret != server.settings.GetWebhookGitLabSecret() {
   838  			log.Infof("gitlab secret modified. restarting")
   839  			break
   840  		}
   841  		if prevBitbucketUUID != server.settings.GetWebhookBitbucketUUID() {
   842  			log.Infof("bitbucket uuid modified. restarting")
   843  			break
   844  		}
   845  		if prevBitbucketServerSecret != server.settings.GetWebhookBitbucketServerSecret() {
   846  			log.Infof("bitbucket server secret modified. restarting")
   847  			break
   848  		}
   849  		if prevGogsSecret != server.settings.GetWebhookGogsSecret() {
   850  			log.Infof("gogs secret modified. restarting")
   851  			break
   852  		}
   853  		if !reflect.DeepEqual(prevExtConfig, server.settings.ExtensionConfig) {
   854  			prevExtConfig = server.settings.ExtensionConfig
   855  			log.Infof("extensions configs modified. Updating proxy registry...")
   856  			err := server.extensionManager.UpdateExtensionRegistry(server.settings)
   857  			if err != nil {
   858  				log.Errorf("error updating extensions configs: %s", err)
   859  			} else {
   860  				log.Info("extensions configs updated successfully")
   861  			}
   862  		}
   863  		if !server.Insecure {
   864  			var newCert, newCertKey string
   865  			if server.settings.Certificate != nil {
   866  				newCert, newCertKey = tlsutil.EncodeX509KeyPairString(*server.settings.Certificate)
   867  			}
   868  			if newCert != prevCert || newCertKey != prevCertKey {
   869  				log.Infof("tls certificate modified. reloading certificate")
   870  				// No need to break out of this loop since TlsConfig.GetCertificate will automagically reload the cert.
   871  			}
   872  		}
   873  	}
   874  	log.Info("shutting down settings watch")
   875  	server.settingsMgr.Unsubscribe(updateCh)
   876  	close(updateCh)
   877  	// Triggers server restart
   878  	server.stopCh <- GracefulRestartSignal{}
   879  }
   880  
   881  func (server *ArgoCDServer) rbacPolicyLoader(ctx context.Context) {
   882  	err := server.enf.RunPolicyLoader(ctx, func(cm *corev1.ConfigMap) error {
   883  		var scopes []string
   884  		if scopesStr, ok := cm.Data[rbac.ConfigMapScopesKey]; scopesStr != "" && ok {
   885  			scopes = make([]string, 0)
   886  			err := yaml.Unmarshal([]byte(scopesStr), &scopes)
   887  			if err != nil {
   888  				return fmt.Errorf("error unmarshalling scopes: %w", err)
   889  			}
   890  		}
   891  
   892  		server.policyEnforcer.SetScopes(scopes)
   893  		return nil
   894  	})
   895  	errorsutil.CheckError(err)
   896  }
   897  
   898  func (server *ArgoCDServer) useTLS() bool {
   899  	if server.Insecure || server.settings.Certificate == nil {
   900  		return false
   901  	}
   902  	return true
   903  }
   904  
   905  func (server *ArgoCDServer) newGRPCServer(prometheusRegistry *prometheus.Registry) (*grpc.Server, application.AppResourceTreeFn) {
   906  	var serverMetricsOptions []grpc_prometheus.ServerMetricsOption
   907  	if enableGRPCTimeHistogram {
   908  		serverMetricsOptions = append(serverMetricsOptions, grpc_prometheus.WithServerHandlingTimeHistogram())
   909  	}
   910  	serverMetrics := grpc_prometheus.NewServerMetrics(serverMetricsOptions...)
   911  	prometheusRegistry.MustRegister(serverMetrics)
   912  
   913  	sOpts := []grpc.ServerOption{
   914  		// Set the both send and receive the bytes limit to be 100MB
   915  		// The proper way to achieve high performance is to have pagination
   916  		// while we work toward that, we can have high limit first
   917  		grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
   918  		grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
   919  		grpc.ConnectionTimeout(300 * time.Second),
   920  		grpc.KeepaliveEnforcementPolicy(
   921  			keepalive.EnforcementPolicy{
   922  				MinTime: common.GetGRPCKeepAliveEnforcementMinimum(),
   923  			},
   924  		),
   925  	}
   926  	sensitiveMethods := map[string]bool{
   927  		"/cluster.ClusterService/Create":                               true,
   928  		"/cluster.ClusterService/Update":                               true,
   929  		"/session.SessionService/Create":                               true,
   930  		"/account.AccountService/UpdatePassword":                       true,
   931  		"/gpgkey.GPGKeyService/CreateGnuPGPublicKey":                   true,
   932  		"/repository.RepositoryService/Create":                         true,
   933  		"/repository.RepositoryService/Update":                         true,
   934  		"/repository.RepositoryService/CreateRepository":               true,
   935  		"/repository.RepositoryService/UpdateRepository":               true,
   936  		"/repository.RepositoryService/ValidateAccess":                 true,
   937  		"/repocreds.RepoCredsService/CreateRepositoryCredentials":      true,
   938  		"/repocreds.RepoCredsService/UpdateRepositoryCredentials":      true,
   939  		"/repository.RepositoryService/CreateWriteRepository":          true,
   940  		"/repository.RepositoryService/UpdateWriteRepository":          true,
   941  		"/repository.RepositoryService/ValidateWriteAccess":            true,
   942  		"/repocreds.RepoCredsService/CreateWriteRepositoryCredentials": true,
   943  		"/repocreds.RepoCredsService/UpdateWriteRepositoryCredentials": true,
   944  		"/application.ApplicationService/PatchResource":                true,
   945  		// Remove from logs both because the contents are sensitive and because they may be very large.
   946  		"/application.ApplicationService/GetManifestsWithFiles": true,
   947  	}
   948  	// NOTE: notice we do not configure the gRPC server here with TLS (e.g. grpc.Creds(creds))
   949  	// This is because TLS handshaking occurs in cmux handling
   950  	sOpts = append(sOpts, grpc.ChainStreamInterceptor(
   951  		logging.StreamServerInterceptor(grpc_util.InterceptorLogger(server.log)),
   952  		serverMetrics.StreamServerInterceptor(),
   953  		grpc_auth.StreamServerInterceptor(server.Authenticate),
   954  		grpc_util.UserAgentStreamServerInterceptor(common.ArgoCDUserAgentName, clientConstraint),
   955  		grpc_util.PayloadStreamServerInterceptor(server.log, true, func(_ context.Context, c interceptors.CallMeta) bool {
   956  			return !sensitiveMethods[c.FullMethod()]
   957  		}),
   958  		grpc_util.ErrorCodeK8sStreamServerInterceptor(),
   959  		grpc_util.ErrorCodeGitStreamServerInterceptor(),
   960  		recovery.StreamServerInterceptor(recovery.WithRecoveryHandler(grpc_util.LoggerRecoveryHandler(server.log))),
   961  	))
   962  	sOpts = append(sOpts, grpc.ChainUnaryInterceptor(
   963  		bug21955WorkaroundInterceptor,
   964  		logging.UnaryServerInterceptor(grpc_util.InterceptorLogger(server.log)),
   965  		serverMetrics.UnaryServerInterceptor(),
   966  		grpc_auth.UnaryServerInterceptor(server.Authenticate),
   967  		grpc_util.UserAgentUnaryServerInterceptor(common.ArgoCDUserAgentName, clientConstraint),
   968  		grpc_util.PayloadUnaryServerInterceptor(server.log, true, func(_ context.Context, c interceptors.CallMeta) bool {
   969  			return !sensitiveMethods[c.FullMethod()]
   970  		}),
   971  		grpc_util.ErrorCodeK8sUnaryServerInterceptor(),
   972  		grpc_util.ErrorCodeGitUnaryServerInterceptor(),
   973  		recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(grpc_util.LoggerRecoveryHandler(server.log))),
   974  	))
   975  	sOpts = append(sOpts, grpc.StatsHandler(otelgrpc.NewServerHandler()))
   976  	grpcS := grpc.NewServer(sOpts...)
   977  
   978  	healthService := health.NewServer()
   979  	grpc_health_v1.RegisterHealthServer(grpcS, healthService)
   980  
   981  	versionpkg.RegisterVersionServiceServer(grpcS, server.serviceSet.VersionService)
   982  	clusterpkg.RegisterClusterServiceServer(grpcS, server.serviceSet.ClusterService)
   983  	applicationpkg.RegisterApplicationServiceServer(grpcS, server.serviceSet.ApplicationService)
   984  	applicationsetpkg.RegisterApplicationSetServiceServer(grpcS, server.serviceSet.ApplicationSetService)
   985  	notificationpkg.RegisterNotificationServiceServer(grpcS, server.serviceSet.NotificationService)
   986  	repositorypkg.RegisterRepositoryServiceServer(grpcS, server.serviceSet.RepoService)
   987  	repocredspkg.RegisterRepoCredsServiceServer(grpcS, server.serviceSet.RepoCredsService)
   988  	sessionpkg.RegisterSessionServiceServer(grpcS, server.serviceSet.SessionService)
   989  	settingspkg.RegisterSettingsServiceServer(grpcS, server.serviceSet.SettingsService)
   990  	projectpkg.RegisterProjectServiceServer(grpcS, server.serviceSet.ProjectService)
   991  	accountpkg.RegisterAccountServiceServer(grpcS, server.serviceSet.AccountService)
   992  	certificatepkg.RegisterCertificateServiceServer(grpcS, server.serviceSet.CertificateService)
   993  	gpgkeypkg.RegisterGPGKeyServiceServer(grpcS, server.serviceSet.GpgkeyService)
   994  	// Register reflection service on gRPC server.
   995  	reflection.Register(grpcS)
   996  	serverMetrics.InitializeMetrics(grpcS)
   997  	errorsutil.CheckError(server.serviceSet.ProjectService.NormalizeProjs())
   998  	return grpcS, server.serviceSet.AppResourceTreeFn
   999  }
  1000  
  1001  type ArgoCDServiceSet struct {
  1002  	ClusterService        *cluster.Server
  1003  	RepoService           *repository.Server
  1004  	RepoCredsService      *repocreds.Server
  1005  	SessionService        *session.Server
  1006  	ApplicationService    applicationpkg.ApplicationServiceServer
  1007  	AppResourceTreeFn     application.AppResourceTreeFn
  1008  	ApplicationSetService applicationsetpkg.ApplicationSetServiceServer
  1009  	ProjectService        *project.Server
  1010  	SettingsService       *settings.Server
  1011  	AccountService        *account.Server
  1012  	NotificationService   notificationpkg.NotificationServiceServer
  1013  	CertificateService    *certificate.Server
  1014  	GpgkeyService         *gpgkey.Server
  1015  	VersionService        *version.Server
  1016  }
  1017  
  1018  func newArgoCDServiceSet(a *ArgoCDServer) *ArgoCDServiceSet {
  1019  	kubectl := kubeutil.NewKubectl()
  1020  	clusterService := cluster.NewServer(a.db, a.enf, a.Cache, kubectl)
  1021  	repoService := repository.NewServer(a.RepoClientset, a.db, a.enf, a.Cache, a.appLister, a.projInformer, a.Namespace, a.settingsMgr, a.HydratorEnabled)
  1022  	repoCredsService := repocreds.NewServer(a.db, a.enf)
  1023  	var loginRateLimiter func() (utilio.Closer, error)
  1024  	if maxConcurrentLoginRequestsCount > 0 {
  1025  		loginRateLimiter = session.NewLoginRateLimiter(maxConcurrentLoginRequestsCount)
  1026  	}
  1027  	sessionService := session.NewServer(a.sessionMgr, a.settingsMgr, a, a.policyEnforcer, loginRateLimiter)
  1028  	projectLock := sync.NewKeyLock()
  1029  	applicationService, appResourceTreeFn := application.NewServer(
  1030  		a.Namespace,
  1031  		a.KubeClientset,
  1032  		a.AppClientset,
  1033  		a.appLister,
  1034  		a.appInformer,
  1035  		nil,
  1036  		a.RepoClientset,
  1037  		a.Cache,
  1038  		kubectl,
  1039  		a.db,
  1040  		a.enf,
  1041  		projectLock,
  1042  		a.settingsMgr,
  1043  		a.projInformer,
  1044  		a.ApplicationNamespaces,
  1045  		a.EnableK8sEvent,
  1046  		a.SyncWithReplaceAllowed,
  1047  	)
  1048  
  1049  	applicationSetService := applicationset.NewServer(
  1050  		a.db,
  1051  		a.KubeClientset,
  1052  		a.DynamicClientset,
  1053  		a.KubeControllerClientset,
  1054  		a.enf,
  1055  		a.RepoClientset,
  1056  		a.AppClientset,
  1057  		a.appsetInformer,
  1058  		a.appsetLister,
  1059  		a.Namespace,
  1060  		projectLock,
  1061  		a.ApplicationNamespaces,
  1062  		a.GitSubmoduleEnabled,
  1063  		a.EnableNewGitFileGlobbing,
  1064  		a.ScmRootCAPath,
  1065  		a.AllowedScmProviders,
  1066  		a.EnableScmProviders,
  1067  		a.EnableGitHubAPIMetrics,
  1068  		a.EnableK8sEvent,
  1069  	)
  1070  
  1071  	projectService := project.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.enf, projectLock, a.sessionMgr, a.policyEnforcer, a.projInformer, a.settingsMgr, a.db, a.EnableK8sEvent)
  1072  	appsInAnyNamespaceEnabled := len(a.ApplicationNamespaces) > 0
  1073  	settingsService := settings.NewServer(a.settingsMgr, a.RepoClientset, a, a.DisableAuth, appsInAnyNamespaceEnabled, a.HydratorEnabled)
  1074  	accountService := account.NewServer(a.sessionMgr, a.settingsMgr, a.enf)
  1075  
  1076  	notificationService := notification.NewServer(a.apiFactory)
  1077  	certificateService := certificate.NewServer(a.db, a.enf)
  1078  	gpgkeyService := gpgkey.NewServer(a.db, a.enf)
  1079  	versionService := version.NewServer(a, func() (bool, error) {
  1080  		if a.DisableAuth {
  1081  			return true, nil
  1082  		}
  1083  		sett, err := a.settingsMgr.GetSettings()
  1084  		if err != nil {
  1085  			return false, err
  1086  		}
  1087  		return sett.AnonymousUserEnabled, err
  1088  	})
  1089  
  1090  	return &ArgoCDServiceSet{
  1091  		ClusterService:        clusterService,
  1092  		RepoService:           repoService,
  1093  		RepoCredsService:      repoCredsService,
  1094  		SessionService:        sessionService,
  1095  		ApplicationService:    applicationService,
  1096  		AppResourceTreeFn:     appResourceTreeFn,
  1097  		ApplicationSetService: applicationSetService,
  1098  		ProjectService:        projectService,
  1099  		SettingsService:       settingsService,
  1100  		AccountService:        accountService,
  1101  		NotificationService:   notificationService,
  1102  		CertificateService:    certificateService,
  1103  		GpgkeyService:         gpgkeyService,
  1104  		VersionService:        versionService,
  1105  	}
  1106  }
  1107  
  1108  // translateGrpcCookieHeader conditionally sets a cookie on the response.
  1109  func (server *ArgoCDServer) translateGrpcCookieHeader(ctx context.Context, w http.ResponseWriter, resp golang_proto.Message) error {
  1110  	if sessionResp, ok := resp.(*sessionpkg.SessionResponse); ok {
  1111  		token := sessionResp.Token
  1112  		err := server.setTokenCookie(token, w)
  1113  		if err != nil {
  1114  			return fmt.Errorf("error setting token cookie from session response: %w", err)
  1115  		}
  1116  	} else if md, ok := runtime.ServerMetadataFromContext(ctx); ok {
  1117  		renewToken := md.HeaderMD[renewTokenKey]
  1118  		if len(renewToken) > 0 {
  1119  			return server.setTokenCookie(renewToken[0], w)
  1120  		}
  1121  	}
  1122  
  1123  	return nil
  1124  }
  1125  
  1126  func (server *ArgoCDServer) setTokenCookie(token string, w http.ResponseWriter) error {
  1127  	cookiePath := "path=/" + strings.TrimRight(strings.TrimLeft(server.BaseHRef, "/"), "/")
  1128  	flags := []string{cookiePath, "SameSite=lax", "httpOnly"}
  1129  	if !server.Insecure {
  1130  		flags = append(flags, "Secure")
  1131  	}
  1132  	cookies, err := httputil.MakeCookieMetadata(common.AuthCookieName, token, flags...)
  1133  	if err != nil {
  1134  		return fmt.Errorf("error creating cookie metadata: %w", err)
  1135  	}
  1136  	for _, cookie := range cookies {
  1137  		w.Header().Add("Set-Cookie", cookie)
  1138  	}
  1139  	return nil
  1140  }
  1141  
  1142  func withRootPath(handler http.Handler, a *ArgoCDServer) http.Handler {
  1143  	// If RootPath is empty, directly return the original handler
  1144  	if a.RootPath == "" {
  1145  		return handler
  1146  	}
  1147  
  1148  	// get rid of slashes
  1149  	root := strings.Trim(a.RootPath, "/")
  1150  
  1151  	mux := http.NewServeMux()
  1152  	mux.Handle("/"+root+"/", http.StripPrefix("/"+root, handler))
  1153  
  1154  	healthz.ServeHealthCheck(mux, a.healthCheck)
  1155  
  1156  	return mux
  1157  }
  1158  
  1159  func compressHandler(handler http.Handler) http.Handler {
  1160  	compr := handlers.CompressHandler(handler)
  1161  	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
  1162  		if request.Header.Get("Accept") == "text/event-stream" {
  1163  			handler.ServeHTTP(writer, request)
  1164  		} else {
  1165  			compr.ServeHTTP(writer, request)
  1166  		}
  1167  	})
  1168  }
  1169  
  1170  // newHTTPServer returns the HTTP server to serve HTTP/HTTPS requests. This is implemented
  1171  // using grpc-gateway as a proxy to the gRPC server.
  1172  func (server *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandler http.Handler, appResourceTreeFn application.AppResourceTreeFn, conn *grpc.ClientConn, metricsReg HTTPMetricsRegistry) *http.Server {
  1173  	endpoint := fmt.Sprintf("localhost:%d", port)
  1174  	mux := http.NewServeMux()
  1175  	httpS := http.Server{
  1176  		Addr: endpoint,
  1177  		Handler: &handlerSwitcher{
  1178  			handler: mux,
  1179  			urlToHandler: map[string]http.Handler{
  1180  				"/api/badge":          badge.NewHandler(server.AppClientset, server.settingsMgr, server.Namespace, server.ApplicationNamespaces),
  1181  				common.LogoutEndpoint: logout.NewHandler(server.settingsMgr, server.sessionMgr, server.RootPath, server.BaseHRef),
  1182  			},
  1183  			contentTypeToHandler: map[string]http.Handler{
  1184  				"application/grpc-web+proto": grpcWebHandler,
  1185  			},
  1186  		},
  1187  	}
  1188  
  1189  	// HTTP 1.1+JSON Server
  1190  	// grpc-ecosystem/grpc-gateway is used to proxy HTTP requests to the corresponding gRPC call
  1191  	// NOTE: if a marshaller option is not supplied, grpc-gateway will default to the jsonpb from
  1192  	// golang/protobuf. Which does not support types such as time.Time. gogo/protobuf does support
  1193  	// time.Time, but does not support custom UnmarshalJSON() and MarshalJSON() methods. Therefore
  1194  	// we use our own Marshaler
  1195  	gwMuxOpts := runtime.WithMarshalerOption(runtime.MIMEWildcard, new(grpc_util.JSONMarshaler))
  1196  	gwCookieOpts := runtime.WithForwardResponseOption(server.translateGrpcCookieHeader)
  1197  	gwmux := runtime.NewServeMux(gwMuxOpts, gwCookieOpts)
  1198  
  1199  	var handler http.Handler = gwmux
  1200  	if server.EnableGZip {
  1201  		handler = compressHandler(handler)
  1202  	}
  1203  	// withTracingHandler is a middleware that extracts OpenTelemetry trace context from HTTP headers
  1204  	// and injects it into the request context. This enables trace context propagation from HTTP clients
  1205  	// to gRPC services, allowing for better distributed tracing across the ArgoCD server.
  1206  	withTracingHandler := func(h http.Handler) http.Handler {
  1207  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1208  			propagator := otel.GetTextMapPropagator()
  1209  			ctx := propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
  1210  			h.ServeHTTP(w, r.WithContext(ctx))
  1211  		})
  1212  	}
  1213  	handler = withTracingHandler(handler)
  1214  	if len(server.ContentTypes) > 0 {
  1215  		handler = enforceContentTypes(handler, server.ContentTypes)
  1216  	} else {
  1217  		log.WithField(common.SecurityField, common.SecurityHigh).Warnf("Content-Type enforcement is disabled, which may make your API vulnerable to CSRF attacks")
  1218  	}
  1219  	mux.Handle("/api/", handler)
  1220  
  1221  	terminalOpts := application.TerminalOptions{DisableAuth: server.DisableAuth, Enf: server.enf}
  1222  
  1223  	terminal := application.NewHandler(server.appLister, server.Namespace, server.ApplicationNamespaces, server.db, appResourceTreeFn, server.settings.ExecShells, server.sessionMgr, &terminalOpts).
  1224  		WithFeatureFlagMiddleware(server.settingsMgr.GetSettings)
  1225  	th := util_session.WithAuthMiddleware(server.DisableAuth, server.sessionMgr, terminal)
  1226  	mux.Handle("/terminal", th)
  1227  
  1228  	// Proxy extension is currently an alpha feature and is disabled
  1229  	// by default.
  1230  	if server.EnableProxyExtension {
  1231  		// API server won't panic if extensions fail to register. In
  1232  		// this case an error log will be sent and no extension route
  1233  		// will be added in mux.
  1234  		registerExtensions(mux, server, metricsReg)
  1235  	}
  1236  
  1237  	mustRegisterGWHandler(ctx, versionpkg.RegisterVersionServiceHandler, gwmux, conn)
  1238  	mustRegisterGWHandler(ctx, clusterpkg.RegisterClusterServiceHandler, gwmux, conn)
  1239  	mustRegisterGWHandler(ctx, applicationpkg.RegisterApplicationServiceHandler, gwmux, conn)
  1240  	mustRegisterGWHandler(ctx, applicationsetpkg.RegisterApplicationSetServiceHandler, gwmux, conn)
  1241  	mustRegisterGWHandler(ctx, notificationpkg.RegisterNotificationServiceHandler, gwmux, conn)
  1242  	mustRegisterGWHandler(ctx, repositorypkg.RegisterRepositoryServiceHandler, gwmux, conn)
  1243  	mustRegisterGWHandler(ctx, repocredspkg.RegisterRepoCredsServiceHandler, gwmux, conn)
  1244  	mustRegisterGWHandler(ctx, sessionpkg.RegisterSessionServiceHandler, gwmux, conn)
  1245  	mustRegisterGWHandler(ctx, settingspkg.RegisterSettingsServiceHandler, gwmux, conn)
  1246  	mustRegisterGWHandler(ctx, projectpkg.RegisterProjectServiceHandler, gwmux, conn)
  1247  	mustRegisterGWHandler(ctx, accountpkg.RegisterAccountServiceHandler, gwmux, conn)
  1248  	mustRegisterGWHandler(ctx, certificatepkg.RegisterCertificateServiceHandler, gwmux, conn)
  1249  	mustRegisterGWHandler(ctx, gpgkeypkg.RegisterGPGKeyServiceHandler, gwmux, conn)
  1250  
  1251  	// Swagger UI
  1252  	swagger.ServeSwaggerUI(mux, assets.SwaggerJSON, "/swagger-ui", server.RootPath)
  1253  	healthz.ServeHealthCheck(mux, server.healthCheck)
  1254  
  1255  	// Dex reverse proxy and client app and OAuth2 login/callback
  1256  	server.registerDexHandlers(mux)
  1257  
  1258  	// Webhook handler for git events (Note: cache timeouts are hardcoded because API server does not write to cache and not really using them)
  1259  	argoDB := db.NewDB(server.Namespace, server.settingsMgr, server.KubeClientset)
  1260  	acdWebhookHandler := webhook.NewHandler(server.Namespace, server.ApplicationNamespaces, server.WebhookParallelism, server.AppClientset, server.appLister, server.settings, server.settingsMgr, server.RepoServerCache, server.Cache, argoDB, server.settingsMgr.GetMaxWebhookPayloadSize())
  1261  
  1262  	mux.HandleFunc("/api/webhook", acdWebhookHandler.Handler)
  1263  
  1264  	// Serve cli binaries directly from API server
  1265  	registerDownloadHandlers(mux, "/download")
  1266  
  1267  	// Serve extensions
  1268  	extensionsSharedPath := "/tmp/extensions/"
  1269  
  1270  	var extensionsHandler http.Handler = http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
  1271  		server.serveExtensions(extensionsSharedPath, writer)
  1272  	})
  1273  	if server.EnableGZip {
  1274  		extensionsHandler = compressHandler(extensionsHandler)
  1275  	}
  1276  	mux.Handle("/extensions.js", extensionsHandler)
  1277  
  1278  	// Serve UI static assets
  1279  	var assetsHandler http.Handler = http.HandlerFunc(server.newStaticAssetsHandler())
  1280  	if server.EnableGZip {
  1281  		assetsHandler = compressHandler(assetsHandler)
  1282  	}
  1283  	mux.Handle("/", assetsHandler)
  1284  	return &httpS
  1285  }
  1286  
  1287  func enforceContentTypes(handler http.Handler, types []string) http.Handler {
  1288  	allowedTypes := map[string]bool{}
  1289  	for _, t := range types {
  1290  		allowedTypes[strings.ToLower(t)] = true
  1291  	}
  1292  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1293  		if r.Method == http.MethodGet || allowedTypes[strings.ToLower(r.Header.Get("Content-Type"))] {
  1294  			handler.ServeHTTP(w, r)
  1295  		} else {
  1296  			http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType)
  1297  		}
  1298  	})
  1299  }
  1300  
  1301  // registerExtensions will try to register all configured extensions
  1302  // in the given mux. If any error is returned while registering
  1303  // extensions handlers, no route will be added in the given mux.
  1304  func registerExtensions(mux *http.ServeMux, a *ArgoCDServer, metricsReg HTTPMetricsRegistry) {
  1305  	a.log.Info("Registering extensions...")
  1306  	extHandler := http.HandlerFunc(a.extensionManager.CallExtension())
  1307  	authMiddleware := a.sessionMgr.AuthMiddlewareFunc(a.DisableAuth)
  1308  	// auth middleware ensures that requests to all extensions are authenticated first
  1309  	mux.Handle(extension.URLPrefix+"/", authMiddleware(extHandler))
  1310  
  1311  	a.extensionManager.AddMetricsRegistry(metricsReg)
  1312  
  1313  	err := a.extensionManager.RegisterExtensions()
  1314  	if err != nil {
  1315  		a.log.Errorf("Error registering extensions: %s", err)
  1316  	}
  1317  }
  1318  
  1319  var extensionsPattern = regexp.MustCompile(`^extension(.*)\.js$`)
  1320  
  1321  func (server *ArgoCDServer) serveExtensions(extensionsSharedPath string, w http.ResponseWriter) {
  1322  	w.Header().Set("Content-Type", "application/javascript")
  1323  
  1324  	err := filepath.Walk(extensionsSharedPath, func(filePath string, info os.FileInfo, err error) error {
  1325  		if err != nil {
  1326  			return fmt.Errorf("failed to iterate files in '%s': %w", extensionsSharedPath, err)
  1327  		}
  1328  		if !files.IsSymlink(info) && !info.IsDir() && extensionsPattern.MatchString(info.Name()) {
  1329  			processFile := func() error {
  1330  				if _, err = fmt.Fprintf(w, "// source: %s/%s \n", filePath, info.Name()); err != nil {
  1331  					return fmt.Errorf("failed to write to response: %w", err)
  1332  				}
  1333  
  1334  				f, err := os.Open(filePath)
  1335  				if err != nil {
  1336  					return fmt.Errorf("failed to open file '%s': %w", filePath, err)
  1337  				}
  1338  				defer utilio.Close(f)
  1339  
  1340  				if _, err := goio.Copy(w, f); err != nil {
  1341  					return fmt.Errorf("failed to copy file '%s': %w", filePath, err)
  1342  				}
  1343  
  1344  				return nil
  1345  			}
  1346  
  1347  			if processFile() != nil {
  1348  				return fmt.Errorf("failed to serve extension file '%s': %w", filePath, processFile())
  1349  			}
  1350  		}
  1351  		return nil
  1352  	})
  1353  
  1354  	if err != nil && !errors.Is(err, fs.ErrNotExist) {
  1355  		log.Errorf("Failed to walk extensions directory: %v", err)
  1356  		http.Error(w, "Internal error", http.StatusInternalServerError)
  1357  		return
  1358  	}
  1359  }
  1360  
  1361  // registerDexHandlers will register dex HTTP handlers, creating the OAuth client app
  1362  func (server *ArgoCDServer) registerDexHandlers(mux *http.ServeMux) {
  1363  	if !server.settings.IsSSOConfigured() {
  1364  		return
  1365  	}
  1366  	// Run dex OpenID Connect Identity Provider behind a reverse proxy (served at /api/dex)
  1367  	var err error
  1368  	mux.HandleFunc(common.DexAPIEndpoint+"/", dexutil.NewDexHTTPReverseProxy(server.DexServerAddr, server.BaseHRef, server.DexTLSConfig))
  1369  	server.ssoClientApp, err = oidc.NewClientApp(server.settings, server.DexServerAddr, server.DexTLSConfig, server.BaseHRef, cacheutil.NewRedisCache(server.RedisClient, server.settings.UserInfoCacheExpiration(), cacheutil.RedisCompressionNone))
  1370  	errorsutil.CheckError(err)
  1371  	mux.HandleFunc(common.LoginEndpoint, server.ssoClientApp.HandleLogin)
  1372  	mux.HandleFunc(common.CallbackEndpoint, server.ssoClientApp.HandleCallback)
  1373  }
  1374  
  1375  // newRedirectServer returns an HTTP server which does a 307 redirect to the HTTPS server
  1376  func newRedirectServer(port int, rootPath string) *http.Server {
  1377  	var addr string
  1378  	if rootPath == "" {
  1379  		addr = fmt.Sprintf("localhost:%d", port)
  1380  	} else {
  1381  		addr = fmt.Sprintf("localhost:%d/%s", port, strings.Trim(rootPath, "/"))
  1382  	}
  1383  
  1384  	return &http.Server{
  1385  		Addr: addr,
  1386  		Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1387  			target := "https://" + req.Host
  1388  
  1389  			if rootPath != "" {
  1390  				root := strings.Trim(rootPath, "/")
  1391  				prefix := "/" + root
  1392  
  1393  				// If the request path already starts with rootPath, no need to add rootPath again
  1394  				if strings.HasPrefix(req.URL.Path, prefix) {
  1395  					target += req.URL.Path
  1396  				} else {
  1397  					target += prefix + req.URL.Path
  1398  				}
  1399  			} else {
  1400  				target += req.URL.Path
  1401  			}
  1402  
  1403  			if req.URL.RawQuery != "" {
  1404  				target += "?" + req.URL.RawQuery
  1405  			}
  1406  			http.Redirect(w, req, target, http.StatusTemporaryRedirect)
  1407  		}),
  1408  	}
  1409  }
  1410  
  1411  // registerDownloadHandlers registers HTTP handlers to support downloads directly from the API server
  1412  // (e.g. argocd CLI)
  1413  func registerDownloadHandlers(mux *http.ServeMux, base string) {
  1414  	linuxPath, err := exec.LookPath("argocd")
  1415  	if err != nil {
  1416  		log.Warnf("argocd not in PATH")
  1417  	} else {
  1418  		mux.HandleFunc(base+"/argocd-linux-"+go_runtime.GOARCH, func(w http.ResponseWriter, r *http.Request) {
  1419  			http.ServeFile(w, r, linuxPath)
  1420  		})
  1421  	}
  1422  }
  1423  
  1424  func (server *ArgoCDServer) getIndexData() ([]byte, error) {
  1425  	server.indexDataInit.Do(func() {
  1426  		data, err := ui.Embedded.ReadFile("dist/app/index.html")
  1427  		if err != nil {
  1428  			server.indexDataErr = err
  1429  			return
  1430  		}
  1431  		if server.BaseHRef == "/" || server.BaseHRef == "" {
  1432  			server.indexData = data
  1433  		} else {
  1434  			server.indexData = []byte(replaceBaseHRef(string(data), fmt.Sprintf(`<base href="/%s/">`, strings.Trim(server.BaseHRef, "/"))))
  1435  		}
  1436  	})
  1437  
  1438  	return server.indexData, server.indexDataErr
  1439  }
  1440  
  1441  func (server *ArgoCDServer) uiAssetExists(filename string) bool {
  1442  	f, err := server.staticAssets.Open(strings.Trim(filename, "/"))
  1443  	if err != nil {
  1444  		return false
  1445  	}
  1446  	defer utilio.Close(f)
  1447  	stat, err := f.Stat()
  1448  	if err != nil {
  1449  		return false
  1450  	}
  1451  	return !stat.IsDir()
  1452  }
  1453  
  1454  // newStaticAssetsHandler returns an HTTP handler to serve UI static assets
  1455  func (server *ArgoCDServer) newStaticAssetsHandler() func(http.ResponseWriter, *http.Request) {
  1456  	return func(w http.ResponseWriter, r *http.Request) {
  1457  		acceptHTML := false
  1458  		for _, acceptType := range strings.Split(r.Header.Get("Accept"), ",") {
  1459  			if acceptType == "text/html" || acceptType == "html" {
  1460  				acceptHTML = true
  1461  				break
  1462  			}
  1463  		}
  1464  
  1465  		fileRequest := r.URL.Path != "/index.html" && server.uiAssetExists(r.URL.Path)
  1466  
  1467  		// Set X-Frame-Options according to configuration
  1468  		if server.XFrameOptions != "" {
  1469  			w.Header().Set("X-Frame-Options", server.XFrameOptions)
  1470  		}
  1471  		// Set Content-Security-Policy according to configuration
  1472  		if server.ContentSecurityPolicy != "" {
  1473  			w.Header().Set("Content-Security-Policy", server.ContentSecurityPolicy)
  1474  		}
  1475  		w.Header().Set("X-XSS-Protection", "1")
  1476  
  1477  		// serve index.html for non file requests to support HTML5 History API
  1478  		if acceptHTML && !fileRequest && (r.Method == http.MethodGet || r.Method == http.MethodHead) {
  1479  			for k, v := range noCacheHeaders {
  1480  				w.Header().Set(k, v)
  1481  			}
  1482  			data, err := server.getIndexData()
  1483  			if err != nil {
  1484  				http.Error(w, err.Error(), http.StatusInternalServerError)
  1485  				return
  1486  			}
  1487  
  1488  			modTime, err := time.Parse(common.GetVersion().BuildDate, time.RFC3339)
  1489  			if err != nil {
  1490  				modTime = time.Now()
  1491  			}
  1492  			http.ServeContent(w, r, "index.html", modTime, utilio.NewByteReadSeeker(data))
  1493  		} else {
  1494  			if isMainJsBundle(r.URL) {
  1495  				cacheControl := "public, max-age=31536000, immutable"
  1496  				if !fileRequest {
  1497  					cacheControl = "no-cache"
  1498  				}
  1499  				w.Header().Set("Cache-Control", cacheControl)
  1500  			}
  1501  			http.FileServer(server.staticAssets).ServeHTTP(w, r)
  1502  		}
  1503  	}
  1504  }
  1505  
  1506  var mainJsBundleRegex = regexp.MustCompile(`^main\.[0-9a-f]{20}\.js$`)
  1507  
  1508  func isMainJsBundle(url *url.URL) bool {
  1509  	filename := path.Base(url.Path)
  1510  	return mainJsBundleRegex.MatchString(filename)
  1511  }
  1512  
  1513  type registerFunc func(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error
  1514  
  1515  // mustRegisterGWHandler is a convenience function to register a gateway handler
  1516  func mustRegisterGWHandler(ctx context.Context, register registerFunc, mux *runtime.ServeMux, conn *grpc.ClientConn) {
  1517  	err := register(ctx, mux, conn)
  1518  	if err != nil {
  1519  		panic(err)
  1520  	}
  1521  }
  1522  
  1523  func replaceBaseHRef(data string, replaceWith string) string {
  1524  	return baseHRefRegex.ReplaceAllString(data, replaceWith)
  1525  }
  1526  
  1527  // Authenticate checks for the presence of a valid token when accessing server-side resources.
  1528  func (server *ArgoCDServer) Authenticate(ctx context.Context) (context.Context, error) {
  1529  	if server.DisableAuth {
  1530  		return ctx, nil
  1531  	}
  1532  	claims, newToken, claimsErr := server.getClaims(ctx)
  1533  	if claims != nil {
  1534  		// Add claims to the context to inspect for RBAC
  1535  		//nolint:staticcheck
  1536  		ctx = context.WithValue(ctx, "claims", claims)
  1537  		if newToken != "" {
  1538  			// Session tokens that are expiring soon should be regenerated if user stays active.
  1539  			// The renewed token is stored in outgoing ServerMetadata. Metadata is available to grpc-gateway
  1540  			// response forwarder that will translate it into Set-Cookie header.
  1541  			if err := grpc.SendHeader(ctx, metadata.New(map[string]string{renewTokenKey: newToken})); err != nil {
  1542  				log.Warnf("Failed to set %s header", renewTokenKey)
  1543  			}
  1544  		}
  1545  	}
  1546  	if claimsErr != nil {
  1547  		//nolint:staticcheck
  1548  		ctx = context.WithValue(ctx, util_session.AuthErrorCtxKey, claimsErr)
  1549  	}
  1550  
  1551  	if claimsErr != nil {
  1552  		argoCDSettings, err := server.settingsMgr.GetSettings()
  1553  		if err != nil {
  1554  			return ctx, status.Errorf(codes.Internal, "unable to load settings: %v", err)
  1555  		}
  1556  		if !argoCDSettings.AnonymousUserEnabled {
  1557  			return ctx, claimsErr
  1558  		}
  1559  		//nolint:staticcheck
  1560  		ctx = context.WithValue(ctx, "claims", "")
  1561  	}
  1562  
  1563  	return ctx, nil
  1564  }
  1565  
  1566  func (server *ArgoCDServer) getClaims(ctx context.Context) (jwt.Claims, string, error) {
  1567  	md, ok := metadata.FromIncomingContext(ctx)
  1568  	if !ok {
  1569  		return nil, "", ErrNoSession
  1570  	}
  1571  	tokenString := getToken(md)
  1572  	if tokenString == "" {
  1573  		return nil, "", ErrNoSession
  1574  	}
  1575  	claims, newToken, err := server.sessionMgr.VerifyToken(tokenString)
  1576  	if err != nil {
  1577  		return claims, "", status.Errorf(codes.Unauthenticated, "invalid session: %v", err)
  1578  	}
  1579  
  1580  	// Some SSO implementations (Okta) require a call to
  1581  	// the OIDC user info path to get attributes like groups
  1582  	// we assume that everywhere in argocd jwt.MapClaims is used as type for interface jwt.Claims
  1583  	// otherwise this would cause a panic
  1584  	var groupClaims jwt.MapClaims
  1585  	if groupClaims, ok = claims.(jwt.MapClaims); !ok {
  1586  		if tmpClaims, ok := claims.(*jwt.MapClaims); ok {
  1587  			groupClaims = *tmpClaims
  1588  		}
  1589  	}
  1590  	iss := jwtutil.StringField(groupClaims, "iss")
  1591  	if iss != util_session.SessionManagerClaimsIssuer && server.settings.UserInfoGroupsEnabled() && server.settings.UserInfoPath() != "" {
  1592  		userInfo, unauthorized, err := server.ssoClientApp.GetUserInfo(groupClaims, server.settings.IssuerURL(), server.settings.UserInfoPath())
  1593  		if unauthorized {
  1594  			log.Errorf("error while quering userinfo endpoint: %v", err)
  1595  			return claims, "", status.Errorf(codes.Unauthenticated, "invalid session")
  1596  		}
  1597  		if err != nil {
  1598  			log.Errorf("error fetching user info endpoint: %v", err)
  1599  			return claims, "", status.Errorf(codes.Internal, "invalid userinfo response")
  1600  		}
  1601  		if groupClaims["sub"] != userInfo["sub"] {
  1602  			return claims, "", status.Error(codes.Unknown, "subject of claims from user info endpoint didn't match subject of idToken, see https://openid.net/specs/openid-connect-core-1_0.html#UserInfo")
  1603  		}
  1604  		groupClaims["groups"] = userInfo["groups"]
  1605  	}
  1606  
  1607  	return groupClaims, newToken, nil
  1608  }
  1609  
  1610  // getToken extracts the token from gRPC metadata or cookie headers
  1611  func getToken(md metadata.MD) string {
  1612  	// check the "token" metadata
  1613  	{
  1614  		tokens, ok := md[apiclient.MetaDataTokenKey]
  1615  		if ok && len(tokens) > 0 {
  1616  			return tokens[0]
  1617  		}
  1618  	}
  1619  
  1620  	// looks for the HTTP header `Authorization: Bearer ...`
  1621  	// argocd prefers bearer token over cookie
  1622  	for _, t := range md["authorization"] {
  1623  		token := strings.TrimPrefix(t, "Bearer ")
  1624  		if strings.HasPrefix(t, "Bearer ") && jwtutil.IsValid(token) {
  1625  			return token
  1626  		}
  1627  	}
  1628  
  1629  	// check the HTTP cookie
  1630  	for _, t := range md["grpcgateway-cookie"] {
  1631  		header := http.Header{}
  1632  		header.Add("Cookie", t)
  1633  		request := http.Request{Header: header}
  1634  		token, err := httputil.JoinCookies(common.AuthCookieName, request.Cookies())
  1635  		if err == nil && jwtutil.IsValid(token) {
  1636  			return token
  1637  		}
  1638  	}
  1639  
  1640  	return ""
  1641  }
  1642  
  1643  type handlerSwitcher struct {
  1644  	handler              http.Handler
  1645  	urlToHandler         map[string]http.Handler
  1646  	contentTypeToHandler map[string]http.Handler
  1647  }
  1648  
  1649  func (s *handlerSwitcher) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  1650  	if urlHandler, ok := s.urlToHandler[r.URL.Path]; ok {
  1651  		urlHandler.ServeHTTP(w, r)
  1652  	} else if contentHandler, ok := s.contentTypeToHandler[r.Header.Get("content-type")]; ok {
  1653  		contentHandler.ServeHTTP(w, r)
  1654  	} else {
  1655  		s.handler.ServeHTTP(w, r)
  1656  	}
  1657  }
  1658  
  1659  // Workaround for https://github.com/golang/go/issues/21955 to support escaped URLs in URL path.
  1660  type bug21955Workaround struct {
  1661  	handler http.Handler
  1662  }
  1663  
  1664  var pathPatters = []*regexp.Regexp{
  1665  	regexp.MustCompile(`/api/v1/clusters/[^/]+`),
  1666  	regexp.MustCompile(`/api/v1/repositories/[^/]+`),
  1667  	regexp.MustCompile(`/api/v1/repocreds/[^/]+`),
  1668  	regexp.MustCompile(`/api/v1/repositories/[^/]+/apps`),
  1669  	regexp.MustCompile(`/api/v1/repositories/[^/]+/apps/[^/]+`),
  1670  	regexp.MustCompile(`/settings/clusters/[^/]+`),
  1671  }
  1672  
  1673  func (bf *bug21955Workaround) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  1674  	for _, pattern := range pathPatters {
  1675  		if pattern.MatchString(r.URL.RawPath) {
  1676  			r.URL.Path = r.URL.RawPath
  1677  			break
  1678  		}
  1679  	}
  1680  	bf.handler.ServeHTTP(w, r)
  1681  }
  1682  
  1683  func bug21955WorkaroundInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
  1684  	switch req := req.(type) {
  1685  	case *repositorypkg.RepoQuery:
  1686  		repo, err := url.QueryUnescape(req.Repo)
  1687  		if err != nil {
  1688  			return nil, err
  1689  		}
  1690  		req.Repo = repo
  1691  	case *repositorypkg.RepoAppsQuery:
  1692  		repo, err := url.QueryUnescape(req.Repo)
  1693  		if err != nil {
  1694  			return nil, err
  1695  		}
  1696  		req.Repo = repo
  1697  	case *repositorypkg.RepoAppDetailsQuery:
  1698  		repo, err := url.QueryUnescape(req.Source.RepoURL)
  1699  		if err != nil {
  1700  			return nil, err
  1701  		}
  1702  		req.Source.RepoURL = repo
  1703  	case *repositorypkg.RepoUpdateRequest:
  1704  		repo, err := url.QueryUnescape(req.Repo.Repo)
  1705  		if err != nil {
  1706  			return nil, err
  1707  		}
  1708  		req.Repo.Repo = repo
  1709  	case *repocredspkg.RepoCredsQuery:
  1710  		pattern, err := url.QueryUnescape(req.Url)
  1711  		if err != nil {
  1712  			return nil, err
  1713  		}
  1714  		req.Url = pattern
  1715  	case *repocredspkg.RepoCredsDeleteRequest:
  1716  		pattern, err := url.QueryUnescape(req.Url)
  1717  		if err != nil {
  1718  			return nil, err
  1719  		}
  1720  		req.Url = pattern
  1721  	case *clusterpkg.ClusterQuery:
  1722  		if req.Id != nil {
  1723  			val, err := url.QueryUnescape(req.Id.Value)
  1724  			if err != nil {
  1725  				return nil, err
  1726  			}
  1727  			req.Id.Value = val
  1728  		}
  1729  	case *clusterpkg.ClusterUpdateRequest:
  1730  		if req.Id != nil {
  1731  			val, err := url.QueryUnescape(req.Id.Value)
  1732  			if err != nil {
  1733  				return nil, err
  1734  			}
  1735  			req.Id.Value = val
  1736  		}
  1737  	}
  1738  	return handler(ctx, req)
  1739  }
  1740  
  1741  // allowedApplicationNamespacesAsString returns a string containing comma-separated list
  1742  // of allowed application namespaces
  1743  func (server *ArgoCDServer) allowedApplicationNamespacesAsString() string {
  1744  	ns := server.Namespace
  1745  	if len(server.ApplicationNamespaces) > 0 {
  1746  		ns += ", "
  1747  		ns += strings.Join(server.ApplicationNamespaces, ", ")
  1748  	}
  1749  	return ns
  1750  }