client-go: add the UID to the auth-proxy roundtripper

This commit is contained in:
Stanislav Láznička 2023-02-16 14:01:53 +01:00
parent 0409ba7ff1
commit 2cc0370169
6 changed files with 42 additions and 9 deletions

View File

@ -251,7 +251,7 @@ func (h *peerProxyHandler) proxyRequestToDestinationAPIServer(req *http.Request,
newReq.Header.Add(PeerProxiedHeader, "true") newReq.Header.Add(PeerProxiedHeader, "true")
defer cancelFn() defer cancelFn()
proxyRoundTripper := transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), h.proxyTransport) proxyRoundTripper := transport.NewAuthProxyRoundTripper(user.GetName(), user.GetUID(), user.GetGroups(), user.GetExtra(), h.proxyTransport)
delegate := &epmetrics.ResponseWriterDelegator{ResponseWriter: rw} delegate := &epmetrics.ResponseWriterDelegator{ResponseWriter: rw}
w := responsewriter.WrapForHTTP1Or2(delegate) w := responsewriter.WrapForHTTP1Or2(delegate)

View File

@ -86,6 +86,7 @@ func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
type authProxyRoundTripper struct { type authProxyRoundTripper struct {
username string username string
uid string
groups []string groups []string
extra map[string][]string extra map[string][]string
@ -98,15 +99,17 @@ var _ utilnet.RoundTripperWrapper = &authProxyRoundTripper{}
// authentication terminating proxy cases // authentication terminating proxy cases
// assuming you pull the user from the context: // assuming you pull the user from the context:
// username is the user.Info.GetName() of the user // username is the user.Info.GetName() of the user
// uid is the user.Info.GetUID() of the user
// groups is the user.Info.GetGroups() of the user // groups is the user.Info.GetGroups() of the user
// extra is the user.Info.GetExtra() of the user // extra is the user.Info.GetExtra() of the user
// extra can contain any additional information that the authenticator // extra can contain any additional information that the authenticator
// thought was interesting, for example authorization scopes. // thought was interesting, for example authorization scopes.
// In order to faithfully round-trip through an impersonation flow, these keys // In order to faithfully round-trip through an impersonation flow, these keys
// MUST be lowercase. // MUST be lowercase.
func NewAuthProxyRoundTripper(username string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper { func NewAuthProxyRoundTripper(username, uid string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper {
return &authProxyRoundTripper{ return &authProxyRoundTripper{
username: username, username: username,
uid: uid,
groups: groups, groups: groups,
extra: extra, extra: extra,
rt: rt, rt: rt,
@ -115,14 +118,15 @@ func NewAuthProxyRoundTripper(username string, groups []string, extra map[string
func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req = utilnet.CloneRequest(req) req = utilnet.CloneRequest(req)
SetAuthProxyHeaders(req, rt.username, rt.groups, rt.extra) SetAuthProxyHeaders(req, rt.username, rt.uid, rt.groups, rt.extra)
return rt.rt.RoundTrip(req) return rt.rt.RoundTrip(req)
} }
// SetAuthProxyHeaders stomps the auth proxy header fields. It mutates its argument. // SetAuthProxyHeaders stomps the auth proxy header fields. It mutates its argument.
func SetAuthProxyHeaders(req *http.Request, username string, groups []string, extra map[string][]string) { func SetAuthProxyHeaders(req *http.Request, username, uid string, groups []string, extra map[string][]string) {
req.Header.Del("X-Remote-User") req.Header.Del("X-Remote-User")
req.Header.Del("X-Remote-Uid")
req.Header.Del("X-Remote-Group") req.Header.Del("X-Remote-Group")
for key := range req.Header { for key := range req.Header {
if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) { if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) {
@ -131,6 +135,9 @@ func SetAuthProxyHeaders(req *http.Request, username string, groups []string, ex
} }
req.Header.Set("X-Remote-User", username) req.Header.Set("X-Remote-User", username)
if len(uid) > 0 {
req.Header.Set("X-Remote-Uid", uid)
}
for _, group := range groups { for _, group := range groups {
req.Header.Add("X-Remote-Group", group) req.Header.Add("X-Remote-Group", group)
} }

View File

@ -306,12 +306,14 @@ func TestImpersonationRoundTripper(t *testing.T) {
func TestAuthProxyRoundTripper(t *testing.T) { func TestAuthProxyRoundTripper(t *testing.T) {
for n, tc := range map[string]struct { for n, tc := range map[string]struct {
username string username string
uid string
groups []string groups []string
extra map[string][]string extra map[string][]string
expectedExtra map[string][]string expectedExtra map[string][]string
}{ }{
"allfields": { "allfields": {
username: "user", username: "user",
uid: "7db46926-e803-4337-9a29-f9c1fab7d34a",
groups: []string{"groupA", "groupB"}, groups: []string{"groupA", "groupB"},
extra: map[string][]string{ extra: map[string][]string{
"one": {"alpha", "bravo"}, "one": {"alpha", "bravo"},
@ -324,6 +326,7 @@ func TestAuthProxyRoundTripper(t *testing.T) {
}, },
"escaped extra": { "escaped extra": {
username: "user", username: "user",
uid: "7db46926-e803-4337-9a29-f9c1fab7d34a",
groups: []string{"groupA", "groupB"}, groups: []string{"groupA", "groupB"},
extra: map[string][]string{ extra: map[string][]string{
"one": {"alpha", "bravo"}, "one": {"alpha", "bravo"},
@ -336,6 +339,7 @@ func TestAuthProxyRoundTripper(t *testing.T) {
}, },
"double escaped extra": { "double escaped extra": {
username: "user", username: "user",
uid: "7db46926-e803-4337-9a29-f9c1fab7d34a",
groups: []string{"groupA", "groupB"}, groups: []string{"groupA", "groupB"},
extra: map[string][]string{ extra: map[string][]string{
"one": {"alpha", "bravo"}, "one": {"alpha", "bravo"},
@ -349,7 +353,7 @@ func TestAuthProxyRoundTripper(t *testing.T) {
} { } {
rt := &testRoundTripper{} rt := &testRoundTripper{}
req := &http.Request{} req := &http.Request{}
NewAuthProxyRoundTripper(tc.username, tc.groups, tc.extra, rt).RoundTrip(req) _, _ = NewAuthProxyRoundTripper(tc.username, tc.uid, tc.groups, tc.extra, rt).RoundTrip(req)
if rt.Request == nil { if rt.Request == nil {
t.Errorf("%s: unexpected nil request: %v", n, rt) t.Errorf("%s: unexpected nil request: %v", n, rt)
continue continue
@ -368,6 +372,15 @@ func TestAuthProxyRoundTripper(t *testing.T) {
t.Errorf("%s expected %v, got %v", n, e, a) t.Errorf("%s expected %v, got %v", n, e, a)
continue continue
} }
actualUID, ok := rt.Request.Header["X-Remote-Uid"]
if !ok {
t.Errorf("%s missing value", n)
continue
}
if e, a := []string{tc.uid}, actualUID; !reflect.DeepEqual(e, a) {
t.Errorf("%s expected %v, got %v", n, e, a)
continue
}
actualGroups, ok := rt.Request.Header["X-Remote-Group"] actualGroups, ok := rt.Request.Header["X-Remote-Group"]
if !ok { if !ok {
t.Errorf("%s missing value", n) t.Errorf("%s missing value", n)

View File

@ -159,7 +159,7 @@ func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
proxyRoundTripper := handlingInfo.proxyRoundTripper proxyRoundTripper := handlingInfo.proxyRoundTripper
upgrade := httpstream.IsUpgradeRequest(req) upgrade := httpstream.IsUpgradeRequest(req)
proxyRoundTripper = transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), proxyRoundTripper) proxyRoundTripper = transport.NewAuthProxyRoundTripper(user.GetName(), user.GetUID(), user.GetGroups(), user.GetExtra(), proxyRoundTripper)
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) && !upgrade { if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) && !upgrade {
tracingWrapper := tracing.WrapperFor(r.tracerProvider) tracingWrapper := tracing.WrapperFor(r.tracerProvider)
@ -170,7 +170,7 @@ func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// NOT use the proxyRoundTripper. It's a direct dial that bypasses the proxyRoundTripper. This means that we have to // NOT use the proxyRoundTripper. It's a direct dial that bypasses the proxyRoundTripper. This means that we have to
// attach the "correct" user headers to the request ahead of time. // attach the "correct" user headers to the request ahead of time.
if upgrade { if upgrade {
transport.SetAuthProxyHeaders(newReq, user.GetName(), user.GetGroups(), user.GetExtra()) transport.SetAuthProxyHeaders(newReq, user.GetName(), user.GetUID(), user.GetGroups(), user.GetExtra())
} }
handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, upgrade, &responder{w: w}) handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, upgrade, &responder{w: w})

View File

@ -154,6 +154,7 @@ func TestProxyHandler(t *testing.T) {
"proxy with user, insecure": { "proxy with user, insecure": {
user: &user.DefaultInfo{ user: &user.DefaultInfo{
Name: "username", Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"}, Groups: []string{"one", "two"},
}, },
path: "/request/path", path: "/request/path",
@ -178,6 +179,7 @@ func TestProxyHandler(t *testing.T) {
"X-Forwarded-Uri": {"/request/path"}, "X-Forwarded-Uri": {"/request/path"},
"X-Forwarded-For": {"127.0.0.1"}, "X-Forwarded-For": {"127.0.0.1"},
"X-Remote-User": {"username"}, "X-Remote-User": {"username"},
"X-Remote-Uid": {"6b60d791-1af9-4513-92e5-e4252a1e0a78"},
"User-Agent": {"Go-http-client/1.1"}, "User-Agent": {"Go-http-client/1.1"},
"Accept-Encoding": {"gzip"}, "Accept-Encoding": {"gzip"},
"X-Remote-Group": {"one", "two"}, "X-Remote-Group": {"one", "two"},
@ -186,6 +188,7 @@ func TestProxyHandler(t *testing.T) {
"proxy with user, cabundle": { "proxy with user, cabundle": {
user: &user.DefaultInfo{ user: &user.DefaultInfo{
Name: "username", Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"}, Groups: []string{"one", "two"},
}, },
path: "/request/path", path: "/request/path",
@ -210,6 +213,7 @@ func TestProxyHandler(t *testing.T) {
"X-Forwarded-Uri": {"/request/path"}, "X-Forwarded-Uri": {"/request/path"},
"X-Forwarded-For": {"127.0.0.1"}, "X-Forwarded-For": {"127.0.0.1"},
"X-Remote-User": {"username"}, "X-Remote-User": {"username"},
"X-Remote-Uid": {"6b60d791-1af9-4513-92e5-e4252a1e0a78"},
"User-Agent": {"Go-http-client/1.1"}, "User-Agent": {"Go-http-client/1.1"},
"Accept-Encoding": {"gzip"}, "Accept-Encoding": {"gzip"},
"X-Remote-Group": {"one", "two"}, "X-Remote-Group": {"one", "two"},
@ -218,6 +222,7 @@ func TestProxyHandler(t *testing.T) {
"service unavailable": { "service unavailable": {
user: &user.DefaultInfo{ user: &user.DefaultInfo{
Name: "username", Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"}, Groups: []string{"one", "two"},
}, },
path: "/request/path", path: "/request/path",
@ -240,6 +245,7 @@ func TestProxyHandler(t *testing.T) {
"service unresolveable": { "service unresolveable": {
user: &user.DefaultInfo{ user: &user.DefaultInfo{
Name: "username", Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"}, Groups: []string{"one", "two"},
}, },
path: "/request/path", path: "/request/path",
@ -263,6 +269,7 @@ func TestProxyHandler(t *testing.T) {
"fail on bad serving cert": { "fail on bad serving cert": {
user: &user.DefaultInfo{ user: &user.DefaultInfo{
Name: "username", Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"}, Groups: []string{"one", "two"},
}, },
path: "/request/path", path: "/request/path",
@ -284,6 +291,7 @@ func TestProxyHandler(t *testing.T) {
"fail on bad serving cert w/o SAN and increase SAN error counter metrics": { "fail on bad serving cert w/o SAN and increase SAN error counter metrics": {
user: &user.DefaultInfo{ user: &user.DefaultInfo{
Name: "username", Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"}, Groups: []string{"one", "two"},
}, },
path: "/request/path", path: "/request/path",
@ -425,6 +433,7 @@ func newBrokenDialerAndSelector() (*mockEgressDialer, *egressselector.EgressSele
func TestProxyUpgrade(t *testing.T) { func TestProxyUpgrade(t *testing.T) {
upgradeUser := "upgradeUser" upgradeUser := "upgradeUser"
upgradeUID := "upgradeUser-UID"
testcases := map[string]struct { testcases := map[string]struct {
APIService *apiregistration.APIService APIService *apiregistration.APIService
NewEgressSelector func() (*mockEgressDialer, *egressselector.EgressSelector) NewEgressSelector func() (*mockEgressDialer, *egressselector.EgressSelector)
@ -534,6 +543,10 @@ func TestProxyUpgrade(t *testing.T) {
if user != upgradeUser { if user != upgradeUser {
t.Errorf("expected user %q, got %q", upgradeUser, user) t.Errorf("expected user %q, got %q", upgradeUser, user)
} }
uid := req.Header.Get("X-Remote-Uid")
if uid != upgradeUID {
t.Errorf("expected UID %q, got %q", upgradeUID, uid)
}
body := make([]byte, 5) body := make([]byte, 5)
ws.Read(body) ws.Read(body)
ws.Write([]byte("hello " + string(body))) ws.Write([]byte("hello " + string(body)))
@ -576,7 +589,7 @@ func TestProxyUpgrade(t *testing.T) {
} }
proxyHandler.updateAPIService(tc.APIService) proxyHandler.updateAPIService(tc.APIService)
aggregator := httptest.NewServer(contextHandler(proxyHandler, &user.DefaultInfo{Name: upgradeUser})) aggregator := httptest.NewServer(contextHandler(proxyHandler, &user.DefaultInfo{Name: upgradeUser, UID: upgradeUID}))
defer aggregator.Close() defer aggregator.Close()
ws, err := websocket.Dial("ws://"+aggregator.Listener.Addr().String()+path, "", "http://127.0.0.1/") ws, err := websocket.Dial("ws://"+aggregator.Listener.Addr().String()+path, "", "http://127.0.0.1/")

View File

@ -305,7 +305,7 @@ func (c *AvailableConditionController) sync(key string) error {
} }
// setting the system-masters identity ensures that we will always have access rights // setting the system-masters identity ensures that we will always have access rights
transport.SetAuthProxyHeaders(newReq, "system:kube-aggregator", []string{"system:masters"}, nil) transport.SetAuthProxyHeaders(newReq, "system:kube-aggregator", "", []string{"system:masters"}, nil)
resp, err := discoveryClient.Do(newReq) resp, err := discoveryClient.Do(newReq)
if resp != nil { if resp != nil {
resp.Body.Close() resp.Body.Close()