104 lines
2.8 KiB
Go

// Copyright (c) 2020 TypeFox GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package proxy
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/gitpod-io/gitpod/ws-manager/api"
"github.com/gorilla/mux"
)
// WorkspaceAuthHandler rejects requests which are not authenticated or authorized to access a workspace
func WorkspaceAuthHandler(domain string, info WorkspaceInfoProvider) mux.MiddlewareFunc {
return func(h http.Handler) http.Handler {
cookiePrefix := domain
for _, c := range []string{" ", "-", "."} {
cookiePrefix = strings.ReplaceAll(cookiePrefix, c, "_")
}
cookiePrefix = "_" + cookiePrefix + "_ws_"
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
var (
log = getLog(req.Context())
vars = mux.Vars(req)
wsID = vars[workspaceIDIdentifier]
port = vars[workspacePortIdentifier]
)
if wsID == "" {
log.Warn("workspace request without workspace ID")
resp.WriteHeader(http.StatusForbidden)
return
}
ws := info.WorkspaceInfo(req.Context(), wsID)
if ws == nil {
log.Warn("did not find workspace info")
resp.WriteHeader(http.StatusNotFound)
return
}
if ws.Auth != nil && ws.Auth.Admission == api.AdmissionLevel_ADMIT_EVERYONE {
// workspace is free for all - no tokens or cookies matter
h.ServeHTTP(resp, req)
return
}
if port != "" {
// this is a workspace port request and ports can be public or private.
// For public ports no tokens or cookies matter, private ports are subject
// to the same access policies as the workspace itself is.
var isPublic bool
prt, err := strconv.Atoi(port)
if err != nil {
log.WithField("port", port).WithError(err).Error("cannot convert port to int")
} else {
for _, p := range ws.Ports {
if int(p.Port) == prt {
isPublic = p.Visibility == api.PortVisibility_PORT_VISIBILITY_PUBLIC
break
}
}
}
if isPublic {
// workspace port is free for all - no tokens or cookies matter
h.ServeHTTP(resp, req)
return
}
// port seems to be private - subject it to the same access policy as the workspace itself
}
cn := fmt.Sprintf("%s%s_owner_", cookiePrefix, ws.InstanceID)
c, err := req.Cookie(cn)
if err != nil {
log.WithField("cookieName", cn).Warn("no owner cookie present")
resp.WriteHeader(http.StatusUnauthorized)
return
}
tkn, err := url.QueryUnescape(c.Value)
if err != nil {
log.WithError(err).Warn("cannot decode owner token")
resp.WriteHeader(http.StatusBadRequest)
return
}
if tkn != ws.Auth.OwnerToken {
log.Warn("owner token mismatch")
resp.WriteHeader(http.StatusForbidden)
return
}
h.ServeHTTP(resp, req)
})
}
}