Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions lib/httplib/httpheaders.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,35 +224,6 @@ func SetIndexContentSecurityPolicy(h http.Header, cfg proto.Features, urlPath st
h.Set("Content-Security-Policy", cspString)
}

// DELETE IN 17.0: Kept for legacy app access.
var appLaunchCSPStringCache *cspCache = newCSPCache()

// DELETE IN 17.0: Kept for legacy app access.
func getAppLaunchContentSecurityPolicyString(applicationURL string) string {
if cspString, ok := appLaunchCSPStringCache.get(applicationURL); ok {
return cspString
}

cspString := getContentSecurityPolicyString(
defaultContentSecurityPolicy,
defaultFontSrc,
cspMap{
"connect-src": {"'self'", applicationURL},
},
)
appLaunchCSPStringCache.set(applicationURL, cspString)

return cspString
}

// DELETE IN 17.0: Kept for legacy app access.
//
// SetAppLaunchContentSecurityPolicy sets the Content-Security-Policy header for /web/launch
func SetAppLaunchContentSecurityPolicy(h http.Header, applicationURL string) {
cspString := getAppLaunchContentSecurityPolicyString(applicationURL)
h.Set("Content-Security-Policy", cspString)
}

var redirectCSPStringCache *cspCache = newCSPCache()

func getRedirectPageContentSecurityPolicyString(scriptSrc string) string {
Expand Down
26 changes: 0 additions & 26 deletions lib/httplib/httplib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,32 +376,6 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) {
}
}

func TestSetAppLaunchContentSecurityPolicy(t *testing.T) {
t.Parallel()

applicationURL := "https://example.com"

expectedCspVals := map[string]string{
"default-src": "'self'",
"base-uri": "'self'",
"form-action": "'self'",
"frame-ancestors": "'none'",
"object-src": "'none'",
"style-src": "'self' 'unsafe-inline'",
"img-src": "'self' data: blob:",
"font-src": "'self' data:",
"connect-src": fmt.Sprintf("'self' %s", applicationURL),
}

h := make(http.Header)
SetAppLaunchContentSecurityPolicy(h, applicationURL)
actualCsp := h.Get("Content-Security-Policy")
for k, v := range expectedCspVals {
expectedCspSubString := fmt.Sprintf("%s %s;", k, v)
require.Contains(t, actualCsp, expectedCspSubString)
}
}

func TestSetRedirectPageContentSecurityPolicy(t *testing.T) {
t.Parallel()

Expand Down
38 changes: 1 addition & 37 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,43 +577,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) {
session.XCSRF = csrfToken

httplib.SetNoCacheHeaders(w.Header())

// DELETE IN 17.0: Delete the first if block. Keep the else case.
// Kept for backwards compatibility.
//
// This case only adds an additional CSP `content-src` value of the
// app URL which allows requesting to the app domain required by
// the legacy app access.
if strings.HasPrefix(r.URL.Path, "/web/launch/") {
// legacy app access needs to make a CORS fetch request,
// so we only set the default CSP on that page
parts := strings.Split(r.URL.Path, "/")

if len(parts[3]) == 0 {
h.log.Warn("Application domain is missing from web/launch URL")
http.Error(w, "missing application domain", http.StatusBadRequest)
return
}

// grab the FQDN from the URL to allow in the connect-src CSP
applicationOrigin := "https://" + parts[3]

// Parse to validate the application domain extracted is in a valid format.
// An invalid domain is:
// - having spaces
// - having escape characters eg: %20
// - invalid port after host eg: :unsafe-inline
_, err := url.Parse(applicationOrigin)
if err != nil {
h.log.WithError(err).Warn("Failed to parse application domain extracted from web/launch.")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

httplib.SetAppLaunchContentSecurityPolicy(w.Header(), applicationOrigin+":*")
} else {
httplib.SetIndexContentSecurityPolicy(w.Header(), cfg.ClusterFeatures, r.URL.Path)
}
httplib.SetIndexContentSecurityPolicy(w.Header(), cfg.ClusterFeatures, r.URL.Path)

if err := indexPage.Execute(w, session); err != nil {
h.log.WithError(err).Error("Failed to execute index page template.")
Expand Down
73 changes: 0 additions & 73 deletions lib/web/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6164,79 +6164,6 @@ func TestListConnectionsDiagnostic(t *testing.T) {
require.Equal(t, "some details", receivedConnectionDiagnostic.Traces[0].Details)
}

func TestWebLauncherURL(t *testing.T) {
t.Parallel()
env := newWebPack(t, 1)
proxy := env.proxies[0]
pack := proxy.authPack(t, "foo@example.com", nil /* roles */)

testcases := []struct {
desc string
appURL string
statusCode int
}{
{
desc: "valid semicolon",
appURL: "dumper.localhost;testing",
statusCode: http.StatusOK,
},
{
desc: "valid with query (question mark)",
appURL: "dumper.localhost?foo",
statusCode: http.StatusOK,
},
{
desc: "valid with multipath",
appURL: "dumper.localhost/foo/bar/",
statusCode: http.StatusOK,
},
{
desc: "valid with multipath with query",
appURL: "dumper.localhost/foo/bar?foo=bar",
statusCode: http.StatusOK,
},
{
desc: "invalid: empty application url",
appURL: "/",
statusCode: http.StatusBadRequest,
},
{
desc: "invalid use of semicolon, invalid port",
appURL: "dumper.localhost;script-src:something",
statusCode: http.StatusBadRequest,
},
{
desc: "invalid use of escape characters (space)",
appURL: "dumper.localhost;%20script-src%20unsafe-inline%20;",
statusCode: http.StatusBadRequest,
},
{
desc: "invalid use of escape characters (double encoded space)",
appURL: "dumper.localhost;%2520script-src%2520unsafe-inline%2520;",
statusCode: http.StatusBadRequest,
},
{
desc: "invalid use of spaces",
appURL: "dumper.localhost; script-src * unsafe-inline;",
statusCode: http.StatusBadRequest,
},
}

for _, tc := range testcases {
t.Run(tc.desc, func(t *testing.T) {
url, err := url.JoinPath(proxy.webURL.String(), "web", "launch", tc.appURL)
require.NoError(t, err)

resp, err := pack.clt.HTTPClient().Get(url)
require.NoError(t, err)
defer resp.Body.Close()

require.Equal(t, tc.statusCode, resp.StatusCode)
})
}

}

func TestDiagnoseSSHConnection(t *testing.T) {
ctx := context.Background()

Expand Down
69 changes: 0 additions & 69 deletions lib/web/app/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,75 +198,6 @@ func (h *Handler) completeAppAuthExchange(w http.ResponseWriter, r *http.Request
return nil
}

// DELETE IN 17.0: kept for backwards compat.
//
// handleAuth handles authentication for an app
// When a `POST` request comes in from a trusted proxy address, it'll set the value from the
// `X-Cookie-Value` header to the `__Host-grv_app_session` cookie.
func (h *Handler) handleAuth(w http.ResponseWriter, r *http.Request, p httprouter.Params) error {
httplib.SetNoCacheHeaders(w.Header())

cookieValue := r.Header.Get("X-Cookie-Value")
if cookieValue == "" {
h.log.Warn("Request failed: missing X-Cookie-Value header")
h.emitErrorEventAndDeleteAppSession(r, emitErrorEventFields{
err: "missing X-Cookie-Value header",
})
return trace.AccessDenied("access denied")
}

subjectCookieValue := r.Header.Get("X-Subject-Cookie-Value")
if subjectCookieValue == "" {
h.log.Warn("Request failed: X-Subject-Cookie-Value")
h.emitErrorEventAndDeleteAppSession(r, emitErrorEventFields{
err: "missing X-Subject-Cookie-Value header",
sessionID: cookieValue,
})
return trace.AccessDenied("access denied")
}

// Validate that the caller is asking for a session that exists.
ws, err := h.c.AccessPoint.GetAppSession(r.Context(), types.GetAppSessionRequest{
SessionID: cookieValue,
})
if err != nil {
h.log.WithError(err).Warn("Request failed: unable to get app session")
return trace.AccessDenied("access denied")
}

if err := checkSubjectToken(subjectCookieValue, ws); err != nil {
h.log.Warnf("Request failed: %v.", err)

h.emitErrorEventAndDeleteAppSession(r, emitErrorEventFields{
sessionID: cookieValue,
err: err.Error(),
loginName: ws.GetUser(),
})

return trace.AccessDenied("access denied")
}

http.SetCookie(w, &http.Cookie{
Name: CookieName,
Value: cookieValue,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteNoneMode,
})

http.SetCookie(w, &http.Cookie{
Name: SubjectCookieName,
Value: subjectCookieValue,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteNoneMode,
})

return nil
}

func checkSubjectToken(subjectCookieValue string, ws types.WebSession) error {
if subjectCookieValue == "" {
return trace.AccessDenied("subject session token is not set")
Expand Down
8 changes: 1 addition & 7 deletions lib/web/app/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,7 @@ func NewHandler(ctx context.Context, c *HandlerConfig) (*Handler, error) {
h.router = httprouter.New()
h.router.UseRawPath = true
h.router.GET("/x-teleport-auth", makeRouterHandler(h.startAppAuthExchange))
// DELETE IN 17.0
// Kept for legacy app access.
h.router.OPTIONS("/x-teleport-auth", makeRouterHandler(h.withCustomCORS(nil)))
// DELETE IN 17.0
// when deleting, replace with the commented handler below:
// h.router.POST("/x-teleport-auth", makeRouterHandler(h.completeAppAuthExchange))
h.router.POST("/x-teleport-auth", makeRouterHandler(h.withCustomCORS(h.handleAuth)))
h.router.POST("/x-teleport-auth", makeRouterHandler(h.completeAppAuthExchange))
h.router.GET("/teleport-logout", h.withRouterAuth(h.handleLogout))
h.router.NotFound = h.withAuth(h.handleHttp)

Expand Down
Loading