Skip to content
Closed
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
48 changes: 42 additions & 6 deletions internal/request/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package request

import (
"context"
"encoding/base64"
"fmt"
h "net/http"
Expand All @@ -17,6 +18,15 @@
"sigs.k8s.io/controller-runtime/pkg/client"
)

var websocketBearerTokenRegexp = regexp.MustCompile(`base64url\.bearer\.authorization\.k8s\.io\.([^,]*)`) //nolint:gochecknoglobals

type userAndGroupsContextKey struct{}

type userAndGroupsContextValue struct {
username string
groups []string
}

type http struct {
*h.Request

Expand All @@ -32,6 +42,25 @@
return &http{Request: request, authTypes: authTypes, usernameClaimField: usernameClaimField, client: client, ignoredImpersonationGroups: ignoredImpersonationGroups, impersonationGroupsRegexp: impersonationGroupsRegexp, skipImpersonationReview: skipImpersonationReview}
}

func ResolveUserAndGroups(request *h.Request, authTypes []AuthType, usernameClaimField string, writer client.Writer, ignoredImpersonationGroups []string, impersonationGroupsRegexp *regexp.Regexp, skipImpersonationReview bool) (*h.Request, string, []string, error) {
if cachedUsername, cachedGroups, ok := cachedUserAndGroups(request.Context()); ok {
return request, cachedUsername, cachedGroups, nil
}

proxyRequest := NewHTTP(request, authTypes, usernameClaimField, writer, ignoredImpersonationGroups, impersonationGroupsRegexp, skipImpersonationReview)
username, groups, err := proxyRequest.GetUserAndGroups()

Check failure on line 51 in internal/request/http.go

View workflow job for this annotation

GitHub Actions / lint

missing whitespace above this line (too many statements above if) (wsl_v5)
if err != nil {

Check failure on line 52 in internal/request/http.go

View workflow job for this annotation

GitHub Actions / lint

only one cuddle assignment allowed before if statement (wsl)
return request, "", nil, err
}

ctx := context.WithValue(request.Context(), userAndGroupsContextKey{}, userAndGroupsContextValue{
username: username,
groups: groups,
})

return request.WithContext(ctx), username, groups, nil
}

func (h http) GetHTTPRequest() *h.Request {
return h.Request
}
Expand Down Expand Up @@ -158,19 +187,17 @@
// Get the JWT from headers
// If there is no Authorizaion Bearer, then try finding the Bearer in Websocket Protocols header. This is for browser support.
func (h http) bearerToken() (string, error) {
tradBearer := strings.ReplaceAll(h.Header.Get("Authorization"), "Bearer ", "")
tradBearer := strings.TrimPrefix(h.Header.Get("Authorization"), "Bearer ")
wsHeader := h.Header.Get("Sec-Websocket-Protocol")

switch {
case tradBearer != "":
return tradBearer, nil
case wsHeader != "":
re := regexp.MustCompile(`base64url\.bearer\.authorization\.k8s\.io\.([^,]*)`)

match := re.FindStringSubmatch(wsHeader)[1]
if match != "" {
match := websocketBearerTokenRegexp.FindStringSubmatch(wsHeader)
if len(match) > 1 && match[1] != "" {
// our token is base64 encoded without padding
b64decode, err := base64.RawStdEncoding.DecodeString(match)
b64decode, err := base64.RawStdEncoding.DecodeString(match[1])
if err != nil {
return "", NewErrUnauthorized("failed to decode websocket auth bearer: " + err.Error())
}
Expand All @@ -184,6 +211,15 @@
}
}

func cachedUserAndGroups(ctx context.Context) (string, []string, bool) {
value, ok := ctx.Value(userAndGroupsContextKey{}).(userAndGroupsContextValue)
if !ok {
return "", nil, false
}

return value.username, value.groups, true
}

type authenticationFn func() (username string, groups []string, err error)

func (h http) authenticationFns() []authenticationFn {
Expand Down
10 changes: 8 additions & 2 deletions internal/webserver/middleware/user_in_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ func CheckUserInIgnoredGroupMiddleware(client client.Writer, log logr.Logger, cl
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if ignoredUserGroups.Len() > 0 {
user, groups, err := req.NewHTTP(request, authTypes, claim, client, ignoredImpersonationGroups, impersonationGroupsRegexp, skipImpersonationReview).GetUserAndGroups()
var (
err error
user string
groups []string
)

request, user, groups, err = req.ResolveUserAndGroups(request, authTypes, claim, client, ignoredImpersonationGroups, impersonationGroupsRegexp, skipImpersonationReview)
if err != nil {
log.Error(err, "Cannot retrieve username and group from request")
}
Expand All @@ -44,7 +50,7 @@ func CheckUserInIgnoredGroupMiddleware(client client.Writer, log logr.Logger, cl
func CheckUserInCapsuleGroupMiddleware(client client.Writer, log logr.Logger, claim string, authTypes []req.AuthType, ignoredImpersonationGroups []string, impersonationGroupsRegexp *regexp.Regexp, skipImpersonationReview bool, impersonate func(http.ResponseWriter, *http.Request)) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
user, groups, err := req.NewHTTP(request, authTypes, claim, client, ignoredImpersonationGroups, impersonationGroupsRegexp, skipImpersonationReview).GetUserAndGroups()
request, user, groups, err := req.ResolveUserAndGroups(request, authTypes, claim, client, ignoredImpersonationGroups, impersonationGroupsRegexp, skipImpersonationReview)
if err != nil {
log.Error(err, "Cannot retrieve username and group from request")
}
Expand Down
42 changes: 21 additions & 21 deletions internal/webserver/webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,21 +328,24 @@
w := httptest.NewRecorder()
next.ServeHTTP(w, request)

body, err := io.ReadAll(w.Result().Body)
result := w.Result()
defer func() {

Check failure on line 332 in internal/webserver/webserver.go

View workflow job for this annotation

GitHub Actions / lint

missing whitespace above this line (no shared variables above defer) (wsl_v5)
_ = result.Body.Close()
}()

body, err := io.ReadAll(result.Body)
if err != nil {
n.log.Error(err, "cannot read response body")

return
}

proxyRequest := req.NewHTTP(request, n.authTypes, n.usernameClaimField, n.writer, n.ignoredImpersonationGroups, n.impersonationGroupsRegexp, n.skipImpersonationReview)

username, groups, err := proxyRequest.GetUserAndGroups()
request, username, groups, err := req.ResolveUserAndGroups(request, n.authTypes, n.usernameClaimField, n.writer, n.ignoredImpersonationGroups, n.impersonationGroupsRegexp, n.skipImpersonationReview)
if err != nil {
server.HandleError(writer, err, "cannot retrieve user and group from the request")
}

proxyTenants, err := n.getTenantsForOwner(request.Context(), username, groups)

Check failure on line 348 in internal/webserver/webserver.go

View workflow job for this annotation

GitHub Actions / lint

Non-inherited new context, use function like `context.WithXXX` or `r.Context` instead (contextcheck)
if err != nil {
server.HandleError(writer, err, "cannot list Tenant resources")
}
Expand All @@ -369,7 +372,7 @@
}
}

for k, v := range w.Result().Header {
for k, v := range result.Header {
if k == "Content-Length" {
continue
}
Expand All @@ -379,7 +382,7 @@
}
}

writer.WriteHeader(w.Result().StatusCode)
writer.WriteHeader(result.StatusCode)

write, err := writer.Write(body)
if err != nil {
Expand All @@ -390,32 +393,31 @@

func (n *kubeFilter) handleRequest(request *http.Request, selector labels.Selector) {
req.SanitizeImpersonationHeaders(request)
selectorValue := selector.String()

Check failure on line 396 in internal/webserver/webserver.go

View workflow job for this annotation

GitHub Actions / lint

assignments should only be cuddled with other assignments (wsl)

q := request.URL.Query()
if e := q.Get("labelSelector"); len(e) > 0 {
n.log.V(4).Info("handling current labelSelector", "selector", e)

v := strings.Join([]string{e, selector.String()}, ",")
v := strings.Join([]string{e, selectorValue}, ",")
q.Set("labelSelector", v)
n.log.V(4).Info("labelSelector updated", "selector", v)
} else {
q.Set("labelSelector", selector.String())
n.log.V(4).Info("labelSelector added", "selector", selector.String())
q.Set("labelSelector", selectorValue)
n.log.V(4).Info("labelSelector added", "selector", selectorValue)
}

n.log.V(4).Info("updating RawQuery", "query", q.Encode())
request.URL.RawQuery = q.Encode()

if len(n.BearerToken()) > 0 {
n.log.V(10).Info("Updating the token", "token", n.BearerToken())
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", n.BearerToken()))
if token := n.BearerToken(); len(token) > 0 {
n.log.V(10).Info("Updating the token", "token", token)
request.Header.Set("Authorization", "Bearer "+token)
}
}

func (n *kubeFilter) impersonateHandler(writer http.ResponseWriter, request *http.Request) {
hr := req.NewHTTP(request, n.authTypes, n.usernameClaimField, n.writer, n.ignoredImpersonationGroups, n.impersonationGroupsRegexp, n.skipImpersonationReview)

username, groups, err := hr.GetUserAndGroups()
request, username, groups, err := req.ResolveUserAndGroups(request, n.authTypes, n.usernameClaimField, n.writer, n.ignoredImpersonationGroups, n.impersonationGroupsRegexp, n.skipImpersonationReview)
if err != nil {
msg := "cannot retrieve user and group"

Expand All @@ -429,8 +431,8 @@

n.log.V(4).Info("impersonating for the current request", "username", username, "groups", groups, "uri", request.URL.Path)

if len(n.BearerToken()) > 0 {
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", n.BearerToken()))
if token := n.BearerToken(); len(token) > 0 {
request.Header.Set("Authorization", "Bearer "+token)
}
// Dropping malicious header connection
// https://github.com/projectcapsule/capsule-proxy/issues/188
Expand Down Expand Up @@ -532,9 +534,7 @@
middleware.CheckUserInCapsuleGroupMiddleware(n.writer, n.log, n.usernameClaimField, n.authTypes, n.ignoredImpersonationGroups, n.impersonationGroupsRegexp, n.skipImpersonationReview, n.impersonateHandler),
)
sr.HandleFunc("", func(writer http.ResponseWriter, request *http.Request) {
proxyRequest := req.NewHTTP(request, n.authTypes, n.usernameClaimField, n.writer, n.ignoredImpersonationGroups, n.impersonationGroupsRegexp, n.skipImpersonationReview)

username, groups, err := proxyRequest.GetUserAndGroups()
request, username, groups, err := req.ResolveUserAndGroups(request, n.authTypes, n.usernameClaimField, n.writer, n.ignoredImpersonationGroups, n.impersonationGroupsRegexp, n.skipImpersonationReview)
if err != nil {
server.HandleError(writer, err, "cannot retrieve user and group from the request")
}
Expand All @@ -546,7 +546,7 @@

var selector labels.Selector

selector, err = mod.Handle(proxyTenants, proxyRequest)
selector, err = mod.Handle(proxyTenants, req.NewHTTP(request, n.authTypes, n.usernameClaimField, n.writer, n.ignoredImpersonationGroups, n.impersonationGroupsRegexp, n.skipImpersonationReview))

switch {
case err != nil:
Expand Down
Loading