Commit ee3289cc authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #36755 from deads2k/cli-09-extend-impersonate

Automatic merge from submit-queue (batch tested with PRs 36263, 36755, 37357, 37222, 37524) add other impersonation fields to transport Adds the group and extra fields to the impersation options in a rest and transport config. @kubernetes/sig-auth
parents 28e8b5ab bbcf0616
...@@ -71,8 +71,8 @@ type Config struct { ...@@ -71,8 +71,8 @@ type Config struct {
// TODO: demonstrate an OAuth2 compatible client. // TODO: demonstrate an OAuth2 compatible client.
BearerToken string BearerToken string
// Impersonate is the username that this RESTClient will impersonate // Impersonate is the configuration that RESTClient will use for impersonation.
Impersonate string Impersonate ImpersonationConfig
// Server requires plugin-specified authentication. // Server requires plugin-specified authentication.
AuthProvider *clientcmdapi.AuthProviderConfig AuthProvider *clientcmdapi.AuthProviderConfig
...@@ -119,6 +119,17 @@ type Config struct { ...@@ -119,6 +119,17 @@ type Config struct {
// Version string // Version string
} }
// ImpersonationConfig has all the available impersonation options
type ImpersonationConfig struct {
// UserName is the username to impersonate on each request.
UserName 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
// to authorization information. This field allows you to impersonate it.
Extra map[string][]string
}
// TLSClientConfig contains settings to enable transport layer security // TLSClientConfig contains settings to enable transport layer security
type TLSClientConfig struct { type TLSClientConfig struct {
// Server requires TLS client certificate authentication // Server requires TLS client certificate authentication
......
...@@ -205,7 +205,7 @@ func TestAnonymousConfig(t *testing.T) { ...@@ -205,7 +205,7 @@ func TestAnonymousConfig(t *testing.T) {
// this is the list of known security related fields, add to this list if a new field // this is the list of known security related fields, add to this list if a new field
// is added to Config, update AnonymousClientConfig to preserve the field otherwise. // is added to Config, update AnonymousClientConfig to preserve the field otherwise.
expected.Impersonate = "" expected.Impersonate = ImpersonationConfig{}
expected.BearerToken = "" expected.BearerToken = ""
expected.Username = "" expected.Username = ""
expected.Password = "" expected.Password = ""
......
...@@ -89,6 +89,10 @@ func (c *Config) TransportConfig() (*transport.Config, error) { ...@@ -89,6 +89,10 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
Username: c.Username, Username: c.Username,
Password: c.Password, Password: c.Password,
BearerToken: c.BearerToken, BearerToken: c.BearerToken,
Impersonate: c.Impersonate, Impersonate: transport.ImpersonationConfig{
UserName: c.Impersonate.UserName,
Groups: c.Impersonate.Groups,
Extra: c.Impersonate.Extra,
},
}, nil }, nil
} }
...@@ -34,8 +34,8 @@ type Config struct { ...@@ -34,8 +34,8 @@ type Config struct {
// Bearer token for authentication // Bearer token for authentication
BearerToken string BearerToken string
// Impersonate is the username that this Config will impersonate // Impersonate is the config that this Config will impersonate using
Impersonate string Impersonate ImpersonationConfig
// Transport may be used for custom HTTP behavior. This attribute may // Transport may be used for custom HTTP behavior. This attribute may
// not be specified with the TLS client certificate options. Use // not be specified with the TLS client certificate options. Use
...@@ -50,6 +50,16 @@ type Config struct { ...@@ -50,6 +50,16 @@ type Config struct {
WrapTransport func(rt http.RoundTripper) http.RoundTripper WrapTransport func(rt http.RoundTripper) http.RoundTripper
} }
// ImpersonationConfig has all the available impersonation options
type ImpersonationConfig struct {
// UserName matches user.Info.GetName()
UserName string
// Groups matches user.Info.GetGroups()
Groups []string
// Extra matches user.Info.GetExtra()
Extra map[string][]string
}
// HasCA returns whether the configuration has a certificate authority or not. // HasCA returns whether the configuration has a certificate authority or not.
func (c *Config) HasCA() bool { func (c *Config) HasCA() bool {
return len(c.TLS.CAData) > 0 || len(c.TLS.CAFile) > 0 return len(c.TLS.CAData) > 0 || len(c.TLS.CAFile) > 0
......
...@@ -48,7 +48,9 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip ...@@ -48,7 +48,9 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
if len(config.UserAgent) > 0 { if len(config.UserAgent) > 0 {
rt = NewUserAgentRoundTripper(config.UserAgent, rt) rt = NewUserAgentRoundTripper(config.UserAgent, rt)
} }
if len(config.Impersonate) > 0 { if len(config.Impersonate.UserName) > 0 ||
len(config.Impersonate.Groups) > 0 ||
len(config.Impersonate.Extra) > 0 {
rt = NewImpersonatingRoundTripper(config.Impersonate, rt) rt = NewImpersonatingRoundTripper(config.Impersonate, rt)
} }
return rt, nil return rt, nil
...@@ -133,22 +135,53 @@ func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) { ...@@ -133,22 +135,53 @@ func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) {
func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt } func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
// These correspond to the headers used in pkg/apis/authentication. We don't want the package dependency,
// but you must not change the values.
const (
// ImpersonateUserHeader is used to impersonate a particular user during an API server request
ImpersonateUserHeader = "Impersonate-User"
// 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"
// ImpersonateUserExtraHeaderPrefix is a prefix for a header used to impersonate an entry in the
// extra map[string][]string for user.Info. The key for the `extra` map is suffix.
// The same key can be repeated multiple times to have multiple elements in the slice under a single key.
// For instance:
// Impersonate-Extra-Foo: one
// Impersonate-Extra-Foo: two
// results in extra["Foo"] = []string{"one", "two"}
ImpersonateUserExtraHeaderPrefix = "Impersonate-Extra-"
)
type impersonatingRoundTripper struct { type impersonatingRoundTripper struct {
impersonate string impersonate ImpersonationConfig
delegate http.RoundTripper delegate http.RoundTripper
} }
// NewImpersonatingRoundTripper will add an Act-As header to a request unless it has already been set. // NewImpersonatingRoundTripper will add an Act-As header to a request unless it has already been set.
func NewImpersonatingRoundTripper(impersonate string, delegate http.RoundTripper) http.RoundTripper { func NewImpersonatingRoundTripper(impersonate ImpersonationConfig, delegate http.RoundTripper) http.RoundTripper {
return &impersonatingRoundTripper{impersonate, delegate} return &impersonatingRoundTripper{impersonate, delegate}
} }
func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if len(req.Header.Get("Impersonate-User")) != 0 { // use the user header as marker for the rest.
if len(req.Header.Get(ImpersonateUserHeader)) != 0 {
return rt.delegate.RoundTrip(req) return rt.delegate.RoundTrip(req)
} }
req = cloneRequest(req) req = cloneRequest(req)
req.Header.Set("Impersonate-User", rt.impersonate) req.Header.Set(ImpersonateUserHeader, rt.impersonate.UserName)
for _, group := range rt.impersonate.Groups {
req.Header.Add(ImpersonateGroupHeader, group)
}
for k, vv := range rt.impersonate.Extra {
for _, v := range vv {
req.Header.Add(ImpersonateUserExtraHeaderPrefix+k, v)
}
}
return rt.delegate.RoundTrip(req) return rt.delegate.RoundTrip(req)
} }
......
...@@ -18,6 +18,7 @@ package transport ...@@ -18,6 +18,7 @@ package transport
import ( import (
"net/http" "net/http"
"reflect"
"testing" "testing"
) )
...@@ -99,3 +100,58 @@ func TestUserAgentRoundTripper(t *testing.T) { ...@@ -99,3 +100,58 @@ func TestUserAgentRoundTripper(t *testing.T) {
t.Errorf("unexpected user agent header: %#v", rt.Request) t.Errorf("unexpected user agent header: %#v", rt.Request)
} }
} }
func TestImpersonationRoundTripper(t *testing.T) {
tcs := []struct {
name string
impersonationConfig ImpersonationConfig
expected map[string][]string
}{
{
name: "all",
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"},
},
},
}
for _, tc := range tcs {
rt := &testRoundTripper{}
req := &http.Request{
Header: make(http.Header),
}
NewImpersonatingRoundTripper(tc.impersonationConfig, rt).RoundTrip(req)
for k, v := range rt.Request.Header {
expected, ok := tc.expected[k]
if !ok {
t.Errorf("%v missing %v=%v", tc.name, k, v)
continue
}
if !reflect.DeepEqual(expected, v) {
t.Errorf("%v expected %v: %v, got %v", tc.name, k, expected, v)
}
}
for k, v := range tc.expected {
expected, ok := rt.Request.Header[k]
if !ok {
t.Errorf("%v missing %v=%v", tc.name, k, v)
continue
}
if !reflect.DeepEqual(expected, v) {
t.Errorf("%v expected %v: %v, got %v", tc.name, k, expected, v)
}
}
}
}
...@@ -144,7 +144,7 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) { ...@@ -144,7 +144,7 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
clientConfig.Host = u.String() clientConfig.Host = u.String()
} }
if len(configAuthInfo.Impersonate) > 0 { if len(configAuthInfo.Impersonate) > 0 {
clientConfig.Impersonate = configAuthInfo.Impersonate clientConfig.Impersonate = restclient.ImpersonationConfig{UserName: configAuthInfo.Impersonate}
} }
// only try to read the auth information if we are secure // only try to read the auth information if we are secure
...@@ -215,7 +215,7 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa ...@@ -215,7 +215,7 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa
mergedConfig.BearerToken = string(tokenBytes) mergedConfig.BearerToken = string(tokenBytes)
} }
if len(configAuthInfo.Impersonate) > 0 { if len(configAuthInfo.Impersonate) > 0 {
mergedConfig.Impersonate = configAuthInfo.Impersonate mergedConfig.Impersonate = restclient.ImpersonationConfig{UserName: configAuthInfo.Impersonate}
} }
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 { if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
mergedConfig.CertFile = configAuthInfo.ClientCertificate mergedConfig.CertFile = configAuthInfo.ClientCertificate
......
...@@ -183,7 +183,7 @@ ...@@ -183,7 +183,7 @@
}, },
{ {
"ImportPath": "github.com/spf13/pflag", "ImportPath": "github.com/spf13/pflag",
"Rev": "5ccb023bc27df288a957c5e994cd44fd19619465" "Rev": "c7e63cf4530bcd3ba943729cee0efeff2ebea63f"
}, },
{ {
"ImportPath": "github.com/stretchr/testify/assert", "ImportPath": "github.com/stretchr/testify/assert",
......
...@@ -416,7 +416,7 @@ func Set(name, value string) error { ...@@ -416,7 +416,7 @@ func Set(name, value string) error {
// otherwise, the default values of all defined flags in the set. // otherwise, the default values of all defined flags in the set.
func (f *FlagSet) PrintDefaults() { func (f *FlagSet) PrintDefaults() {
usages := f.FlagUsages() usages := f.FlagUsages()
fmt.Fprint(f.out(), usages) fmt.Fprintf(f.out(), "%s", usages)
} }
// defaultIsZeroValue returns true if the default value for this flag represents // defaultIsZeroValue returns true if the default value for this flag represents
...@@ -514,7 +514,7 @@ func (f *FlagSet) FlagUsages() string { ...@@ -514,7 +514,7 @@ func (f *FlagSet) FlagUsages() string {
if len(flag.NoOptDefVal) > 0 { if len(flag.NoOptDefVal) > 0 {
switch flag.Value.Type() { switch flag.Value.Type() {
case "string": case "string":
line += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal) line += fmt.Sprintf("[=%q]", flag.NoOptDefVal)
case "bool": case "bool":
if flag.NoOptDefVal != "true" { if flag.NoOptDefVal != "true" {
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal) line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
...@@ -534,7 +534,7 @@ func (f *FlagSet) FlagUsages() string { ...@@ -534,7 +534,7 @@ func (f *FlagSet) FlagUsages() string {
line += usage line += usage
if !flag.defaultIsZeroValue() { if !flag.defaultIsZeroValue() {
if flag.Value.Type() == "string" { if flag.Value.Type() == "string" {
line += fmt.Sprintf(" (default \"%s\")", flag.DefValue) line += fmt.Sprintf(" (default %q)", flag.DefValue)
} else { } else {
line += fmt.Sprintf(" (default %s)", flag.DefValue) line += fmt.Sprintf(" (default %s)", flag.DefValue)
} }
......
...@@ -2,6 +2,7 @@ package pflag ...@@ -2,6 +2,7 @@ package pflag
import ( import (
"fmt" "fmt"
"strings"
) )
var _ = fmt.Fprint var _ = fmt.Fprint
...@@ -39,7 +40,7 @@ func (s *stringArrayValue) String() string { ...@@ -39,7 +40,7 @@ func (s *stringArrayValue) String() string {
} }
func stringArrayConv(sval string) (interface{}, error) { func stringArrayConv(sval string) (interface{}, error) {
sval = sval[1 : len(sval)-1] sval = strings.Trim(sval, "[]")
// An empty string would cause a array with one (empty) string // An empty string would cause a array with one (empty) string
if len(sval) == 0 { if len(sval) == 0 {
return []string{}, nil return []string{}, nil
......
...@@ -66,7 +66,7 @@ func (s *stringSliceValue) String() string { ...@@ -66,7 +66,7 @@ func (s *stringSliceValue) String() string {
} }
func stringSliceConv(sval string) (interface{}, error) { func stringSliceConv(sval string) (interface{}, error) {
sval = sval[1 : len(sval)-1] sval = strings.Trim(sval, "[]")
// An empty string would cause a slice with one (empty) string // An empty string would cause a slice with one (empty) string
if len(sval) == 0 { if len(sval) == 0 {
return []string{}, nil return []string{}, nil
......
...@@ -71,8 +71,8 @@ type Config struct { ...@@ -71,8 +71,8 @@ type Config struct {
// TODO: demonstrate an OAuth2 compatible client. // TODO: demonstrate an OAuth2 compatible client.
BearerToken string BearerToken string
// Impersonate is the username that this RESTClient will impersonate // Impersonate is the configuration that RESTClient will use for impersonation.
Impersonate string Impersonate ImpersonationConfig
// Server requires plugin-specified authentication. // Server requires plugin-specified authentication.
AuthProvider *clientcmdapi.AuthProviderConfig AuthProvider *clientcmdapi.AuthProviderConfig
...@@ -119,6 +119,17 @@ type Config struct { ...@@ -119,6 +119,17 @@ type Config struct {
// Version string // Version string
} }
// ImpersonationConfig has all the available impersonation options
type ImpersonationConfig struct {
// UserName is the username to impersonate on each request.
UserName 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
// to authorization information. This field allows you to impersonate it.
Extra map[string][]string
}
// TLSClientConfig contains settings to enable transport layer security // TLSClientConfig contains settings to enable transport layer security
type TLSClientConfig struct { type TLSClientConfig struct {
// Server requires TLS client certificate authentication // Server requires TLS client certificate authentication
......
...@@ -205,7 +205,7 @@ func TestAnonymousConfig(t *testing.T) { ...@@ -205,7 +205,7 @@ func TestAnonymousConfig(t *testing.T) {
// this is the list of known security related fields, add to this list if a new field // this is the list of known security related fields, add to this list if a new field
// is added to Config, update AnonymousClientConfig to preserve the field otherwise. // is added to Config, update AnonymousClientConfig to preserve the field otherwise.
expected.Impersonate = "" expected.Impersonate = ImpersonationConfig{}
expected.BearerToken = "" expected.BearerToken = ""
expected.Username = "" expected.Username = ""
expected.Password = "" expected.Password = ""
......
...@@ -89,6 +89,10 @@ func (c *Config) TransportConfig() (*transport.Config, error) { ...@@ -89,6 +89,10 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
Username: c.Username, Username: c.Username,
Password: c.Password, Password: c.Password,
BearerToken: c.BearerToken, BearerToken: c.BearerToken,
Impersonate: c.Impersonate, Impersonate: transport.ImpersonationConfig{
UserName: c.Impersonate.UserName,
Groups: c.Impersonate.Groups,
Extra: c.Impersonate.Extra,
},
}, nil }, nil
} }
...@@ -144,7 +144,7 @@ func (config *DirectClientConfig) ClientConfig() (*rest.Config, error) { ...@@ -144,7 +144,7 @@ func (config *DirectClientConfig) ClientConfig() (*rest.Config, error) {
clientConfig.Host = u.String() clientConfig.Host = u.String()
} }
if len(configAuthInfo.Impersonate) > 0 { if len(configAuthInfo.Impersonate) > 0 {
clientConfig.Impersonate = configAuthInfo.Impersonate clientConfig.Impersonate = rest.ImpersonationConfig{UserName: configAuthInfo.Impersonate}
} }
// only try to read the auth information if we are secure // only try to read the auth information if we are secure
...@@ -215,7 +215,7 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa ...@@ -215,7 +215,7 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa
mergedConfig.BearerToken = string(tokenBytes) mergedConfig.BearerToken = string(tokenBytes)
} }
if len(configAuthInfo.Impersonate) > 0 { if len(configAuthInfo.Impersonate) > 0 {
mergedConfig.Impersonate = configAuthInfo.Impersonate mergedConfig.Impersonate = rest.ImpersonationConfig{UserName: configAuthInfo.Impersonate}
} }
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 { if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
mergedConfig.CertFile = configAuthInfo.ClientCertificate mergedConfig.CertFile = configAuthInfo.ClientCertificate
......
...@@ -244,7 +244,9 @@ func (e *eventLogger) eventObserve(newEvent *v1.Event) (*v1.Event, []byte, error ...@@ -244,7 +244,9 @@ func (e *eventLogger) eventObserve(newEvent *v1.Event) (*v1.Event, []byte, error
newData, _ := json.Marshal(event) newData, _ := json.Marshal(event)
oldData, _ := json.Marshal(eventCopy2) oldData, _ := json.Marshal(eventCopy2)
patch, err = strategicpatch.CreateStrategicMergePatch(oldData, newData, event) // TODO: need to figure out if we need to let eventObserve() use the new behavior of StrategicMergePatch.
// Currently default to old behavior now. Ref: issue #35936
patch, err = strategicpatch.CreateStrategicMergePatch(oldData, newData, event, strategicpatch.SMPatchVersion_1_0)
} }
// record our new observation // record our new observation
......
...@@ -34,8 +34,8 @@ type Config struct { ...@@ -34,8 +34,8 @@ type Config struct {
// Bearer token for authentication // Bearer token for authentication
BearerToken string BearerToken string
// Impersonate is the username that this Config will impersonate // Impersonate is the config that this Config will impersonate using
Impersonate string Impersonate ImpersonationConfig
// Transport may be used for custom HTTP behavior. This attribute may // Transport may be used for custom HTTP behavior. This attribute may
// not be specified with the TLS client certificate options. Use // not be specified with the TLS client certificate options. Use
...@@ -50,6 +50,16 @@ type Config struct { ...@@ -50,6 +50,16 @@ type Config struct {
WrapTransport func(rt http.RoundTripper) http.RoundTripper WrapTransport func(rt http.RoundTripper) http.RoundTripper
} }
// ImpersonationConfig has all the available impersonation options
type ImpersonationConfig struct {
// UserName matches user.Info.GetName()
UserName string
// Groups matches user.Info.GetGroups()
Groups []string
// Extra matches user.Info.GetExtra()
Extra map[string][]string
}
// HasCA returns whether the configuration has a certificate authority or not. // HasCA returns whether the configuration has a certificate authority or not.
func (c *Config) HasCA() bool { func (c *Config) HasCA() bool {
return len(c.TLS.CAData) > 0 || len(c.TLS.CAFile) > 0 return len(c.TLS.CAData) > 0 || len(c.TLS.CAFile) > 0
......
...@@ -48,7 +48,9 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip ...@@ -48,7 +48,9 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
if len(config.UserAgent) > 0 { if len(config.UserAgent) > 0 {
rt = NewUserAgentRoundTripper(config.UserAgent, rt) rt = NewUserAgentRoundTripper(config.UserAgent, rt)
} }
if len(config.Impersonate) > 0 { if len(config.Impersonate.UserName) > 0 ||
len(config.Impersonate.Groups) > 0 ||
len(config.Impersonate.Extra) > 0 {
rt = NewImpersonatingRoundTripper(config.Impersonate, rt) rt = NewImpersonatingRoundTripper(config.Impersonate, rt)
} }
return rt, nil return rt, nil
...@@ -133,22 +135,53 @@ func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) { ...@@ -133,22 +135,53 @@ func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) {
func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt } func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
// These correspond to the headers used in pkg/apis/authentication. We don't want the package dependency,
// but you must not change the values.
const (
// ImpersonateUserHeader is used to impersonate a particular user during an API server request
ImpersonateUserHeader = "Impersonate-User"
// 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"
// ImpersonateUserExtraHeaderPrefix is a prefix for a header used to impersonate an entry in the
// extra map[string][]string for user.Info. The key for the `extra` map is suffix.
// The same key can be repeated multiple times to have multiple elements in the slice under a single key.
// For instance:
// Impersonate-Extra-Foo: one
// Impersonate-Extra-Foo: two
// results in extra["Foo"] = []string{"one", "two"}
ImpersonateUserExtraHeaderPrefix = "Impersonate-Extra-"
)
type impersonatingRoundTripper struct { type impersonatingRoundTripper struct {
impersonate string impersonate ImpersonationConfig
delegate http.RoundTripper delegate http.RoundTripper
} }
// NewImpersonatingRoundTripper will add an Act-As header to a request unless it has already been set. // NewImpersonatingRoundTripper will add an Act-As header to a request unless it has already been set.
func NewImpersonatingRoundTripper(impersonate string, delegate http.RoundTripper) http.RoundTripper { func NewImpersonatingRoundTripper(impersonate ImpersonationConfig, delegate http.RoundTripper) http.RoundTripper {
return &impersonatingRoundTripper{impersonate, delegate} return &impersonatingRoundTripper{impersonate, delegate}
} }
func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if len(req.Header.Get("Impersonate-User")) != 0 { // use the user header as marker for the rest.
if len(req.Header.Get(ImpersonateUserHeader)) != 0 {
return rt.delegate.RoundTrip(req) return rt.delegate.RoundTrip(req)
} }
req = cloneRequest(req) req = cloneRequest(req)
req.Header.Set("Impersonate-User", rt.impersonate) req.Header.Set(ImpersonateUserHeader, rt.impersonate.UserName)
for _, group := range rt.impersonate.Groups {
req.Header.Add(ImpersonateGroupHeader, group)
}
for k, vv := range rt.impersonate.Extra {
for _, v := range vv {
req.Header.Add(ImpersonateUserExtraHeaderPrefix+k, v)
}
}
return rt.delegate.RoundTrip(req) return rt.delegate.RoundTrip(req)
} }
......
...@@ -18,6 +18,7 @@ package transport ...@@ -18,6 +18,7 @@ package transport
import ( import (
"net/http" "net/http"
"reflect"
"testing" "testing"
) )
...@@ -99,3 +100,58 @@ func TestUserAgentRoundTripper(t *testing.T) { ...@@ -99,3 +100,58 @@ func TestUserAgentRoundTripper(t *testing.T) {
t.Errorf("unexpected user agent header: %#v", rt.Request) t.Errorf("unexpected user agent header: %#v", rt.Request)
} }
} }
func TestImpersonationRoundTripper(t *testing.T) {
tcs := []struct {
name string
impersonationConfig ImpersonationConfig
expected map[string][]string
}{
{
name: "all",
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"},
},
},
}
for _, tc := range tcs {
rt := &testRoundTripper{}
req := &http.Request{
Header: make(http.Header),
}
NewImpersonatingRoundTripper(tc.impersonationConfig, rt).RoundTrip(req)
for k, v := range rt.Request.Header {
expected, ok := tc.expected[k]
if !ok {
t.Errorf("%v missing %v=%v", tc.name, k, v)
continue
}
if !reflect.DeepEqual(expected, v) {
t.Errorf("%v expected %v: %v, got %v", tc.name, k, expected, v)
}
}
for k, v := range tc.expected {
expected, ok := rt.Request.Header[k]
if !ok {
t.Errorf("%v missing %v=%v", tc.name, k, v)
continue
}
if !reflect.DeepEqual(expected, v) {
t.Errorf("%v expected %v: %v, got %v", tc.name, k, expected, v)
}
}
}
}
...@@ -10883,6 +10883,7 @@ go_library( ...@@ -10883,6 +10883,7 @@ go_library(
deps = [ deps = [
"//vendor:github.com/davecgh/go-spew/spew", "//vendor:github.com/davecgh/go-spew/spew",
"//vendor:github.com/ghodss/yaml", "//vendor:github.com/ghodss/yaml",
"//vendor:k8s.io/client-go/discovery",
"//vendor:k8s.io/client-go/pkg/third_party/forked/golang/json", "//vendor:k8s.io/client-go/pkg/third_party/forked/golang/json",
"//vendor:k8s.io/client-go/pkg/util/json", "//vendor:k8s.io/client-go/pkg/util/json",
], ],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment