add auth proxy roundtripper

This commit is contained in:
deads2k 2016-11-17 11:13:16 -05:00
parent be5d1724f5
commit 61b5585bac
2 changed files with 122 additions and 0 deletions

View File

@ -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

View File

@ -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
}
}
}