github.com/argoproj/argo-cd/v2@v2.10.9/server/server.go (about)

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