Skip to content

Commit

Permalink
Introduces Impersonate-Uid to client-go.
Browse files Browse the repository at this point in the history
* 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 <margaretc@vmware.com>
  • Loading branch information
margocrawf committed Sep 24, 2021
1 parent fffaadc commit d9ddfb2
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 19 deletions.
3 changes: 3 additions & 0 deletions pkg/apis/authentication/types.go
Expand Up @@ -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"
Expand Down
5 changes: 4 additions & 1 deletion staging/src/k8s.io/client-go/rest/config.go
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion staging/src/k8s.io/client-go/rest/config_test.go
Expand Up @@ -594,6 +594,7 @@ func TestConfigSprint(t *testing.T) {
BearerToken: "1234567890",
Impersonate: ImpersonationConfig{
UserName: "gopher2",
UID: "uid123",
},
AuthProvider: &clientcmdapi.AuthProviderConfig{
Name: "gopher",
Expand Down Expand Up @@ -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,
)

Expand Down
1 change: 1 addition & 0 deletions staging/src/k8s.io/client-go/rest/transport.go
Expand Up @@ -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,
},
Expand Down
2 changes: 2 additions & 0 deletions staging/src/k8s.io/client-go/transport/config.go
Expand Up @@ -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()
Expand Down
8 changes: 7 additions & 1 deletion staging/src/k8s.io/client-go/transport/round_trippers.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down
30 changes: 30 additions & 0 deletions staging/src/k8s.io/client-go/transport/round_trippers_test.go
Expand Up @@ -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"},
Expand All @@ -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{
Expand Down
21 changes: 5 additions & 16 deletions test/integration/auth/auth_test.go
Expand Up @@ -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.
Expand Down Expand Up @@ -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)

Expand All @@ -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(
Expand Down Expand Up @@ -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{})
Expand Down Expand Up @@ -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{})
Expand Down

0 comments on commit d9ddfb2

Please sign in to comment.