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:
Kubernetes Submit Queue 2016-08-05 22:22:22 -07:00 committed by GitHub
commit 5f44431275
3 changed files with 259 additions and 5 deletions

View File

@ -17,6 +17,8 @@ api-token
api-version
apiserver-count
auth-path
auth-provider
auth-provider-arg
authentication-token-webhook-cache-ttl
authentication-token-webhook-config-file
authorization-mode

View File

@ -29,6 +29,7 @@ import (
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
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/flag"
)
@ -43,8 +44,17 @@ type createAuthInfoOptions struct {
username util.StringFlag
password util.StringFlag
embedCertData flag.Tristate
authProvider util.StringFlag
authProviderArgs map[string]string
authProviderArgsToRemove []string
}
const (
flagAuthProvider = "auth-provider"
flagAuthProviderArg = "auth-provider-arg"
)
var (
create_authinfo_long = fmt.Sprintf(`
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
# 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 {
options := &createAuthInfoOptions{configAccess: configAccess}
return newCmdConfigSetAuthInfo(out, options)
}
func newCmdConfigSetAuthInfo(out io.Writer, options *createAuthInfoOptions) *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",
Long: create_authinfo_long,
Example: create_authinfo_example,
Run: func(cmd *cobra.Command, args []string) {
if !options.complete(cmd) {
if !options.complete(cmd, out) {
cmd.Help()
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.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.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.NoOptDefVal = "true"
@ -180,6 +205,28 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut
modifiedAuthInfo.Password = o.password.Value()
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 setToken || setBasic {
@ -195,13 +242,28 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut
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()
if len(args) != 1 {
cmd.Help()
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]
return true
}

View 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)
}
}
}