From 61b5585bac93caf90dd00ecf19319ad15d2277dc Mon Sep 17 00:00:00 2001 From: deads2k Date: Thu, 17 Nov 2016 11:13:16 -0500 Subject: [PATCH] add auth proxy roundtripper --- pkg/client/transport/round_trippers.go | 61 +++++++++++++++++++++ pkg/client/transport/round_trippers_test.go | 61 +++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/pkg/client/transport/round_trippers.go b/pkg/client/transport/round_trippers.go index 3188d062137..1ad9f8285d1 100644 --- a/pkg/client/transport/round_trippers.go +++ b/pkg/client/transport/round_trippers.go @@ -19,6 +19,7 @@ package transport import ( "fmt" "net/http" + "strings" "time" "github.com/golang/glog" @@ -76,6 +77,66 @@ type requestCanceler interface { CancelRequest(*http.Request) } +type authProxyRoundTripper struct { + username string + groups []string + extra map[string][]string + + rt http.RoundTripper +} + +// NewAuthProxyRoundTripper provides a roundtripper which will add auth proxy fields to requests for +// authentication terminating proxy cases +// assuming you pull the user from the context: +// username is the user.Info.GetName() of the user +// groups is the user.Info.GetGroups() of the user +// extra is the user.Info.GetExtra() of the user +// extra can contain any additional information that the authenticator +// thought was interesting, for example authorization scopes. +// In order to faithfully round-trip through an impersonation flow, these keys +// MUST be lowercase. +func NewAuthProxyRoundTripper(username string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper { + return &authProxyRoundTripper{ + username: username, + groups: groups, + extra: extra, + rt: rt, + } +} + +func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + req = cloneRequest(req) + req.Header.Del("X-Remote-User") + req.Header.Del("X-Remote-Group") + for key := range req.Header { + if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) { + req.Header.Del(key) + } + } + + req.Header.Set("X-Remote-User", rt.username) + for _, group := range rt.groups { + req.Header.Add("X-Remote-Group", group) + } + for key, values := range rt.extra { + for _, value := range values { + req.Header.Add("X-Remote-Extra-"+key, value) + } + } + + return rt.rt.RoundTrip(req) +} + +func (rt *authProxyRoundTripper) CancelRequest(req *http.Request) { + if canceler, ok := rt.rt.(requestCanceler); ok { + canceler.CancelRequest(req) + } else { + glog.Errorf("CancelRequest not implemented") + } +} + +func (rt *authProxyRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt } + type userAgentRoundTripper struct { agent string rt http.RoundTripper diff --git a/pkg/client/transport/round_trippers_test.go b/pkg/client/transport/round_trippers_test.go index 85ea12e0a60..d5ffc6bde30 100644 --- a/pkg/client/transport/round_trippers_test.go +++ b/pkg/client/transport/round_trippers_test.go @@ -19,6 +19,7 @@ package transport import ( "net/http" "reflect" + "strings" "testing" ) @@ -155,3 +156,63 @@ func TestImpersonationRoundTripper(t *testing.T) { } } } + +func TestAuthProxyRoundTripper(t *testing.T) { + for n, tc := range map[string]struct { + username string + groups []string + extra map[string][]string + }{ + "allfields": { + username: "user", + groups: []string{"groupA", "groupB"}, + extra: map[string][]string{ + "one": {"alpha", "bravo"}, + "two": {"charlie", "delta"}, + }, + }, + } { + rt := &testRoundTripper{} + req := &http.Request{} + NewAuthProxyRoundTripper(tc.username, tc.groups, tc.extra, rt).RoundTrip(req) + if rt.Request == nil { + t.Errorf("%s: unexpected nil request: %v", n, rt) + continue + } + if rt.Request == req { + t.Errorf("%s: round tripper should have copied request object: %#v", n, rt.Request) + continue + } + + actualUsernames, ok := rt.Request.Header["X-Remote-User"] + if !ok { + t.Errorf("%s missing value", n) + continue + } + if e, a := []string{tc.username}, actualUsernames; !reflect.DeepEqual(e, a) { + t.Errorf("%s expected %v, got %v", n, e, a) + continue + } + actualGroups, ok := rt.Request.Header["X-Remote-Group"] + if !ok { + t.Errorf("%s missing value", n) + continue + } + if e, a := tc.groups, actualGroups; !reflect.DeepEqual(e, a) { + t.Errorf("%s expected %v, got %v", n, e, a) + continue + } + + actualExtra := map[string][]string{} + for key, values := range rt.Request.Header { + if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) { + extraKey := strings.ToLower(key[len("X-Remote-Extra-"):]) + actualExtra[extraKey] = append(actualExtra[key], values...) + } + } + if e, a := tc.extra, actualExtra; !reflect.DeepEqual(e, a) { + t.Errorf("%s expected %v, got %v", n, e, a) + continue + } + } +}