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).
This commit is contained in:
Andrew Burke
2022-08-15 21:28:24 +00:00
committed by GitHub
parent 732b291a6f
commit 9607fdd78c
7 changed files with 386 additions and 172 deletions
+4 -4
View File
@@ -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)
+31
View File
@@ -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)
+14 -13
View File
@@ -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
}
}
+22
View File
@@ -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)))
})
}
}
+11 -2
View File
@@ -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()
+154 -35
View File
@@ -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
+150 -118
View File
@@ -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