Merge pull request #105794 from margocrawf/master

--as-uid flag in kubectl and kubeconfigs.

Kubernetes-commit: c9baa14d708415225ed030e520970b3e1b6e4349
This commit is contained in:
Kubernetes Publisher 2021-11-08 07:03:13 -08:00
commit 6d69eb8ad6
10 changed files with 139 additions and 10 deletions

4
go.mod
View File

@ -31,7 +31,7 @@ require (
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
google.golang.org/protobuf v1.26.0
k8s.io/api v0.0.0-20211104203923-7979b39b3911
k8s.io/apimachinery v0.0.0-20211104003341-94020522c95c
k8s.io/apimachinery v0.0.0-20211105203412-d7e096fd217f
k8s.io/klog/v2 v2.30.0
k8s.io/kube-openapi v0.0.0-20210817084001-7fbd8d59e5b8
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b
@ -41,5 +41,5 @@ require (
replace (
k8s.io/api => k8s.io/api v0.0.0-20211104203923-7979b39b3911
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20211104003341-94020522c95c
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20211105203412-d7e096fd217f
)

4
go.sum
View File

@ -600,8 +600,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.0.0-20211104203923-7979b39b3911 h1:KKASx2y52X9MofhS6vOnUdLGgz+CH8/OxZrJsG7lnk4=
k8s.io/api v0.0.0-20211104203923-7979b39b3911/go.mod h1:Y3aSuR3Z7RwvAGbnpQ1+oBIWgAIxvlHcaUCVUzOgpuo=
k8s.io/apimachinery v0.0.0-20211104003341-94020522c95c h1:18i+Svc8AQt2tsHgUJjMgipgIRROqVsdttom0N2URHQ=
k8s.io/apimachinery v0.0.0-20211104003341-94020522c95c/go.mod h1:NdmIf2dMPBkkSfPmCDRbThro3RAWWypv6x+CttBbMto=
k8s.io/apimachinery v0.0.0-20211105203412-d7e096fd217f h1:GhfOaNapo86CszGIgYEv2kO136l13jxtg9Ollcge114=
k8s.io/apimachinery v0.0.0-20211105203412-d7e096fd217f/go.mod h1:NdmIf2dMPBkkSfPmCDRbThro3RAWWypv6x+CttBbMto=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw=

View File

@ -124,7 +124,10 @@ type AuthInfo struct {
// Impersonate is the username to act-as.
// +optional
Impersonate string `json:"act-as,omitempty"`
// ImpersonateGroups is the groups to imperonate.
// ImpersonateUID is the uid to impersonate.
// +optional
ImpersonateUID string `json:"act-as-uid,omitempty"`
// ImpersonateGroups is the groups to impersonate.
// +optional
ImpersonateGroups []string `json:"act-as-groups,omitempty"`
// ImpersonateUserExtra contains additional information for impersonated user.

View File

@ -111,10 +111,13 @@ type AuthInfo struct {
// TokenFile is a pointer to a file that contains a bearer token (as described above). If both Token and TokenFile are present, Token takes precedence.
// +optional
TokenFile string `json:"tokenFile,omitempty"`
// Impersonate is the username to imperonate. The name matches the flag.
// Impersonate is the username to impersonate. The name matches the flag.
// +optional
Impersonate string `json:"as,omitempty"`
// ImpersonateGroups is the groups to imperonate.
// ImpersonateUID is the uid to impersonate.
// +optional
ImpersonateUID string `json:"as-uid,omitempty"`
// ImpersonateGroups is the groups to impersonate.
// +optional
ImpersonateGroups []string `json:"as-groups,omitempty"`
// ImpersonateUserExtra contains additional information for impersonated user.

View File

@ -167,6 +167,7 @@ func autoConvert_v1_AuthInfo_To_api_AuthInfo(in *AuthInfo, out *api.AuthInfo, s
out.Token = in.Token
out.TokenFile = in.TokenFile
out.Impersonate = in.Impersonate
out.ImpersonateUID = in.ImpersonateUID
out.ImpersonateGroups = *(*[]string)(unsafe.Pointer(&in.ImpersonateGroups))
out.ImpersonateUserExtra = *(*map[string][]string)(unsafe.Pointer(&in.ImpersonateUserExtra))
out.Username = in.Username
@ -201,6 +202,7 @@ func autoConvert_api_AuthInfo_To_v1_AuthInfo(in *api.AuthInfo, out *AuthInfo, s
out.Token = in.Token
out.TokenFile = in.TokenFile
out.Impersonate = in.Impersonate
out.ImpersonateUID = in.ImpersonateUID
out.ImpersonateGroups = *(*[]string)(unsafe.Pointer(&in.ImpersonateGroups))
out.ImpersonateUserExtra = *(*map[string][]string)(unsafe.Pointer(&in.ImpersonateUserExtra))
out.Username = in.Username

View File

@ -181,6 +181,7 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
if len(configAuthInfo.Impersonate) > 0 {
clientConfig.Impersonate = restclient.ImpersonationConfig{
UserName: configAuthInfo.Impersonate,
UID: configAuthInfo.ImpersonateUID,
Groups: configAuthInfo.ImpersonateGroups,
Extra: configAuthInfo.ImpersonateUserExtra,
}
@ -255,6 +256,7 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
if len(configAuthInfo.Impersonate) > 0 {
mergedConfig.Impersonate = restclient.ImpersonationConfig{
UserName: configAuthInfo.Impersonate,
UID: configAuthInfo.ImpersonateUID,
Groups: configAuthInfo.ImpersonateGroups,
Extra: configAuthInfo.ImpersonateUserExtra,
}

View File

@ -217,6 +217,43 @@ func TestTLSServerNameClearsWhenServerNameSet(t *testing.T) {
matchStringArg("", actualCfg.ServerName, t)
}
func TestFullImpersonateConfig(t *testing.T) {
config := createValidTestConfig()
config.Clusters["clean"] = &clientcmdapi.Cluster{
Server: "https://localhost:8443",
}
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
Impersonate: "alice",
ImpersonateUID: "abc123",
ImpersonateGroups: []string{"group-1"},
ImpersonateUserExtra: map[string][]string{"some-key": {"some-value"}},
}
config.Contexts["clean"] = &clientcmdapi.Context{
Cluster: "clean",
AuthInfo: "clean",
}
config.CurrentContext = "clean"
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
ClusterInfo: clientcmdapi.Cluster{
Server: "http://something",
},
}, nil)
actualCfg, err := clientBuilder.ClientConfig()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
matchStringArg("alice", actualCfg.Impersonate.UserName, t)
matchStringArg("abc123", actualCfg.Impersonate.UID, t)
matchIntArg(1, len(actualCfg.Impersonate.Groups), t)
matchStringArg("group-1", actualCfg.Impersonate.Groups[0], t)
matchIntArg(1, len(actualCfg.Impersonate.Extra), t)
matchIntArg(1, len(actualCfg.Impersonate.Extra["some-key"]), t)
matchStringArg("some-value", actualCfg.Impersonate.Extra["some-key"][0], t)
}
func TestMergeContext(t *testing.T) {
const namespace = "overridden-namespace"
@ -808,6 +845,12 @@ func matchByteArg(expected, got []byte, t *testing.T) {
}
}
func matchIntArg(expected, got int, t *testing.T) {
if expected != got {
t.Errorf("Expected %d, got %d", expected, got)
}
}
func TestNamespaceOverride(t *testing.T) {
config := &DirectClientConfig{
overrides: &ConfigOverrides{

View File

@ -53,6 +53,7 @@ type AuthOverrideFlags struct {
ClientKey FlagInfo
Token FlagInfo
Impersonate FlagInfo
ImpersonateUID FlagInfo
ImpersonateGroups FlagInfo
Username FlagInfo
Password FlagInfo
@ -154,6 +155,7 @@ const (
FlagEmbedCerts = "embed-certs"
FlagBearerToken = "token"
FlagImpersonate = "as"
FlagImpersonateUID = "as-uid"
FlagImpersonateGroup = "as-group"
FlagUsername = "username"
FlagPassword = "password"
@ -179,6 +181,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"},
ImpersonateUID: FlagInfo{prefix + FlagImpersonateUID, "", "", "UID 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"},
@ -219,6 +222,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.ImpersonateUID.BindStringFlag(flags, &authInfo.ImpersonateUID).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)

View File

@ -323,9 +323,9 @@ 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))
// ImpersonateUID, ImpersonateGroups or ImpersonateUserExtra should be requested with a user
if (len(authInfo.ImpersonateUID) > 0 || len(authInfo.ImpersonateGroups) > 0 || len(authInfo.ImpersonateUserExtra) > 0) && (len(authInfo.Impersonate) == 0) {
validationErrors = append(validationErrors, fmt.Errorf("requesting uid, groups or user-extra for %v without impersonating a user", authInfoName))
}
return validationErrors
}

View File

@ -508,6 +508,78 @@ func TestValidateAuthInfoExecInteractiveModeInvalid(t *testing.T) {
test.testConfig(t)
}
func TestValidateAuthInfoImpersonateUser(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
Impersonate: "user",
}
test := configValidationTest{
config: config,
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
func TestValidateAuthInfoImpersonateEverything(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
Impersonate: "user",
ImpersonateUID: "abc123",
ImpersonateGroups: []string{"group-1", "group-2"},
ImpersonateUserExtra: map[string][]string{"key": {"val1", "val2"}},
}
test := configValidationTest{
config: config,
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
func TestValidateAuthInfoImpersonateGroupsWithoutUserInvalid(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
ImpersonateGroups: []string{"group-1", "group-2"},
}
test := configValidationTest{
config: config,
expectedErrorSubstring: []string{
`requesting uid, groups or user-extra for user without impersonating a user`,
},
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
func TestValidateAuthInfoImpersonateExtraWithoutUserInvalid(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
ImpersonateUserExtra: map[string][]string{"key": {"val1", "val2"}},
}
test := configValidationTest{
config: config,
expectedErrorSubstring: []string{
`requesting uid, groups or user-extra for user without impersonating a user`,
},
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
func TestValidateAuthInfoImpersonateUIDWithoutUserInvalid(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
ImpersonateUID: "abc123",
}
test := configValidationTest{
config: config,
expectedErrorSubstring: []string{
`requesting uid, groups or user-extra for user without impersonating a user`,
},
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
type configValidationTest struct {
config *clientcmdapi.Config
expectedErrorSubstring []string