From 9607fdd78c4c4bc08d4005ce6d9451f2f8bbbbe3 Mon Sep 17 00:00:00 2001 From: Andrew Burke <31974658+atburke@users.noreply.github.com> Date: Mon, 15 Aug 2022 14:28:24 -0700 Subject: [PATCH] Allow reverse tunnel join without exposing the web API (#13598) This change allows agents to join over a reverse tunnel (port 3024 by default) only, instead of also requiring access to the web API (port 3080). --- integration/helpers/instance.go | 8 +- integration/integration_test.go | 31 ++++ lib/multiplexer/multiplexer.go | 27 +-- lib/multiplexer/multiplexer_test.go | 22 +++ lib/reversetunnel/agentpool.go | 13 +- lib/service/service.go | 189 ++++++++++++++++---- lib/web/apiserver.go | 268 ++++++++++++++++------------ 7 files changed, 386 insertions(+), 172 deletions(-) diff --git a/integration/helpers/instance.go b/integration/helpers/instance.go index 20214c103a6..4488beaebcc 100644 --- a/integration/helpers/instance.go +++ b/integration/helpers/instance.go @@ -596,7 +596,7 @@ func (i *TeleInstance) StartNode(tconf *service.Config) (*service.TeleportProces if err != nil { return nil, trace.Wrap(err) } - return i.startNode(tconf, port) + return i.StartNodeWithTargetPort(tconf, port) } // StartReverseTunnelNode starts a SSH node and connects it to the cluster via reverse tunnel. @@ -605,11 +605,11 @@ func (i *TeleInstance) StartReverseTunnelNode(tconf *service.Config) (*service.T if err != nil { return nil, trace.Wrap(err) } - return i.startNode(tconf, port) + return i.StartNodeWithTargetPort(tconf, port) } -// startNode starts a node and connects it to the cluster. -func (i *TeleInstance) startNode(tconf *service.Config, authPort string) (*service.TeleportProcess, error) { +// StartNodeWithTargetPort starts a node and connects it to the cluster via a specified port. +func (i *TeleInstance) StartNodeWithTargetPort(tconf *service.Config, authPort string) (*service.TeleportProcess, error) { dataDir, err := os.MkdirTemp("", "cluster-"+i.Secrets.SiteName) if err != nil { return nil, trace.Wrap(err) diff --git a/integration/integration_test.go b/integration/integration_test.go index 9d7a1b89944..2490a49d07e 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -156,6 +156,7 @@ func TestIntegrations(t *testing.T) { t.Run("ListResourcesAcrossClusters", suite.bind(testListResourcesAcrossClusters)) t.Run("SessionRecordingModes", suite.bind(testSessionRecordingModes)) t.Run("DifferentPinnedIP", suite.bind(testDifferentPinnedIP)) + t.Run("JoinOverReverseTunnelOnly", suite.bind(testJoinOverReverseTunnelOnly)) t.Run("SFTP", suite.bind(testSFTP)) t.Run("EscapeSequenceTriggers", suite.bind(testEscapeSequenceTriggers)) t.Run("AuthLocalNodeControlStream", suite.bind(testAuthLocalNodeControlStream)) @@ -6615,6 +6616,36 @@ func testListResourcesAcrossClusters(t *testing.T, suite *integrationTestSuite) } } +func testJoinOverReverseTunnelOnly(t *testing.T, suite *integrationTestSuite) { + lib.SetInsecureDevMode(true) + defer lib.SetInsecureDevMode(false) + + // Create a Teleport instance with Auth/Proxy. + mainConfig := suite.defaultServiceConfig() + mainConfig.Auth.Enabled = true + + mainConfig.Proxy.Enabled = true + mainConfig.Proxy.DisableWebService = false + mainConfig.Proxy.DisableWebInterface = true + + mainConfig.SSH.Enabled = false + + main := suite.NewTeleportWithConfig(t, nil, nil, mainConfig) + t.Cleanup(func() { require.NoError(t, main.StopAll()) }) + + // Create a Teleport instance with a Node. + nodeConfig := suite.defaultServiceConfig() + nodeConfig.Hostname = Host + nodeConfig.SetToken("token") + + nodeConfig.Auth.Enabled = false + nodeConfig.Proxy.Enabled = false + nodeConfig.SSH.Enabled = true + + _, err := main.StartNodeWithTargetPort(nodeConfig, helpers.PortStr(t, main.ReverseTunnel)) + require.NoError(t, err, "Node failed to join over reverse tunnel") +} + func testSFTP(t *testing.T, suite *integrationTestSuite) { // Create Teleport instance. teleport := suite.newTeleport(t, nil, true) diff --git a/lib/multiplexer/multiplexer.go b/lib/multiplexer/multiplexer.go index 94354881bf1..853d5d198ca 100644 --- a/lib/multiplexer/multiplexer.go +++ b/lib/multiplexer/multiplexer.go @@ -366,22 +366,23 @@ var ( postgresCancelRequest = []byte{0x0, 0x0, 0x0, 0x10, 0x4, 0xd2, 0x16, 0x2e} ) +var httpMethods = [...][]byte{ + []byte("GET"), + []byte("POST"), + []byte("PUT"), + []byte("DELETE"), + []byte("HEAD"), + []byte("CONNECT"), + []byte("OPTIONS"), + []byte("TRACE"), + []byte("PATCH"), +} + // isHTTP returns true if the first few bytes of the prefix indicate // the use of an HTTP method. func isHTTP(in []byte) bool { - methods := [...][]byte{ - []byte("GET"), - []byte("POST"), - []byte("PUT"), - []byte("DELETE"), - []byte("HEAD"), - []byte("CONNECT"), - []byte("OPTIONS"), - []byte("TRACE"), - []byte("PATCH"), - } - for _, verb := range methods { - if bytes.HasPrefix(verb, in) { + for _, verb := range httpMethods { + if bytes.HasPrefix(in, verb) { return true } } diff --git a/lib/multiplexer/multiplexer_test.go b/lib/multiplexer/multiplexer_test.go index 79d101804bf..4194d71c43c 100644 --- a/lib/multiplexer/multiplexer_test.go +++ b/lib/multiplexer/multiplexer_test.go @@ -868,3 +868,25 @@ func (noopListener) Close() error { func (l noopListener) Addr() net.Addr { return l.addr } + +func TestIsHTTP(t *testing.T) { + t.Parallel() + for _, verb := range httpMethods { + t.Run(fmt.Sprintf("Accept %v", string(verb)), func(t *testing.T) { + data := fmt.Sprintf("%v /some/path HTTP/1.1", string(verb)) + require.True(t, isHTTP([]byte(data))) + }) + } + + rejectedInputs := []string{ + "some random junk", + "FAKE /some/path HTTP/1.1", + // This case checks for a bug where the arguments to bytes.HasPrefix are reversed. + "GE", + } + for _, input := range rejectedInputs { + t.Run(fmt.Sprintf("Reject %q", input), func(t *testing.T) { + require.False(t, isHTTP([]byte(input))) + }) + } +} diff --git a/lib/reversetunnel/agentpool.go b/lib/reversetunnel/agentpool.go index 3aaf0308a92..36becdc4a54 100644 --- a/lib/reversetunnel/agentpool.go +++ b/lib/reversetunnel/agentpool.go @@ -675,8 +675,17 @@ func (c *agentPoolRuntimeConfig) updateRemote(ctx context.Context, addr *utils.N if ok := errors.As(err, &tls.RecordHeaderError{}); !ok { return trace.Wrap(err) } - } else { - tlsRoutingEnabled = ping.Proxy.TLSRoutingEnabled + } + + if ping != nil { + // Only use the ping results if they weren't from a minimal handler. + // The minimal API handler only exists when the proxy and reverse tunnel are + // listening on separate ports, so it will never do TLS routing. + isMinimalHandler := addr.Addr == ping.Proxy.SSH.TunnelListenAddr && + ping.Proxy.SSH.TunnelListenAddr != ping.Proxy.SSH.WebListenAddr + if !isMinimalHandler { + tlsRoutingEnabled = ping.Proxy.TLSRoutingEnabled + } } c.mu.Lock() diff --git a/lib/service/service.go b/lib/service/service.go index cce19a1fb1f..e25d2482021 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -2869,16 +2869,18 @@ func (process *TeleportProcess) initProxy() error { } type proxyListeners struct { - mux *multiplexer.Mux - tls *multiplexer.WebListener - ssh net.Listener - web net.Listener - reverseTunnel net.Listener - kube net.Listener - db dbListeners - alpn net.Listener - proxy net.Listener - grpc net.Listener + mux *multiplexer.Mux + tls *multiplexer.WebListener + ssh net.Listener + web net.Listener + reverseTunnel net.Listener + kube net.Listener + db dbListeners + alpn net.Listener + proxy net.Listener + grpc net.Listener + reverseTunnelMux *multiplexer.Mux + minimalTLS *multiplexer.WebListener } // dbListeners groups database access listeners. @@ -2940,6 +2942,12 @@ func (l *proxyListeners) Close() { if l.proxy != nil { l.proxy.Close() } + if l.reverseTunnelMux != nil { + l.reverseTunnelMux.Close() + } + if l.minimalTLS != nil { + l.minimalTLS.Close() + } } // setupProxyListeners sets up web proxy listeners based on the configuration @@ -3105,10 +3113,17 @@ func (process *TeleportProcess) setupProxyListeners(networkingConfig types.Clust default: process.log.Debug("Setup Proxy: Proxy and reverse tunnel are listening on separate ports.") if !cfg.Proxy.DisableReverseTunnel && !cfg.Proxy.ReverseTunnelListenAddr.IsEmpty() { - listeners.reverseTunnel, err = process.importOrCreateListener(ListenerProxyTunnel, cfg.Proxy.ReverseTunnelListenAddr.Addr) - if err != nil { - listeners.Close() - return nil, trace.Wrap(err) + if cfg.Proxy.DisableWebService { + listeners.reverseTunnel, err = process.importOrCreateListener(ListenerProxyTunnel, cfg.Proxy.ReverseTunnelListenAddr.Addr) + if err != nil { + listeners.Close() + return nil, trace.Wrap(err) + } + } else { + if err := process.initMinimalReverseTunnelListener(cfg, &listeners); err != nil { + listeners.Close() + return nil, trace.Wrap(err) + } } } if !cfg.Proxy.DisableWebService && !cfg.Proxy.WebAddr.IsEmpty() { @@ -3156,6 +3171,31 @@ func (process *TeleportProcess) setupProxyListeners(networkingConfig types.Clust } } +// initMinimalReverseTunnelListener starts a listener over a reverse tunnel that multiplexes a minimal subset of the +// web API. +func (process *TeleportProcess) initMinimalReverseTunnelListener(cfg *Config, listeners *proxyListeners) error { + listener, err := process.importOrCreateListener(ListenerProxyTunnel, cfg.Proxy.ReverseTunnelListenAddr.Addr) + if err != nil { + return trace.Wrap(err) + } + listeners.reverseTunnelMux, err = multiplexer.New(multiplexer.Config{ + EnableProxyProtocol: cfg.Proxy.EnableProxyProtocol, + Listener: listener, + ID: teleport.Component(teleport.ComponentProxy, "tunnel", "web", process.id), + }) + if err != nil { + listener.Close() + return trace.Wrap(err) + } + listeners.reverseTunnel = listeners.reverseTunnelMux.SSH() + go func() { + if err := listeners.reverseTunnelMux.Serve(); err != nil { + process.log.WithError(err).Debug("Minimal reverse tunnel mux exited with error") + } + }() + return nil +} + // muxPostgresOnWebPort starts Postgres proxy listener multiplexed on Teleport Proxy web port, // unless postgres_listen_addr was specified. func (process *TeleportProcess) muxPostgresOnWebPort(cfg *Config, listeners *proxyListeners) { @@ -3357,6 +3397,9 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { // Register web proxy server var webServer *http.Server var webHandler *web.APIHandler + var minimalWebServer *http.Server + var minimalWebHandler *web.APIHandler + if !process.Config.Proxy.DisableWebService { var fs http.FileSystem if !process.Config.Proxy.DisableWebInterface { @@ -3372,27 +3415,27 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { accessPoint: accessPoint, } - webHandler, err = web.NewHandler( - web.Config{ - Proxy: tsrv, - AuthServers: cfg.AuthServers[0], - DomainName: cfg.Hostname, - ProxyClient: conn.Client, - ProxySSHAddr: proxySSHAddr, - ProxyWebAddr: cfg.Proxy.WebAddr, - ProxyPublicAddrs: cfg.Proxy.PublicAddrs, - CipherSuites: cfg.CipherSuites, - FIPS: cfg.FIPS, - AccessPoint: accessPoint, - Emitter: streamEmitter, - PluginRegistry: process.PluginRegistry, - HostUUID: process.Config.HostUUID, - Context: process.ExitContext(), - StaticFS: fs, - ClusterFeatures: process.getClusterFeatures(), - ProxySettings: proxySettings, - PublicProxyAddr: process.proxyPublicAddr().Addr, - }) + webConfig := web.Config{ + Proxy: tsrv, + AuthServers: cfg.AuthServers[0], + DomainName: cfg.Hostname, + ProxyClient: conn.Client, + ProxySSHAddr: proxySSHAddr, + ProxyWebAddr: cfg.Proxy.WebAddr, + ProxyPublicAddrs: cfg.Proxy.PublicAddrs, + CipherSuites: cfg.CipherSuites, + FIPS: cfg.FIPS, + AccessPoint: accessPoint, + Emitter: streamEmitter, + PluginRegistry: process.PluginRegistry, + HostUUID: process.Config.HostUUID, + Context: process.ExitContext(), + StaticFS: fs, + ClusterFeatures: process.getClusterFeatures(), + ProxySettings: proxySettings, + PublicProxyAddr: process.proxyPublicAddr().Addr, + } + webHandler, err = web.NewHandler(webConfig) if err != nil { return trace.Wrap(err) } @@ -3434,6 +3477,12 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { log.Info("Exited.") return nil }) + + if listeners.reverseTunnelMux != nil { + if minimalWebServer, minimalWebHandler, err = process.initMinimalReverseTunnel(listeners, tlsConfigWeb, cfg, webConfig, log); err != nil { + return trace.Wrap(err) + } + } } else { log.Info("Web UI is disabled.") } @@ -3783,6 +3832,12 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { if webHandler != nil { warnOnErr(webHandler.Close(), log) } + if minimalWebServer != nil { + warnOnErr(minimalWebServer.Close(), log) + } + if minimalWebHandler != nil { + warnOnErr(minimalWebHandler.Close(), log) + } warnOnErr(sshProxy.Close(), log) if kubeServer != nil { warnOnErr(kubeServer.Close(), log) @@ -3812,12 +3867,18 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { if webServer != nil { warnOnErr(webServer.Shutdown(ctx), log) } + if minimalWebServer != nil { + warnOnErr(minimalWebServer.Shutdown(ctx), log) + } if kubeServer != nil { warnOnErr(kubeServer.Shutdown(ctx), log) } if webHandler != nil { warnOnErr(webHandler.Close(), log) } + if minimalWebHandler != nil { + warnOnErr(minimalWebHandler.Close(), log) + } if grpcServer != nil { grpcServer.GracefulStop() } @@ -3844,6 +3905,64 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { return nil } +func (process *TeleportProcess) initMinimalReverseTunnel(listeners *proxyListeners, tlsConfigWeb *tls.Config, cfg *Config, webConfig web.Config, log *logrus.Entry) (*http.Server, *web.APIHandler, error) { + var minimalWebServer *http.Server + var minimalWebHandler *web.APIHandler + + internalListener := listeners.reverseTunnelMux.TLS() + if !cfg.Proxy.DisableTLS { + internalListener = tls.NewListener(internalListener, tlsConfigWeb) + } + + minimalListener, err := multiplexer.NewWebListener(multiplexer.WebListenerConfig{ + Listener: internalListener, + }) + if err != nil { + return nil, nil, trace.Wrap(err) + } + listeners.minimalTLS = minimalListener + + minimalProxyLimiter, err := limiter.NewLimiter(cfg.Proxy.Limiter) + if err != nil { + return nil, nil, trace.Wrap(err) + } + webConfig.MinimalReverseTunnelRoutesOnly = true + minimalWebHandler, err = web.NewHandler(webConfig) + if err != nil { + return nil, nil, trace.Wrap(err) + } + minimalProxyLimiter.WrapHandle(minimalWebHandler) + + process.RegisterCriticalFunc("proxy.reversetunnel.tls", func() error { + log.Infof("TLS multiplexer is starting on %v.", cfg.Proxy.ReverseTunnelListenAddr.Addr) + if err := minimalListener.Serve(); !trace.IsConnectionProblem(err) { + log.WithError(err).Warn("TLS multiplexer error.") + } + log.Info("TLS multiplexer exited.") + return nil + }) + + minimalWebServer = &http.Server{ + Handler: minimalProxyLimiter, + ReadHeaderTimeout: apidefaults.DefaultDialTimeout, + ErrorLog: utils.NewStdlogger(log.Error, teleport.ComponentReverseTunnelServer), + } + process.RegisterCriticalFunc("proxy.reversetunnel.web", func() error { + utils.Consolef( + cfg.Console, log, teleport.ComponentProxy, + "Minimal web proxy service %s:%s is starting on %v.", + teleport.Version, teleport.Gitref, cfg.Proxy.ReverseTunnelListenAddr.Addr) + log.Infof("Minimal web proxy service %s:%s is starting on %v.", teleport.Version, teleport.Gitref, cfg.Proxy.ReverseTunnelListenAddr.Addr) + defer minimalWebHandler.Close() + if err := minimalWebServer.Serve(minimalListener.Web()); err != nil && err != http.ErrServerClosed { + log.Warningf("Error while serving web requests: %v", err) + } + log.Info("Exited.") + return nil + }) + return minimalWebServer, minimalWebHandler, nil +} + // kubeDialAddr returns Proxy Kube service address used for dialing local kube service // by remote trusted cluster. // If the proxy is running with Multiplex mode the WebPort is returned diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index e77822688b1..0e6150ab365 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -193,6 +193,10 @@ type Config struct { // ProxySettings allows fetching the current proxy settings. ProxySettings proxySettingsGetter + // MinimalReverseTunnelRoutesOnly mode handles only the endpoints required for + // a reverse tunnel agent to establish a connection. + MinimalReverseTunnelRoutesOnly bool + // PublicProxyAddr is used to template the public proxy address // into the installer script responses PublicProxyAddr string @@ -212,7 +216,7 @@ func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // request is already authenticated (has a session cookie), forward to // application handlers. If the request is unauthenticated and requesting a // FQDN that is not of the proxy, redirect to application launcher. - if app.HasFragment(r) || app.HasSession(r) || app.HasClientCert(r) { + if h.appHandler != nil && (app.HasFragment(r) || app.HasSession(r) || app.HasClientCert(r)) { h.appHandler.ServeHTTP(w, r) return } @@ -295,16 +299,157 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { return nil, trace.Wrap(err) } + if cfg.MinimalReverseTunnelRoutesOnly { + h.bindMinimalEndpoints() + } else { + h.bindDefaultEndpoints(challengeLimiter) + } + + // if Web UI is enabled, check the assets dir: + var indexPage *template.Template + if cfg.StaticFS != nil { + index, err := cfg.StaticFS.Open("/index.html") + if err != nil { + h.log.WithError(err).Error("Failed to open index file.") + return nil, trace.Wrap(err) + } + defer index.Close() + indexContent, err := io.ReadAll(index) + if err != nil { + return nil, trace.ConvertSystemError(err) + } + indexPage, err = template.New("index").Parse(string(indexContent)) + if err != nil { + return nil, trace.BadParameter("failed parsing index.html template: %v", err) + } + + h.Handle("GET", "/web/config.js", httplib.MakeHandler(h.getWebConfig)) + } + + routingHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // request is going to the API? + if strings.HasPrefix(r.URL.Path, apiPrefix) { + http.StripPrefix(apiPrefix, h).ServeHTTP(w, r) + return + } + + // request is going to the web UI + if cfg.StaticFS == nil { + w.WriteHeader(http.StatusNotImplemented) + return + } + + // redirect to "/web" when someone hits "/" + if r.URL.Path == "/" { + http.Redirect(w, r, "/web", http.StatusFound) + return + } + + // serve Web UI: + if strings.HasPrefix(r.URL.Path, "/web/app") { + httplib.SetStaticFileHeaders(w.Header()) + http.StripPrefix("/web", makeGzipHandler(http.FileServer(cfg.StaticFS))).ServeHTTP(w, r) + } else if strings.HasPrefix(r.URL.Path, "/web/") || r.URL.Path == "/web" { + csrfToken, err := csrf.AddCSRFProtection(w, r) + if err != nil { + h.log.WithError(err).Warn("Failed to generate CSRF token.") + } + + session, err := h.authenticateWebSession(w, r) + if err != nil { + h.log.WithError(err).Debug("Could not authenticate.") + } + session.XCSRF = csrfToken + + httplib.SetIndexHTMLHeaders(w.Header()) + if err := indexPage.Execute(w, session); err != nil { + h.log.WithError(err).Error("Failed to execute index page template.") + } + } else { + http.NotFound(w, r) + } + }) + + h.NotFound = routingHandler + + if cfg.PluginRegistry != nil { + if err := cfg.PluginRegistry.RegisterProxyWebHandlers(h); err != nil { + return nil, trace.Wrap(err) + } + } + + resp, err := h.cfg.ProxySettings.GetProxySettings(cfg.Context) + if err != nil { + return nil, trace.Wrap(err) + } + + // Create application specific handler. This handler handles sessions and + // forwarding for application access. + var appHandler *app.Handler + if !cfg.MinimalReverseTunnelRoutesOnly { + appHandler, err = app.NewHandler(cfg.Context, &app.HandlerConfig{ + Clock: h.clock, + AuthClient: cfg.ProxyClient, + AccessPoint: cfg.AccessPoint, + ProxyClient: cfg.Proxy, + CipherSuites: cfg.CipherSuites, + WebPublicAddr: resp.SSH.PublicAddr, + }) + if err != nil { + return nil, trace.Wrap(err) + } + } + + return &APIHandler{ + handler: h, + appHandler: appHandler, + }, nil +} + +type webSession struct { + Session string + XCSRF string +} + +func (h *Handler) authenticateWebSession(w http.ResponseWriter, r *http.Request) (webSession, error) { + ctx, err := h.AuthenticateRequest(w, r, false) + if err != nil { + return webSession{}, trace.Wrap(err) + } + resp, err := newSessionResponse(ctx) + if err != nil { + return webSession{}, trace.Wrap(err) + } + out, err := json.Marshal(resp) + if err != nil { + return webSession{}, trace.Wrap(err) + } + return webSession{ + Session: base64.StdEncoding.EncodeToString(out), + }, nil +} + +// bindMinimalEndpoints binds only the endpoints required for a reverse tunnel +// agent to establish a connection. +func (h *Handler) bindMinimalEndpoints() { + // find is like ping, but is faster because it is optimized for servers + // and does not fetch the data that servers don't need, e.g. + // OIDC connectors and auth preferences + h.GET("/webapi/find", httplib.MakeHandler(h.find)) + // Issue host credentials. + h.POST("/webapi/host/credentials", httplib.MakeHandler(h.hostCredentials)) +} + +// bindDefaultEndpoints binds the default endpoints for the web API. +func (h *Handler) bindDefaultEndpoints(challengeLimiter *limiter.RateLimiter) { + h.bindMinimalEndpoints() + // ping endpoint is used to check if the server is up. the /webapi/ping // endpoint returns the default authentication method and configuration that // the server supports. the /webapi/ping/:connector endpoint can be used to // query the authentication configuration for a specific connector. h.GET("/webapi/ping", httplib.MakeHandler(h.ping)) h.GET("/webapi/ping/:connector", httplib.MakeHandler(h.pingWithConnector)) - // find is like ping, but is faster because it is optimized for servers - // and does not fetch the data that servers don't need, e.g. - // OIDC connectors and auth preferences - h.GET("/webapi/find", httplib.MakeHandler(h.find)) // Unauthenticated access to JWT public keys. h.GET("/.well-known/jwks.json", httplib.MakeHandler(h.jwks)) @@ -432,9 +577,6 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { // User Status (used by client to check if user session is valid) h.GET("/webapi/user/status", h.WithAuth(h.getUserStatus)) - // Issue host credentials. - h.POST("/webapi/host/credentials", httplib.MakeHandler(h.hostCredentials)) - h.GET("/webapi/roles", h.WithAuth(h.getRolesHandle)) h.PUT("/webapi/roles", h.WithAuth(h.upsertRoleHandle)) h.POST("/webapi/roles", h.WithAuth(h.upsertRoleHandle)) @@ -463,116 +605,6 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { // GET a Connection Diagnostics by its name h.GET("/webapi/sites/:site/diagnostics/connections/:connectionid", h.WithClusterAuth(h.getConnectionDiagnostic)) - - // if Web UI is enabled, check the assets dir: - var indexPage *template.Template - if cfg.StaticFS != nil { - index, err := cfg.StaticFS.Open("/index.html") - if err != nil { - h.log.WithError(err).Error("Failed to open index file.") - return nil, trace.Wrap(err) - } - defer index.Close() - indexContent, err := io.ReadAll(index) - if err != nil { - return nil, trace.ConvertSystemError(err) - } - indexPage, err = template.New("index").Parse(string(indexContent)) - if err != nil { - return nil, trace.BadParameter("failed parsing index.html template: %v", err) - } - - h.Handle("GET", "/web/config.js", httplib.MakeHandler(h.getWebConfig)) - } - - routingHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // request is going to the API? - if strings.HasPrefix(r.URL.Path, apiPrefix) { - http.StripPrefix(apiPrefix, h).ServeHTTP(w, r) - return - } - - // request is going to the web UI - if cfg.StaticFS == nil { - w.WriteHeader(http.StatusNotImplemented) - return - } - - // redirect to "/web" when someone hits "/" - if r.URL.Path == "/" { - http.Redirect(w, r, "/web", http.StatusFound) - return - } - - // serve Web UI: - if strings.HasPrefix(r.URL.Path, "/web/app") { - httplib.SetStaticFileHeaders(w.Header()) - http.StripPrefix("/web", makeGzipHandler(http.FileServer(cfg.StaticFS))).ServeHTTP(w, r) - } else if strings.HasPrefix(r.URL.Path, "/web/") || r.URL.Path == "/web" { - csrfToken, err := csrf.AddCSRFProtection(w, r) - if err != nil { - h.log.WithError(err).Warn("Failed to generate CSRF token.") - } - - session := struct { - Session string - XCSRF string - }{ - XCSRF: csrfToken, - } - - ctx, err := h.AuthenticateRequest(w, r, false) - if err == nil { - resp, err := newSessionResponse(ctx) - if err == nil { - out, err := json.Marshal(resp) - if err == nil { - session.Session = base64.StdEncoding.EncodeToString(out) - } - } else { - h.log.WithError(err).Debug("Could not authenticate.") - } - } - httplib.SetIndexHTMLHeaders(w.Header()) - if err := indexPage.Execute(w, session); err != nil { - h.log.WithError(err).Error("Failed to execute index page template.") - } - } else { - http.NotFound(w, r) - } - }) - - h.NotFound = routingHandler - - if cfg.PluginRegistry != nil { - if err := cfg.PluginRegistry.RegisterProxyWebHandlers(h); err != nil { - return nil, trace.Wrap(err) - } - } - - resp, err := h.cfg.ProxySettings.GetProxySettings(cfg.Context) - if err != nil { - return nil, trace.Wrap(err) - } - - // Create application specific handler. This handler handles sessions and - // forwarding for application access. - appHandler, err := app.NewHandler(cfg.Context, &app.HandlerConfig{ - Clock: h.clock, - AuthClient: cfg.ProxyClient, - AccessPoint: cfg.AccessPoint, - ProxyClient: cfg.Proxy, - CipherSuites: cfg.CipherSuites, - WebPublicAddr: resp.SSH.PublicAddr, - }) - if err != nil { - return nil, trace.Wrap(err) - } - - return &APIHandler{ - handler: h, - appHandler: appHandler, - }, nil } // GetProxyClient returns authenticated auth server client