add other impersonation fields to transport

This commit is contained in:
deads2k 2016-11-14 11:32:27 -05:00
parent c9d0969d25
commit ef19bf8413
7 changed files with 127 additions and 13 deletions

View File

@ -70,8 +70,8 @@ type Config struct {
// TODO: demonstrate an OAuth2 compatible client.
BearerToken string
// Impersonate is the username that this RESTClient will impersonate
Impersonate string
// Impersonate is the configuration that RESTClient will use for impersonation.
Impersonate ImpersonationConfig
// Server requires plugin-specified authentication.
AuthProvider *clientcmdapi.AuthProviderConfig
@ -118,6 +118,17 @@ type Config struct {
// 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
type TLSClientConfig struct {
// Server requires TLS client certificate authentication

View File

@ -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
// is added to Config, update AnonymousClientConfig to preserve the field otherwise.
expected.Impersonate = ""
expected.Impersonate = ImpersonationConfig{}
expected.BearerToken = ""
expected.Username = ""
expected.Password = ""

View File

@ -89,6 +89,10 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
Username: c.Username,
Password: c.Password,
BearerToken: c.BearerToken,
Impersonate: c.Impersonate,
Impersonate: transport.ImpersonationConfig{
UserName: c.Impersonate.UserName,
Groups: c.Impersonate.Groups,
Extra: c.Impersonate.Extra,
},
}, nil
}

View File

@ -34,8 +34,8 @@ type Config struct {
// Bearer token for authentication
BearerToken string
// Impersonate is the username that this Config will impersonate
Impersonate string
// Impersonate is the config that this Config will impersonate using
Impersonate ImpersonationConfig
// Transport may be used for custom HTTP behavior. This attribute may
// not be specified with the TLS client certificate options. Use
@ -50,6 +50,16 @@ type Config struct {
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.
func (c *Config) HasCA() bool {
return len(c.TLS.CAData) > 0 || len(c.TLS.CAFile) > 0

View File

@ -48,7 +48,9 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
if len(config.UserAgent) > 0 {
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)
}
return rt, nil
@ -133,22 +135,53 @@ func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) {
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 {
impersonate string
impersonate ImpersonationConfig
delegate http.RoundTripper
}
// 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}
}
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)
}
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)
}

View File

@ -18,6 +18,7 @@ package transport
import (
"net/http"
"reflect"
"testing"
)
@ -99,3 +100,58 @@ func TestUserAgentRoundTripper(t *testing.T) {
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)
}
}
}
}

View File

@ -144,7 +144,7 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
clientConfig.Host = u.String()
}
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
@ -215,7 +215,7 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa
mergedConfig.BearerToken = string(tokenBytes)
}
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 {
mergedConfig.CertFile = configAuthInfo.ClientCertificate