fix: avoid redirect loop on workspace proxies (#9389)

* fix: avoid redirect loop on workspace proxies

---------

Co-authored-by: Steven Masley <stevenmasley@coder.com>
This commit is contained in:
Dean Sheather
2023-08-28 20:34:52 -05:00
committed by GitHub
co-authored by Steven Masley
parent eb68684327
commit 5993f85ec9
12 changed files with 265 additions and 99 deletions
+39 -12
View File
@@ -5,6 +5,7 @@ import (
"encoding/hex"
"encoding/json"
"net/http"
"strings"
"time"
"github.com/go-jose/go-jose/v3"
@@ -38,8 +39,17 @@ type SignedToken struct {
// MatchesRequest returns true if the token matches the request. Any token that
// does not match the request should be considered invalid.
func (t SignedToken) MatchesRequest(req Request) bool {
tokenBasePath := t.Request.BasePath
if !strings.HasSuffix(tokenBasePath, "/") {
tokenBasePath += "/"
}
reqBasePath := req.BasePath
if !strings.HasSuffix(reqBasePath, "/") {
reqBasePath += "/"
}
return t.AccessMethod == req.AccessMethod &&
t.BasePath == req.BasePath &&
tokenBasePath == reqBasePath &&
t.UsernameOrID == req.UsernameOrID &&
t.WorkspaceNameOrID == req.WorkspaceNameOrID &&
t.AgentNameOrID == req.AgentNameOrID &&
@@ -227,22 +237,39 @@ func (k SecurityKey) DecryptAPIKey(encryptedAPIKey string) (string, error) {
// FromRequest returns the signed token from the request, if it exists and is
// valid. The caller must check that the token matches the request.
func FromRequest(r *http.Request, key SecurityKey) (*SignedToken, bool) {
// Get the token string from the request. We usually use a cookie for this,
// but for web terminal we also support a query parameter to support
// cross-domain terminal access.
tokenStr := ""
tokenCookie, cookieErr := r.Cookie(codersdk.DevURLSignedAppTokenCookie)
if cookieErr == nil {
tokenStr = tokenCookie.Value
} else {
tokenStr = r.URL.Query().Get(codersdk.SignedAppTokenQueryParameter)
// Get all signed app tokens from the request. This includes the query
// parameter and all matching cookies sent with the request. If there are
// somehow multiple signed app token cookies, we want to try all of them
// (up to 4). The first one that is valid is used.
//
// Browsers will send all cookies in the request, even if there are multiple
// with the same name on different paths.
//
// If using a query parameter the request MUST be a terminal request. We use
// this to support cross-domain terminal access for the web terminal.
var (
tokens = []string{}
hasQueryParam = false
)
if q := r.URL.Query().Get(codersdk.SignedAppTokenQueryParameter); q != "" {
hasQueryParam = true
tokens = append(tokens, q)
}
for _, cookie := range r.Cookies() {
if cookie.Name == codersdk.SignedAppTokenCookie {
tokens = append(tokens, cookie.Value)
}
}
if tokenStr != "" {
if len(tokens) > 4 {
tokens = tokens[:4]
}
for _, tokenStr := range tokens {
token, err := key.VerifySignedToken(tokenStr)
if err == nil {
req := token.Request.Normalize()
if cookieErr != nil && req.AccessMethod != AccessMethodTerminal {
if hasQueryParam && req.AccessMethod != AccessMethodTerminal {
// The request must be a terminal request if we're using a
// query parameter.
return nil, false