Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: Strip coder cookies from app proxy requests #3528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 17, 2022
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
31 changes: 31 additions & 0 deletions coderd/httpapi/cookie.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package httpapi

import (
"net/textproto"
"strings"

"github.com/coder/coder/codersdk"
)

// StripCoderCookies removes the session token from the cookie header provided.
func StripCoderCookies(header string) string {
header = textproto.TrimString(header)
cookies := []string{}

var part string
for len(header) > 0 { // continue since we have rest
part, header, _ = strings.Cut(header, ";")
part = textproto.TrimString(part)
if part == "" {
continue
}
name, _, _ := strings.Cut(part, "=")
if name == codersdk.SessionTokenKey ||
name == codersdk.OAuth2StateKey ||
name == codersdk.OAuth2RedirectKey {
continue
}
cookies = append(cookies, part)
}
return strings.Join(cookies, "; ")
}
35 changes: 35 additions & 0 deletions coderd/httpapi/cookie_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package httpapi_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/coder/coder/coderd/httpapi"
)

func TestStripCoderCookies(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
Input string
Output string
}{{
"testing=hello; wow=test",
"testing=hello; wow=test",
}, {
"session_token=moo; wow=test",
"wow=test",
}, {
"another_token=wow; session_token=ok",
"another_token=wow",
}, {
"session_token=ok; oauth_state=wow; oauth_redirect=/",
"",
}} {
tc := tc
t.Run(tc.Input, func(t *testing.T) {
t.Parallel()
require.Equal(t, tc.Output, httpapi.StripCoderCookies(tc.Input))
})
}
}
15 changes: 5 additions & 10 deletions coderd/httpmw/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import (
"github.com/coder/coder/cryptorand"
)

const (
oauth2StateCookieName = "oauth_state"
oauth2RedirectCookieName = "oauth_redirect"
)

type oauth2StateKey struct{}

type OAuth2State struct {
Expand Down Expand Up @@ -71,7 +66,7 @@ func ExtractOAuth2(config OAuth2Config) func(http.Handler) http.Handler {
}

http.SetCookie(rw, &http.Cookie{
Name: oauth2StateCookieName,
Name: codersdk.OAuth2StateKey,
Value: state,
Path: "/",
HttpOnly: true,
Expand All @@ -80,7 +75,7 @@ func ExtractOAuth2(config OAuth2Config) func(http.Handler) http.Handler {
// Redirect must always be specified, otherwise
// an old redirect could apply!
http.SetCookie(rw, &http.Cookie{
Name: oauth2RedirectCookieName,
Name: codersdk.OAuth2RedirectKey,
Value: r.URL.Query().Get("redirect"),
Path: "/",
HttpOnly: true,
Expand All @@ -98,10 +93,10 @@ func ExtractOAuth2(config OAuth2Config) func(http.Handler) http.Handler {
return
}

stateCookie, err := r.Cookie(oauth2StateCookieName)
stateCookie, err := r.Cookie(codersdk.OAuth2StateKey)
if err != nil {
httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{
Message: fmt.Sprintf("Cookie %q must be provided.", oauth2StateCookieName),
Message: fmt.Sprintf("Cookie %q must be provided.", codersdk.OAuth2StateKey),
})
return
}
Expand All @@ -113,7 +108,7 @@ func ExtractOAuth2(config OAuth2Config) func(http.Handler) http.Handler {
}

var redirect string
stateRedirect, err := r.Cookie(oauth2RedirectCookieName)
stateRedirect, err := r.Cookie(codersdk.OAuth2RedirectKey)
if err == nil {
redirect = stateRedirect.Value
}
Expand Down
5 changes: 3 additions & 2 deletions coderd/httpmw/oauth2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"golang.org/x/oauth2"

"github.com/coder/coder/coderd/httpmw"
"github.com/coder/coder/codersdk"
)

type testOAuth2Provider struct {
Expand Down Expand Up @@ -71,7 +72,7 @@ func TestOAuth2(t *testing.T) {
t.Parallel()
req := httptest.NewRequest("GET", "/?code=something&state=test", nil)
req.AddCookie(&http.Cookie{
Name: "oauth_state",
Name: codersdk.OAuth2StateKey,
Value: "mismatch",
})
res := httptest.NewRecorder()
Expand All @@ -82,7 +83,7 @@ func TestOAuth2(t *testing.T) {
t.Parallel()
req := httptest.NewRequest("GET", "/?code=test&state=something", nil)
req.AddCookie(&http.Cookie{
Name: "oauth_state",
Name: codersdk.OAuth2StateKey,
Value: "something",
})
req.AddCookie(&http.Cookie{
Expand Down
4 changes: 2 additions & 2 deletions coderd/userauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ func oauth2Callback(t *testing.T, client *codersdk.Client) *http.Response {
req, err := http.NewRequest("GET", oauthURL.String(), nil)
require.NoError(t, err)
req.AddCookie(&http.Cookie{
Name: "oauth_state",
Name: codersdk.OAuth2StateKey,
Value: state,
})
res, err := client.HTTPClient.Do(req)
Expand All @@ -469,7 +469,7 @@ func oidcCallback(t *testing.T, client *codersdk.Client) *http.Response {
req, err := http.NewRequest("GET", oauthURL.String(), nil)
require.NoError(t, err)
req.AddCookie(&http.Cookie{
Name: "oauth_state",
Name: codersdk.OAuth2StateKey,
Value: state,
})
res, err := client.HTTPClient.Do(req)
Expand Down
6 changes: 6 additions & 0 deletions coderd/workspaceapps.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request)
}
defer release()

// This strips the session token from a workspace app request.
cookieHeaders := r.Header.Values("Cookie")[:]
r.Header.Del("Cookie")
for _, cookieHeader := range cookieHeaders {
r.Header.Add("Cookie", httpapi.StripCoderCookies(cookieHeader))
}
proxy.Transport = conn.HTTPTransport()
proxy.ServeHTTP(rw, r)
}
3 changes: 3 additions & 0 deletions coderd/workspaceapps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"cdr.dev/slog/sloggers/slogtest"
Expand All @@ -27,6 +28,8 @@ func TestWorkspaceAppsProxyPath(t *testing.T) {
require.NoError(t, err)
server := http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := r.Cookie(codersdk.SessionTokenKey)
assert.ErrorIs(t, err, http.ErrNoCookie)
w.WriteHeader(http.StatusOK)
}),
}
Expand Down
11 changes: 9 additions & 2 deletions codersdk/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ import (
"nhooyr.io/websocket"
)

// SessionTokenKey represents the name of the cookie or query parameter the API key is stored in.
const SessionTokenKey = "session_token"
// These cookies are Coder-specific. If a new one is added or changed, the name
// shouldn't be likely to conflict with any user-application set cookies.
// Be sure to strip additional cookies in httpapi.StripCoder Cookies!
const (
// SessionTokenKey represents the name of the cookie or query parameter the API key is stored in.
SessionTokenKey = "session_token"
OAuth2StateKey = "oauth_state"
OAuth2RedirectKey = "oauth_redirect"
)

// New creates a Coder client for the provided URL.
func New(serverURL *url.URL) *Client {
Expand Down