Skip to content

Commit

Permalink
Merge pull request kubernetes#41653 from jlowdermilk/gcp-auth-plugin
Browse files Browse the repository at this point in the history
Automatic merge from submit-queue (batch tested with PRs 42080, 41653, 42598, 42555)

Support whitespace in command path for gcp auth plugin

```
External command option on gcp client auth plugin supports whitespace in command path.
```

Splitting on whitespace to get cmd+args breaks when the path the executable contains spaces. Resolve by adding a new "cmd-args" field to config to allow the full string of "cmd-path" to be interpreted as path to executable.

This change is backwards compatible with existing behavior.
  • Loading branch information
Kubernetes Submit Queue authored Mar 7, 2017
2 parents d50a59e + 995ecfe commit 73c5d6c
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 72 deletions.
52 changes: 32 additions & 20 deletions staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ func init() {
}
}

// Stubbable for testing
var execCommand = exec.Command

// gcpAuthProvider is an auth provider plugin that uses GCP credentials to provide
// tokens for kubectl to authenticate itself to the apiserver. A sample json config
// is provided below with all recognized options described.
Expand All @@ -62,10 +65,13 @@ func init() {
// # These options direct the plugin to execute a specified command and parse
// # token and expiry time from the output of the command.
//
// # Command to execute for access token. String is split on whitespace
// # with first field treated as the executable, remaining fields as args.
// # Command output will be parsed as JSON.
// "cmd-path": "/usr/bin/gcloud config config-helper --output=json",
// # Command to execute for access token. Command output will be parsed as JSON.
// # If "cmd-args" is not present, this value will be split on whitespace, with
// # the first element interpreted as the command, remaining elements as args.
// "cmd-path": "/usr/bin/gcloud",
//
// # Arguments to pass to command to execute for access token.
// "cmd-args": "config config-helper --output=json"
//
// # JSONPath to the string field that represents the access token in
// # command output. If omitted, defaults to "{.access_token}".
Expand All @@ -89,11 +95,21 @@ type gcpAuthProvider struct {
}

func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
cmd, useCmd := gcpConfig["cmd-path"]
var ts oauth2.TokenSource
var err error
if useCmd {
ts, err = newCmdTokenSource(cmd, gcpConfig["token-key"], gcpConfig["expiry-key"], gcpConfig["time-fmt"])
if cmd, useCmd := gcpConfig["cmd-path"]; useCmd {
if len(cmd) == 0 {
return nil, fmt.Errorf("missing access token cmd")
}
var args []string
if cmdArgs, ok := gcpConfig["cmd-args"]; ok {
args = strings.Fields(cmdArgs)
} else {
fields := strings.Fields(cmd)
cmd = fields[0]
args = fields[1:]
}
ts = newCmdTokenSource(cmd, args, gcpConfig["token-key"], gcpConfig["expiry-key"], gcpConfig["time-fmt"])
} else {
ts, err = google.DefaultTokenSource(context.Background(), "https://www.googleapis.com/auth/cloud-platform")
}
Expand Down Expand Up @@ -192,7 +208,7 @@ type commandTokenSource struct {
timeFmt string
}

func newCmdTokenSource(cmd, tokenKey, expiryKey, timeFmt string) (*commandTokenSource, error) {
func newCmdTokenSource(cmd string, args []string, tokenKey, expiryKey, timeFmt string) *commandTokenSource {
if len(timeFmt) == 0 {
timeFmt = time.RFC3339Nano
}
Expand All @@ -202,25 +218,21 @@ func newCmdTokenSource(cmd, tokenKey, expiryKey, timeFmt string) (*commandTokenS
if len(expiryKey) == 0 {
expiryKey = "{.token_expiry}"
}
fields := strings.Fields(cmd)
if len(fields) == 0 {
return nil, fmt.Errorf("missing access token cmd")
}
return &commandTokenSource{
cmd: fields[0],
args: fields[1:],
cmd: cmd,
args: args,
tokenKey: tokenKey,
expiryKey: expiryKey,
timeFmt: timeFmt,
}, nil
}
}

func (c *commandTokenSource) Token() (*oauth2.Token, error) {
fullCmd := fmt.Sprintf("%s %s", c.cmd, strings.Join(c.args, " "))
cmd := exec.Command(c.cmd, c.args...)
fullCmd := strings.Join(append([]string{c.cmd}, c.args...), " ")
cmd := execCommand(c.cmd, c.args...)
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("error executing access token command %q: %v", fullCmd, err)
return nil, fmt.Errorf("error executing access token command %q: err=%v output=%s", fullCmd, err, output)
}
token, err := c.parseTokenCmdOutput(output)
if err != nil {
Expand All @@ -241,11 +253,11 @@ func (c *commandTokenSource) parseTokenCmdOutput(output []byte) (*oauth2.Token,

accessToken, err := parseJSONPath(data, "token-key", c.tokenKey)
if err != nil {
return nil, fmt.Errorf("error parsing token-key %q: %v", c.tokenKey, err)
return nil, fmt.Errorf("error parsing token-key %q from %q: %v", c.tokenKey, string(output), err)
}
expiryStr, err := parseJSONPath(data, "expiry-key", c.expiryKey)
if err != nil {
return nil, fmt.Errorf("error parsing expiry-key %q: %v", c.expiryKey, err)
return nil, fmt.Errorf("error parsing expiry-key %q from %q: %v", c.expiryKey, string(output), err)
}
var expiry time.Time
if t, err := time.Parse(c.timeFmt, expiryStr); err != nil {
Expand Down
Loading

0 comments on commit 73c5d6c

Please sign in to comment.