From d9ddfb26e10ee353fc4617b66d2c9274bf6d1c08 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Fri, 30 Jul 2021 15:50:51 -0700 Subject: [PATCH] Introduces Impersonate-Uid to client-go. * Updates ImpersonationConfig in rest/config.go to include UID attribute, and pass it through when copying the config * Updates ImpersonationConfig in transport/config.go to include UID attribute * In transport/round_tripper.go, Set the "Impersonate-Uid" header in requests based on the UID value in the config * Update auth_test.go integration test to specify a UID through the new rest.ImpersonationConfig field rather than manually setting the Impersonate-Uid header Signed-off-by: Margo Crawford --- pkg/apis/authentication/types.go | 3 ++ staging/src/k8s.io/client-go/rest/config.go | 5 +++- .../src/k8s.io/client-go/rest/config_test.go | 3 +- .../src/k8s.io/client-go/rest/transport.go | 1 + .../src/k8s.io/client-go/transport/config.go | 2 ++ .../client-go/transport/round_trippers.go | 8 ++++- .../transport/round_trippers_test.go | 30 +++++++++++++++++++ test/integration/auth/auth_test.go | 21 ++++--------- 8 files changed, 54 insertions(+), 19 deletions(-) diff --git a/pkg/apis/authentication/types.go b/pkg/apis/authentication/types.go index 4b9af6fa773c..a15482c3640d 100644 --- a/pkg/apis/authentication/types.go +++ b/pkg/apis/authentication/types.go @@ -25,6 +25,9 @@ const ( // ImpersonateUserHeader is used to impersonate a particular user during an API server request ImpersonateUserHeader = "Impersonate-User" + // ImpersonateUIDHeader is used to impersonate a particular UID during an API server request. + ImpersonateUidHeader = "Impersonate-Uid" + // ImpersonateGroupHeader is used to impersonate a particular group during an API server request. // It can be repeated multiplied times for multiple groups. ImpersonateGroupHeader = "Impersonate-Group" diff --git a/staging/src/k8s.io/client-go/rest/config.go b/staging/src/k8s.io/client-go/rest/config.go index 3735750bbc53..e179e012cda8 100644 --- a/staging/src/k8s.io/client-go/rest/config.go +++ b/staging/src/k8s.io/client-go/rest/config.go @@ -202,6 +202,8 @@ func (c *Config) String() string { type ImpersonationConfig struct { // UserName is the username to impersonate on each request. UserName string + // UID is a unique value that identifies the user. + UID string // Groups are the groups to impersonate on each request. Groups []string // Extra is a free-form field which can be used to link some authentication information @@ -608,9 +610,10 @@ func CopyConfig(config *Config) *Config { BearerToken: config.BearerToken, BearerTokenFile: config.BearerTokenFile, Impersonate: ImpersonationConfig{ + UserName: config.Impersonate.UserName, + UID: config.Impersonate.UID, Groups: config.Impersonate.Groups, Extra: config.Impersonate.Extra, - UserName: config.Impersonate.UserName, }, AuthProvider: config.AuthProvider, AuthConfigPersister: config.AuthConfigPersister, diff --git a/staging/src/k8s.io/client-go/rest/config_test.go b/staging/src/k8s.io/client-go/rest/config_test.go index 7650928daf66..5cd3cf103ade 100644 --- a/staging/src/k8s.io/client-go/rest/config_test.go +++ b/staging/src/k8s.io/client-go/rest/config_test.go @@ -594,6 +594,7 @@ func TestConfigSprint(t *testing.T) { BearerToken: "1234567890", Impersonate: ImpersonationConfig{ UserName: "gopher2", + UID: "uid123", }, AuthProvider: &clientcmdapi.AuthProviderConfig{ Name: "gopher", @@ -626,7 +627,7 @@ func TestConfigSprint(t *testing.T) { Proxy: fakeProxyFunc, } want := fmt.Sprintf( - `&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.ExecConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: "", ProvideClusterInfo: true, Config: runtime.Object(--- REDACTED ---), StdinUnavailable: false}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), WarningHandler:rest.fakeWarningHandler{}, Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`, + `&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", UID:"uid123", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.ExecConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: "", ProvideClusterInfo: true, Config: runtime.Object(--- REDACTED ---), StdinUnavailable: false}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), WarningHandler:rest.fakeWarningHandler{}, Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`, c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc, ) diff --git a/staging/src/k8s.io/client-go/rest/transport.go b/staging/src/k8s.io/client-go/rest/transport.go index 87792750ad3c..57d9215c0637 100644 --- a/staging/src/k8s.io/client-go/rest/transport.go +++ b/staging/src/k8s.io/client-go/rest/transport.go @@ -83,6 +83,7 @@ func (c *Config) TransportConfig() (*transport.Config, error) { BearerTokenFile: c.BearerTokenFile, Impersonate: transport.ImpersonationConfig{ UserName: c.Impersonate.UserName, + UID: c.Impersonate.UID, Groups: c.Impersonate.Groups, Extra: c.Impersonate.Extra, }, diff --git a/staging/src/k8s.io/client-go/transport/config.go b/staging/src/k8s.io/client-go/transport/config.go index 070474831756..89de798f605e 100644 --- a/staging/src/k8s.io/client-go/transport/config.go +++ b/staging/src/k8s.io/client-go/transport/config.go @@ -82,6 +82,8 @@ type Config struct { type ImpersonationConfig struct { // UserName matches user.Info.GetName() UserName string + // UID matches user.Info.GetUID() + UID string // Groups matches user.Info.GetGroups() Groups []string // Extra matches user.Info.GetExtra() diff --git a/staging/src/k8s.io/client-go/transport/round_trippers.go b/staging/src/k8s.io/client-go/transport/round_trippers.go index 818fd52d6e50..4f2976da7347 100644 --- a/staging/src/k8s.io/client-go/transport/round_trippers.go +++ b/staging/src/k8s.io/client-go/transport/round_trippers.go @@ -57,6 +57,7 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip rt = NewUserAgentRoundTripper(config.UserAgent, rt) } if len(config.Impersonate.UserName) > 0 || + len(config.Impersonate.UID) > 0 || len(config.Impersonate.Groups) > 0 || len(config.Impersonate.Extra) > 0 { rt = NewImpersonatingRoundTripper(config.Impersonate, rt) @@ -199,6 +200,9 @@ const ( // ImpersonateUserHeader is used to impersonate a particular user during an API server request ImpersonateUserHeader = "Impersonate-User" + // ImpersonateUIDHeader is used to impersonate a particular UID during an API server request + ImpersonateUIDHeader = "Impersonate-Uid" + // ImpersonateGroupHeader is used to impersonate a particular group during an API server request. // It can be repeated multiplied times for multiple groups. ImpersonateGroupHeader = "Impersonate-Group" @@ -230,7 +234,9 @@ func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Respons } req = utilnet.CloneRequest(req) req.Header.Set(ImpersonateUserHeader, rt.impersonate.UserName) - + if rt.impersonate.UID != "" { + req.Header.Set(ImpersonateUIDHeader, rt.impersonate.UID) + } for _, group := range rt.impersonate.Groups { req.Header.Add(ImpersonateGroupHeader, group) } diff --git a/staging/src/k8s.io/client-go/transport/round_trippers_test.go b/staging/src/k8s.io/client-go/transport/round_trippers_test.go index f9ab5f570ef7..d0410452f8f6 100644 --- a/staging/src/k8s.io/client-go/transport/round_trippers_test.go +++ b/staging/src/k8s.io/client-go/transport/round_trippers_test.go @@ -202,6 +202,7 @@ func TestImpersonationRoundTripper(t *testing.T) { name: "all", impersonationConfig: ImpersonationConfig{ UserName: "user", + UID: "uid-a", Groups: []string{"one", "two"}, Extra: map[string][]string{ "first": {"A", "a"}, @@ -210,11 +211,40 @@ func TestImpersonationRoundTripper(t *testing.T) { }, expected: map[string][]string{ ImpersonateUserHeader: {"user"}, + ImpersonateUIDHeader: {"uid-a"}, ImpersonateGroupHeader: {"one", "two"}, ImpersonateUserExtraHeaderPrefix + "First": {"A", "a"}, ImpersonateUserExtraHeaderPrefix + "Second": {"B", "b"}, }, }, + { + name: "username, groups and extra", + impersonationConfig: ImpersonationConfig{ + UserName: "user", + Groups: []string{"one", "two"}, + Extra: map[string][]string{ + "first": {"A", "a"}, + "second": {"B", "b"}, + }, + }, + expected: map[string][]string{ + ImpersonateUserHeader: {"user"}, + ImpersonateGroupHeader: {"one", "two"}, + ImpersonateUserExtraHeaderPrefix + "First": {"A", "a"}, + ImpersonateUserExtraHeaderPrefix + "Second": {"B", "b"}, + }, + }, + { + name: "username and uid", + impersonationConfig: ImpersonationConfig{ + UserName: "user", + UID: "uid-a", + }, + expected: map[string][]string{ + ImpersonateUserHeader: {"user"}, + ImpersonateUIDHeader: {"uid-a"}, + }, + }, { name: "escape handling", impersonationConfig: ImpersonationConfig{ diff --git a/test/integration/auth/auth_test.go b/test/integration/auth/auth_test.go index 61400a834a36..a0ddba6302a5 100644 --- a/test/integration/auth/auth_test.go +++ b/test/integration/auth/auth_test.go @@ -72,12 +72,6 @@ import ( "k8s.io/kubernetes/test/integration/framework" ) -type roundTripperFunc func(*http.Request) (*http.Response, error) - -func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req) -} - const ( AliceToken string = "abc123" // username: alice. Present in token file. BobToken string = "xyz987" // username: bob. Present in token file. @@ -910,13 +904,6 @@ func TestImpersonateWithUID(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) t.Cleanup(cancel) - setUIDWrapper := func(rt http.RoundTripper) http.RoundTripper { - return roundTripperFunc(func(req *http.Request) (*http.Response, error) { - req.Header.Set("Impersonate-Uid", "1234") - return rt.RoundTrip(req) - }) - } - t.Run("impersonation with uid header", func(t *testing.T) { adminClient := clientset.NewForConfigOrDie(server.ClientConfig) @@ -933,8 +920,8 @@ func TestImpersonateWithUID(t *testing.T) { clientConfig := rest.CopyConfig(server.ClientConfig) clientConfig.Impersonate = rest.ImpersonationConfig{ UserName: "alice", + UID: "1234", } - clientConfig.Wrap(setUIDWrapper) client := clientset.NewForConfigOrDie(clientConfig) createdCsr, err := client.CertificatesV1().CertificateSigningRequests().Create( @@ -974,7 +961,9 @@ func TestImpersonateWithUID(t *testing.T) { t.Run("impersonation with only UID fails", func(t *testing.T) { clientConfig := rest.CopyConfig(server.ClientConfig) - clientConfig.Wrap(setUIDWrapper) + clientConfig.Impersonate = rest.ImpersonationConfig{ + UID: "1234", + } client := clientset.NewForConfigOrDie(clientConfig) _, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) @@ -1007,8 +996,8 @@ func TestImpersonateWithUID(t *testing.T) { clientConfig := rest.AnonymousClientConfig(server.ClientConfig) clientConfig.Impersonate = rest.ImpersonationConfig{ UserName: "some-user-anonymous-can-impersonate", + UID: "1234", } - clientConfig.Wrap(setUIDWrapper) client := clientset.NewForConfigOrDie(clientConfig) _, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})