From 29b5bff0fa6ae098128e5b9c4fe5096bb84b8603 Mon Sep 17 00:00:00 2001 From: Cao Shufeng Date: Thu, 23 Mar 2017 23:18:53 -0400 Subject: [PATCH] add --as-group option to cli The usecase of this change: When a super user grant some RBAC permissions to a group, he can use --as-group to test whether the group get the permissions. Note that now we support as-groups, as-user-extra in kubeconfig file after this change. Kubernetes-commit: e541defd49d01024d17dddf8e966eba2c46a6db0 --- tools/clientcmd/api/types.go | 11 ++++++- tools/clientcmd/api/v1/types.go | 6 ++++ tools/clientcmd/client_config.go | 12 ++++++-- tools/clientcmd/overrides.go | 49 +++++++++++++++++++++----------- tools/clientcmd/validation.go | 4 +++ 5 files changed, 63 insertions(+), 19 deletions(-) diff --git a/tools/clientcmd/api/types.go b/tools/clientcmd/api/types.go index d52c7c94..11a5e7a2 100644 --- a/tools/clientcmd/api/types.go +++ b/tools/clientcmd/api/types.go @@ -109,6 +109,12 @@ type AuthInfo struct { // Impersonate is the username to act-as. // +optional Impersonate string `json:"act-as,omitempty"` + // ImpersonateGroups is the groups to imperonate. + // +optional + ImpersonateGroups []string `json:"act-as-groups,omitempty"` + // ImpersonateUserExtra contains additional information for impersonated user. + // +optional + ImpersonateUserExtra map[string][]string `json:"act-as-user-extra,omitempty"` // Username is the username for basic authentication to the kubernetes cluster. // +optional Username string `json:"username,omitempty"` @@ -172,7 +178,10 @@ func NewCluster() *Cluster { // NewAuthInfo is a convenience function that returns a new AuthInfo // object with non-nil maps func NewAuthInfo() *AuthInfo { - return &AuthInfo{Extensions: make(map[string]runtime.Object)} + return &AuthInfo{ + Extensions: make(map[string]runtime.Object), + ImpersonateUserExtra: make(map[string][]string), + } } // NewPreferences is a convenience function that returns a new diff --git a/tools/clientcmd/api/v1/types.go b/tools/clientcmd/api/v1/types.go index a0777ba5..bad2f6ac 100644 --- a/tools/clientcmd/api/v1/types.go +++ b/tools/clientcmd/api/v1/types.go @@ -103,6 +103,12 @@ type AuthInfo struct { // Impersonate is the username to imperonate. The name matches the flag. // +optional Impersonate string `json:"as,omitempty"` + // ImpersonateGroups is the groups to imperonate. + // +optional + ImpersonateGroups []string `json:"as-groups,omitempty"` + // ImpersonateUserExtra contains additional information for impersonated user. + // +optional + ImpersonateUserExtra map[string][]string `json:"as-user-extra,omitempty"` // Username is the username for basic authentication to the kubernetes cluster. // +optional Username string `json:"username,omitempty"` diff --git a/tools/clientcmd/client_config.go b/tools/clientcmd/client_config.go index c5c35307..b622c6dc 100644 --- a/tools/clientcmd/client_config.go +++ b/tools/clientcmd/client_config.go @@ -146,7 +146,11 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) { clientConfig.Host = u.String() } if len(configAuthInfo.Impersonate) > 0 { - clientConfig.Impersonate = restclient.ImpersonationConfig{UserName: configAuthInfo.Impersonate} + clientConfig.Impersonate = restclient.ImpersonationConfig{ + UserName: configAuthInfo.Impersonate, + Groups: configAuthInfo.ImpersonateGroups, + Extra: configAuthInfo.ImpersonateUserExtra, + } } // only try to read the auth information if we are secure @@ -217,7 +221,11 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI mergedConfig.BearerToken = string(tokenBytes) } if len(configAuthInfo.Impersonate) > 0 { - mergedConfig.Impersonate = restclient.ImpersonationConfig{UserName: configAuthInfo.Impersonate} + mergedConfig.Impersonate = restclient.ImpersonationConfig{ + UserName: configAuthInfo.Impersonate, + Groups: configAuthInfo.ImpersonateGroups, + Extra: configAuthInfo.ImpersonateUserExtra, + } } if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 { mergedConfig.CertFile = configAuthInfo.ClientCertificate diff --git a/tools/clientcmd/overrides.go b/tools/clientcmd/overrides.go index 9ab8b654..bd817bbf 100644 --- a/tools/clientcmd/overrides.go +++ b/tools/clientcmd/overrides.go @@ -52,6 +52,7 @@ type AuthOverrideFlags struct { ClientKey FlagInfo Token FlagInfo Impersonate FlagInfo + ImpersonateGroups FlagInfo Username FlagInfo Password FlagInfo } @@ -100,6 +101,19 @@ func (f FlagInfo) BindStringFlag(flags *pflag.FlagSet, target *string) FlagInfo return f } +// BindStringSliceFlag binds the flag based on the provided info. If LongName == "", nothing is registered +func (f FlagInfo) BindStringArrayFlag(flags *pflag.FlagSet, target *[]string) FlagInfo { + // you can't register a flag without a long name + if len(f.LongName) > 0 { + sliceVal := []string{} + if len(f.Default) > 0 { + sliceVal = []string{f.Default} + } + flags.StringArrayVarP(target, f.LongName, f.ShortName, sliceVal, f.Description) + } + return f +} + // BindBoolFlag binds the flag based on the provided info. If LongName == "", nothing is registered func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) FlagInfo { // you can't register a flag without a long name @@ -116,22 +130,23 @@ func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) FlagInfo { } const ( - FlagClusterName = "cluster" - FlagAuthInfoName = "user" - FlagContext = "context" - FlagNamespace = "namespace" - FlagAPIServer = "server" - FlagAPIVersion = "api-version" - FlagInsecure = "insecure-skip-tls-verify" - FlagCertFile = "client-certificate" - FlagKeyFile = "client-key" - FlagCAFile = "certificate-authority" - FlagEmbedCerts = "embed-certs" - FlagBearerToken = "token" - FlagImpersonate = "as" - FlagUsername = "username" - FlagPassword = "password" - FlagTimeout = "request-timeout" + FlagClusterName = "cluster" + FlagAuthInfoName = "user" + FlagContext = "context" + FlagNamespace = "namespace" + FlagAPIServer = "server" + FlagAPIVersion = "api-version" + FlagInsecure = "insecure-skip-tls-verify" + FlagCertFile = "client-certificate" + FlagKeyFile = "client-key" + FlagCAFile = "certificate-authority" + FlagEmbedCerts = "embed-certs" + FlagBearerToken = "token" + FlagImpersonate = "as" + FlagImpersonateGroup = "as-group" + FlagUsername = "username" + FlagPassword = "password" + FlagTimeout = "request-timeout" ) // RecommendedConfigOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing @@ -153,6 +168,7 @@ func RecommendedAuthOverrideFlags(prefix string) AuthOverrideFlags { ClientKey: FlagInfo{prefix + FlagKeyFile, "", "", "Path to a client key file for TLS"}, Token: FlagInfo{prefix + FlagBearerToken, "", "", "Bearer token for authentication to the API server"}, Impersonate: FlagInfo{prefix + FlagImpersonate, "", "", "Username to impersonate for the operation"}, + ImpersonateGroups: FlagInfo{prefix + FlagImpersonateGroup, "", "", "Group to impersonate for the operation, this flag can be repeated to specify multiple groups."}, Username: FlagInfo{prefix + FlagUsername, "", "", "Username for basic authentication to the API server"}, Password: FlagInfo{prefix + FlagPassword, "", "", "Password for basic authentication to the API server"}, } @@ -192,6 +208,7 @@ func BindAuthInfoFlags(authInfo *clientcmdapi.AuthInfo, flags *pflag.FlagSet, fl flagNames.ClientKey.BindStringFlag(flags, &authInfo.ClientKey).AddSecretAnnotation(flags) flagNames.Token.BindStringFlag(flags, &authInfo.Token).AddSecretAnnotation(flags) flagNames.Impersonate.BindStringFlag(flags, &authInfo.Impersonate).AddSecretAnnotation(flags) + flagNames.ImpersonateGroups.BindStringArrayFlag(flags, &authInfo.ImpersonateGroups).AddSecretAnnotation(flags) flagNames.Username.BindStringFlag(flags, &authInfo.Username).AddSecretAnnotation(flags) flagNames.Password.BindStringFlag(flags, &authInfo.Password).AddSecretAnnotation(flags) } diff --git a/tools/clientcmd/validation.go b/tools/clientcmd/validation.go index 18cf2680..2bae0c39 100644 --- a/tools/clientcmd/validation.go +++ b/tools/clientcmd/validation.go @@ -242,6 +242,10 @@ func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []err validationErrors = append(validationErrors, fmt.Errorf("more than one authentication method found for %v; found %v, only one is allowed", authInfoName, methods)) } + // ImpersonateGroups or ImpersonateUserExtra should be requested with a user + if (len(authInfo.ImpersonateGroups) > 0 || len(authInfo.ImpersonateUserExtra) > 0) && (len(authInfo.Impersonate) == 0) { + validationErrors = append(validationErrors, fmt.Errorf("requesting groups or user-extra for %v without impersonating a user", authInfoName)) + } return validationErrors }