diff --git a/cmd/argocd-server/commands/argocd_server.go b/cmd/argocd-server/commands/argocd_server.go index b9ef17a79c83..2a1db3f45e32 100644 --- a/cmd/argocd-server/commands/argocd_server.go +++ b/cmd/argocd-server/commands/argocd_server.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math" + "strings" "time" "github.com/argoproj/pkg/stats" @@ -62,6 +63,7 @@ func NewCommand() *cobra.Command { repoServerAddress string dexServerAddress string disableAuth bool + contentTypes string enableGZip bool tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error) cacheSrc func() (*servercache.Cache, error) @@ -181,6 +183,7 @@ func NewCommand() *cobra.Command { DexServerAddr: dexServerAddress, DexTLSConfig: dexTlsConfig, DisableAuth: disableAuth, + ContentTypes: strings.Split(contentTypes, ";"), EnableGZip: enableGZip, TLSConfigCustomizer: tlsConfigCustomizer, Cache: cache, @@ -228,6 +231,7 @@ func NewCommand() *cobra.Command { command.Flags().StringVar(&repoServerAddress, "repo-server", env.StringFromEnv("ARGOCD_SERVER_REPO_SERVER", common.DefaultRepoServerAddr), "Repo server address") command.Flags().StringVar(&dexServerAddress, "dex-server", env.StringFromEnv("ARGOCD_SERVER_DEX_SERVER", common.DefaultDexServerAddr), "Dex server address") command.Flags().BoolVar(&disableAuth, "disable-auth", env.ParseBoolFromEnv("ARGOCD_SERVER_DISABLE_AUTH", false), "Disable client authentication") + command.Flags().StringVar(&contentTypes, "api-content-types", "application/json", "Semicolon separated list of allowed content types for non GET api requests. Any content type is allowed if empty.") command.Flags().BoolVar(&enableGZip, "enable-gzip", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_GZIP", true), "Enable GZIP compression") command.AddCommand(cli.NewVersionCmd(cliName)) command.Flags().StringVar(&listenHost, "address", env.StringFromEnv("ARGOCD_SERVER_LISTEN_ADDRESS", common.DefaultAddressAPIServer), "Listen on given address") diff --git a/docs/operator-manual/server-commands/argocd-server.md b/docs/operator-manual/server-commands/argocd-server.md index e120c757b2ec..e498f8a73605 100644 --- a/docs/operator-manual/server-commands/argocd-server.md +++ b/docs/operator-manual/server-commands/argocd-server.md @@ -14,6 +14,7 @@ argocd-server [flags] ``` --address string Listen on given address (default "0.0.0.0") + --api-content-types string Semicolon separated list of allowed content types for non GET api requests. Any content type is allowed if empty. (default "application/json") --app-state-cache-expiration duration Cache expiration for app state (default 1h0m0s) --application-namespaces strings List of additional namespaces where application resources can be managed in --as string Username to impersonate for the operation diff --git a/server/server.go b/server/server.go index a3c6846c70e8..7334abe4a17b 100644 --- a/server/server.go +++ b/server/server.go @@ -199,6 +199,7 @@ type ArgoCDServer struct { type ArgoCDServerOpts struct { DisableAuth bool + ContentTypes []string EnableGZip bool Insecure bool StaticAssetsDir string @@ -974,6 +975,9 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl if a.EnableGZip { handler = compressHandler(handler) } + if len(a.ContentTypes) > 0 { + handler = enforceContentTypes(handler, a.ContentTypes) + } mux.Handle("/api/", handler) terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells, *a.sessionMgr). @@ -1040,6 +1044,20 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl return &httpS } +func enforceContentTypes(handler http.Handler, types []string) http.Handler { + allowedTypes := map[string]bool{} + for _, t := range types { + allowedTypes[strings.ToLower(t)] = true + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet || allowedTypes[strings.ToLower(r.Header.Get("Content-Type"))] { + handler.ServeHTTP(w, r) + } else { + http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) + } + }) +} + // registerExtensions will try to register all configured extensions // in the given mux. If any error is returned while registering // extensions handlers, no route will be added in the given mux. diff --git a/test/e2e/fixture/http.go b/test/e2e/fixture/http.go index 58c56046d385..00c123ab5d89 100644 --- a/test/e2e/fixture/http.go +++ b/test/e2e/fixture/http.go @@ -2,6 +2,7 @@ package fixture import ( "bytes" + "crypto/tls" "encoding/json" "io" "net/http" @@ -27,7 +28,15 @@ func DoHttpRequest(method string, path string, data ...byte) (*http.Response, er return nil, err } req.AddCookie(&http.Cookie{Name: common.AuthCookieName, Value: token}) - return http.DefaultClient.Do(req) + req.Header.Set("Content-Type", "application/json") + + httpClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: IsRemote()}, + }, + } + + return httpClient.Do(req) } // DoHttpJsonRequest executes a http request against the Argo CD API server and unmarshals the response body as JSON