mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 15:58:37 +00:00
Merge pull request #30007 from ericchiang/kubectl-config-set-credentials-auth-providers
Automatic merge from submit-queue kubectl config set-crentials: add arguments for auth providers This PR adds `--auth-provider` and `--auth-provider-arg` flags to the `kubectl config set-credentials` sub-command. There's currently no way of interacting with the new auth provider framework added in #23066 through kubectl. You have to render a custom kubeconfig to use them. Additionally `kubectl config set` just sort of craps out when attempting to interact with authentication info objects (#29312). This is a minimal implementation of allowing `kubect config set-credentials` to set fields for client auth providers. cc @cjcullen @kubernetes/kubectl
This commit is contained in:
commit
5f44431275
@ -17,6 +17,8 @@ api-token
|
|||||||
api-version
|
api-version
|
||||||
apiserver-count
|
apiserver-count
|
||||||
auth-path
|
auth-path
|
||||||
|
auth-provider
|
||||||
|
auth-provider-arg
|
||||||
authentication-token-webhook-cache-ttl
|
authentication-token-webhook-cache-ttl
|
||||||
authentication-token-webhook-config-file
|
authentication-token-webhook-config-file
|
||||||
authorization-mode
|
authorization-mode
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||||
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
"k8s.io/kubernetes/pkg/util"
|
"k8s.io/kubernetes/pkg/util"
|
||||||
"k8s.io/kubernetes/pkg/util/flag"
|
"k8s.io/kubernetes/pkg/util/flag"
|
||||||
)
|
)
|
||||||
@ -43,8 +44,17 @@ type createAuthInfoOptions struct {
|
|||||||
username util.StringFlag
|
username util.StringFlag
|
||||||
password util.StringFlag
|
password util.StringFlag
|
||||||
embedCertData flag.Tristate
|
embedCertData flag.Tristate
|
||||||
|
authProvider util.StringFlag
|
||||||
|
|
||||||
|
authProviderArgs map[string]string
|
||||||
|
authProviderArgsToRemove []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagAuthProvider = "auth-provider"
|
||||||
|
flagAuthProviderArg = "auth-provider-arg"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
create_authinfo_long = fmt.Sprintf(`
|
create_authinfo_long = fmt.Sprintf(`
|
||||||
Sets a user entry in kubeconfig
|
Sets a user entry in kubeconfig
|
||||||
@ -71,19 +81,32 @@ Specifying a name that already exists will merge new fields on top of existing v
|
|||||||
kubectl config set-credentials cluster-admin --username=admin --password=uXFGweU9l35qcif
|
kubectl config set-credentials cluster-admin --username=admin --password=uXFGweU9l35qcif
|
||||||
|
|
||||||
# Embed client certificate data in the "cluster-admin" entry
|
# Embed client certificate data in the "cluster-admin" entry
|
||||||
kubectl config set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true`)
|
kubectl config set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true
|
||||||
|
|
||||||
|
# Enable the Google Compute Platform auth provider for the "cluster-admin" entry
|
||||||
|
kubectl config set-credentials cluster-admin --auth-provider=gcp
|
||||||
|
|
||||||
|
# Enable the OpenID Connect auth provider for the "cluster-admin" entry with additional args
|
||||||
|
kubectl config set-credentials cluster-admin --auth-provider=oidc --auth-provider-arg=client-id=foo --auth-provider-arg=client-secret=bar
|
||||||
|
|
||||||
|
# Remove the "client-secret" config value for the OpenID Connect auth provider for the "cluster-admin" entry
|
||||||
|
kubectl config set-credentials cluster-admin --auth-provider=oidc --auth-provider-arg=client-secret-`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||||
options := &createAuthInfoOptions{configAccess: configAccess}
|
options := &createAuthInfoOptions{configAccess: configAccess}
|
||||||
|
return newCmdConfigSetAuthInfo(out, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCmdConfigSetAuthInfo(out io.Writer, options *createAuthInfoOptions) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: fmt.Sprintf("set-credentials NAME [--%v=path/to/certfile] [--%v=path/to/keyfile] [--%v=bearer_token] [--%v=basic_user] [--%v=basic_password]", clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword),
|
Use: fmt.Sprintf("set-credentials NAME [--%v=path/to/certfile] [--%v=path/to/keyfile] [--%v=bearer_token] [--%v=basic_user] [--%v=basic_password] [--%v=provider_name] [--%v=key=value]", clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword, flagAuthProvider, flagAuthProviderArg),
|
||||||
Short: "Sets a user entry in kubeconfig",
|
Short: "Sets a user entry in kubeconfig",
|
||||||
Long: create_authinfo_long,
|
Long: create_authinfo_long,
|
||||||
Example: create_authinfo_example,
|
Example: create_authinfo_example,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if !options.complete(cmd) {
|
if !options.complete(cmd, out) {
|
||||||
|
cmd.Help()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +126,8 @@ func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess)
|
|||||||
cmd.Flags().Var(&options.token, clientcmd.FlagBearerToken, clientcmd.FlagBearerToken+" for the user entry in kubeconfig")
|
cmd.Flags().Var(&options.token, clientcmd.FlagBearerToken, clientcmd.FlagBearerToken+" for the user entry in kubeconfig")
|
||||||
cmd.Flags().Var(&options.username, clientcmd.FlagUsername, clientcmd.FlagUsername+" for the user entry in kubeconfig")
|
cmd.Flags().Var(&options.username, clientcmd.FlagUsername, clientcmd.FlagUsername+" for the user entry in kubeconfig")
|
||||||
cmd.Flags().Var(&options.password, clientcmd.FlagPassword, clientcmd.FlagPassword+" for the user entry in kubeconfig")
|
cmd.Flags().Var(&options.password, clientcmd.FlagPassword, clientcmd.FlagPassword+" for the user entry in kubeconfig")
|
||||||
|
cmd.Flags().Var(&options.authProvider, flagAuthProvider, "auth provider for the user entry in kubeconfig")
|
||||||
|
cmd.Flags().StringSlice(flagAuthProviderArg, nil, "'key=value' arugments for the auth provider")
|
||||||
f := cmd.Flags().VarPF(&options.embedCertData, clientcmd.FlagEmbedCerts, "", "embed client cert/key for the user entry in kubeconfig")
|
f := cmd.Flags().VarPF(&options.embedCertData, clientcmd.FlagEmbedCerts, "", "embed client cert/key for the user entry in kubeconfig")
|
||||||
f.NoOptDefVal = "true"
|
f.NoOptDefVal = "true"
|
||||||
|
|
||||||
@ -180,6 +205,28 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut
|
|||||||
modifiedAuthInfo.Password = o.password.Value()
|
modifiedAuthInfo.Password = o.password.Value()
|
||||||
setBasic = setBasic || len(modifiedAuthInfo.Password) > 0
|
setBasic = setBasic || len(modifiedAuthInfo.Password) > 0
|
||||||
}
|
}
|
||||||
|
if o.authProvider.Provided() {
|
||||||
|
newName := o.authProvider.Value()
|
||||||
|
|
||||||
|
// Only overwrite if the existing auth-provider is nil, or different than the newly specified one.
|
||||||
|
if modifiedAuthInfo.AuthProvider == nil || modifiedAuthInfo.AuthProvider.Name != newName {
|
||||||
|
modifiedAuthInfo.AuthProvider = &clientcmdapi.AuthProviderConfig{
|
||||||
|
Name: newName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if modifiedAuthInfo.AuthProvider != nil {
|
||||||
|
if modifiedAuthInfo.AuthProvider.Config == nil {
|
||||||
|
modifiedAuthInfo.AuthProvider.Config = make(map[string]string)
|
||||||
|
}
|
||||||
|
for _, toRemove := range o.authProviderArgsToRemove {
|
||||||
|
delete(modifiedAuthInfo.AuthProvider.Config, toRemove)
|
||||||
|
}
|
||||||
|
for key, value := range o.authProviderArgs {
|
||||||
|
modifiedAuthInfo.AuthProvider.Config[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If any auth info was set, make sure any other existing auth types are cleared
|
// If any auth info was set, make sure any other existing auth types are cleared
|
||||||
if setToken || setBasic {
|
if setToken || setBasic {
|
||||||
@ -195,13 +242,28 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut
|
|||||||
return modifiedAuthInfo
|
return modifiedAuthInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *createAuthInfoOptions) complete(cmd *cobra.Command) bool {
|
func (o *createAuthInfoOptions) complete(cmd *cobra.Command, out io.Writer) bool {
|
||||||
args := cmd.Flags().Args()
|
args := cmd.Flags().Args()
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
cmd.Help()
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authProviderArgs, err := cmd.Flags().GetStringSlice(flagAuthProviderArg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(out, "Error: %s\n", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(authProviderArgs) > 0 {
|
||||||
|
newPairs, removePairs, err := cmdutil.ParsePairs(authProviderArgs, flagAuthProviderArg, true)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(out, "Error: %s\n", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
o.authProviderArgs = newPairs
|
||||||
|
o.authProviderArgsToRemove = removePairs
|
||||||
|
}
|
||||||
|
|
||||||
o.name = args[0]
|
o.name = args[0]
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
190
pkg/kubectl/cmd/config/create_authinfo_test.go
Normal file
190
pkg/kubectl/cmd/config/create_authinfo_test.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func stringFlagFor(s string) util.StringFlag {
|
||||||
|
var f util.StringFlag
|
||||||
|
f.Set(s)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAuthInfoOptions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
flags []string
|
||||||
|
wantParseErr bool
|
||||||
|
wantCompleteErr bool
|
||||||
|
wantValidateErr bool
|
||||||
|
|
||||||
|
wantOptions *createAuthInfoOptions
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
flags: []string{
|
||||||
|
"me",
|
||||||
|
},
|
||||||
|
wantOptions: &createAuthInfoOptions{
|
||||||
|
name: "me",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flags: []string{
|
||||||
|
"me",
|
||||||
|
"--token=foo",
|
||||||
|
},
|
||||||
|
wantOptions: &createAuthInfoOptions{
|
||||||
|
name: "me",
|
||||||
|
token: stringFlagFor("foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flags: []string{
|
||||||
|
"me",
|
||||||
|
"--username=jane",
|
||||||
|
"--password=bar",
|
||||||
|
},
|
||||||
|
wantOptions: &createAuthInfoOptions{
|
||||||
|
name: "me",
|
||||||
|
username: stringFlagFor("jane"),
|
||||||
|
password: stringFlagFor("bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Cannot provide both token and basic auth.
|
||||||
|
flags: []string{
|
||||||
|
"me",
|
||||||
|
"--token=foo",
|
||||||
|
"--username=jane",
|
||||||
|
"--password=bar",
|
||||||
|
},
|
||||||
|
wantValidateErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flags: []string{
|
||||||
|
"--auth-provider=oidc",
|
||||||
|
"--auth-provider-arg=client-id=foo",
|
||||||
|
"--auth-provider-arg=client-secret=bar",
|
||||||
|
"me",
|
||||||
|
},
|
||||||
|
wantOptions: &createAuthInfoOptions{
|
||||||
|
name: "me",
|
||||||
|
authProvider: stringFlagFor("oidc"),
|
||||||
|
authProviderArgs: map[string]string{
|
||||||
|
"client-id": "foo",
|
||||||
|
"client-secret": "bar",
|
||||||
|
},
|
||||||
|
authProviderArgsToRemove: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flags: []string{
|
||||||
|
"--auth-provider=oidc",
|
||||||
|
"--auth-provider-arg=client-id-",
|
||||||
|
"--auth-provider-arg=client-secret-",
|
||||||
|
"me",
|
||||||
|
},
|
||||||
|
wantOptions: &createAuthInfoOptions{
|
||||||
|
name: "me",
|
||||||
|
authProvider: stringFlagFor("oidc"),
|
||||||
|
authProviderArgs: map[string]string{},
|
||||||
|
authProviderArgsToRemove: []string{
|
||||||
|
"client-id",
|
||||||
|
"client-secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flags: []string{
|
||||||
|
"--auth-provider-arg=client-id-", // auth provider name not required
|
||||||
|
"--auth-provider-arg=client-secret-",
|
||||||
|
"me",
|
||||||
|
},
|
||||||
|
wantOptions: &createAuthInfoOptions{
|
||||||
|
name: "me",
|
||||||
|
authProviderArgs: map[string]string{},
|
||||||
|
authProviderArgsToRemove: []string{
|
||||||
|
"client-id",
|
||||||
|
"client-secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flags: []string{
|
||||||
|
"--auth-provider=oidc",
|
||||||
|
"--auth-provider-arg=client-id", // values must be of form 'key=value' or 'key-'
|
||||||
|
"me",
|
||||||
|
},
|
||||||
|
wantCompleteErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flags: []string{
|
||||||
|
// No name for authinfo provided.
|
||||||
|
},
|
||||||
|
wantCompleteErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
buff := new(bytes.Buffer)
|
||||||
|
|
||||||
|
opts := new(createAuthInfoOptions)
|
||||||
|
cmd := newCmdConfigSetAuthInfo(buff, opts)
|
||||||
|
if err := cmd.ParseFlags(test.flags); err != nil {
|
||||||
|
if !test.wantParseErr {
|
||||||
|
t.Errorf("case %d: parsing error for flags %q: %v: %s", i, test.flags, err, buff)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.wantParseErr {
|
||||||
|
t.Errorf("case %d: expected parsing error for flags %q: %s", i, test.flags, buff)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.complete(cmd, buff) {
|
||||||
|
if !test.wantCompleteErr {
|
||||||
|
t.Errorf("case %d: complete() error for flags %q: %s", i, test.flags, buff)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.wantCompleteErr {
|
||||||
|
t.Errorf("case %d: complete() expected errors for flags %q: %s", i, test.flags, buff)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := opts.validate(); err != nil {
|
||||||
|
if !test.wantValidateErr {
|
||||||
|
t.Errorf("case %d: flags %q: validate failed: %v", i, test.flags, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.wantValidateErr {
|
||||||
|
t.Errorf("case %d: flags %q: expected validate to fail", i, test.flags)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(opts, test.wantOptions) {
|
||||||
|
t.Errorf("case %d: flags %q: mis-matched options,\nwanted=%#v\ngot= %#v", i, test.flags, test.wantOptions, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user