Skip to content

Commit

Permalink
proposal: external kubectl auth providers
Browse files Browse the repository at this point in the history
  • Loading branch information
ericchiang committed Dec 14, 2017
1 parent fdb9fbd commit 76938f5
Showing 1 changed file with 193 additions and 0 deletions.
193 changes: 193 additions & 0 deletions contributors/design-proposals/auth/kubectl-exec-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Out-of-tree client authentication providers

Author: @ericchiang

# Objective

This document describes a credential rotation strategy for kubectl and client-go
using an exec-based plugin mechanism.

# Background

Kubernetes clients can provide three kind of credentials: bearer tokens, TLS
client certs, and basic auth username and password. Kubeconfigs can either
in-line the credential, load credentials from a file, or can use an `AuthProvider`
to actively fetch and rotate credentials. `AuthProviders` are compiled into kubectl
and target specific providers (GCP, Keystone, Azure AD) or implement a
specification supported but a subset of vendors (OpenID Connect).

External tools that do rotation generally have to be called on a regular bases in
case the current credentials have expired, which leads to wrapping `kubectl` with
another tool or aliasing. For example, this is a [real example](
https://github.com/heptio/authenticator#4-set-up-kubectl-to-use-heptio-authenticator-for-aws-tokens)
from Heptio's AWS authenticator:

```
kubectl --kubeconfig /path/to/kubeconfig --token "$(heptio-authenticator-aws token -i CLUSTER_ID -r ROLE_ARN)" [...]
```

Beside resulting in a long command, this potentially encourages distributions to
wrap or fork kubectl, changing the way that users interact with different
Kubernetes clusters.

# Proposal

This proposal builds off of earlier requests to [support exec-based plugins](
https://github.com/kubernetes/kubernetes/issues/35530#issuecomment-256170024), and
proposes that we should add this as a first-class feature of kubectl. Specifically,
kubectl (and client-go) should be able to receive credentials by executing a
command and reading that command's stdout.

In fact, kubectl already does this today. The GCP plugin can already be configured
to [call a command](
https://github.com/kubernetes/client-go/blob/kubernetes-1.8.5/plugin/pkg/client/auth/gcp/gcp.go#L228-L240)
other than `gcloud`.

## Kubeconfig changes

The current `AuthProviderConfig` uses `map[string]string` for configuration, which
makes it hard to express things like a list of arguments or list key/value environment
variables. As such, `AuthProviderConfig` should add another field which expresses the
`exec` config. This has the benefit of a more natural structure, but the trade-off of
not being compatible with the existing `kubectl config set-credentials` implementation.

```go
const (
AuthProviderExecKindBearerToken = "bearer-token"
AuthProviderExecKindClientCert = "tls-certificate"
)

type AuthProviderConfig struct {
Name string `json:"name"`
Config map[string]string `json:"config"`

// Exec should only be provided if Name and Config aren't.
Exec ExecAuthProviderConfig `json:"exec"`
}

type ExecAuthProviderConfig struct {
Command string `json:"command"`
Args []string `json:"args"`
// Env defines additional environment variables to expose to the process. These
// are unioned with the host's environment, with variable values lists here having
// precedence.
Env []ExecEnvVar `json:"env"`
// Kind determines the kind of credentials the command will output to stdout.
// This can be "bearer-token" or "tls-certificate".
Kind string `json:"kind"`
}

type ExecEnvVar struct {
Name string `json:"name"`
Value string `json:"value"`

// TODO: Load env vars from files or from other envs?
}
```

This would allow a user block of a kubeconfig to declare the following:

```yaml
users:
- name: mmosley
user:
auth-provider:
exec:
kind: bearer-token
command: /bin/kubectl-login
args: ["hello", "world"]
env:
- name: "VERSION"
value: "v1.0.1"
```
The AWS authenticator above example becomes:
```
users:
- name: kubernetes-admin
user:
auth-provider:
exec:
kind: bearer-token
command: heptio-authenticator-aws
# CLUSTER_ID and ROLE_ARN should be replaced with actual desired values.
args: ["token", "-i", "(CLUSTER_ID)", "-r", "(ROLE_ARN)"]
```
## TLS client certificate support
The current The auth provider interface doesn't let the user modify the dialer,
only wrap the transport.
```
type AuthProvider interface {
// WrapTransport allows the plugin to create a modified RoundTripper that
// attaches authorization headers (or other info) to requests.
WrapTransport(http.RoundTripper) http.RoundTripper
// Login allows the plugin to initialize its configuration. It must not
// require direct user interaction.
Login() error
}
```

Since this doesn't let a `AuthProvider` supply things like client certificates,
the signature of the `AuthProvider` should change too ([with corresponding changes
to `k8s.io/client-go-transport`](
https://gist.github.com/ericchiang/7f5804403b359ebdf79dcf76c4071bff)):

```
import (
"k8s.io/client-go/transport"
// ...
)
type AuthProvider interface {
// UpdateTransportConfig updates a config by adding a transport wrapper,
// setting a bearer token (should ignore if one is already set), or adding
// TLS client certificate credentials.
UpdateTransportConfig(c *transport.Config)
// Login() dropped, it was never used.
}
```

This would let auth transports supply TLS credentials, as well as instrument
transports with in-memory rotation code like the utilities implemented by
[`k8s.io/client-go/util/certificate`](https://godoc.org/k8s.io/client-go/util/certificate).

If the exec command specifies `kind: tls-certificate`, the command is expected to
output a unencrypted PEM encoded private key, followed by the PEM encoded client
certificate chain.

```
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA6IVXGPX5yP2Q6TAlQXIQsavzSqZ973iZvpQBGTI6M98gTSVm
# ...
TUwpZmylTKEJ9zLt2PADglyDrQ2D+1WNzh966Oo9c+kZt4WJM0aF
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIJAKK9m2Cfg5uhMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
# ...
xar2YeJ6mCCzSAPM69DP
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIJAPqJyUfmRxGLMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
# ...
zgmWLORg+ls1H1oaJiNW
-----END CERTIFICATE-----
```

The `AuthProvider` then adds those credentials to the `transport.Config`.

## Login

Historically, `AuthProviders` have had a `Login()` method with the hope that it
could trigger bootstrapping into the cluster. While no providers implement this
method, the Azure `AuthProvider` can already prompt an [interactive auth flow](
https://github.com/kubernetes/client-go/blob/kubernetes-1.8.5/plugin/pkg/client/auth/azure/azure.go#L343).
This suggests that an exec'd tool should be able to trigger its own custom logins,
either by opening a browser, or performing a text based prompt.

We should take care that interactive stderr and stdin are correctly
inherited by the sub-process to enable this kind of interaction.

0 comments on commit 76938f5

Please sign in to comment.