exec credential provider: ProvideClusterInfo and kubeconfig shadow

- The main idea here is that we want to 1) prevent potentially large CA
  bundles from being set in an exec plugin's environment and 2) ensure
  that the exec plugin is getting everything it needs in order to talk to
  a cluster.
- Avoid breaking existing manual declarations of rest.Config instances by
  moving exec Cluster to kubeconfig internal type.
- Use client.authentication.k8s.io/exec to qualify exec cluster extension.
- Deep copy the exec Cluster.Config when we copy a rest.Config.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>

Kubernetes-commit: c4299d15d5289768808034676858e76a177eeae5
This commit is contained in:
Andrew Keesler
2020-10-29 13:38:42 -04:00
committed by Kubernetes Publisher
parent eb15c10113
commit a7ba87c612
22 changed files with 822 additions and 174 deletions

View File

@@ -87,10 +87,10 @@ func newCache() *cache {
var spewConfig = &spew.ConfigState{DisableMethods: true, Indent: " "}
func cacheKey(conf *api.ExecConfig, cluster clientauthentication.Cluster) string {
func cacheKey(conf *api.ExecConfig, cluster *clientauthentication.Cluster) string {
key := struct {
conf *api.ExecConfig
cluster clientauthentication.Cluster
cluster *clientauthentication.Cluster
}{
conf: conf,
cluster: cluster,
@@ -162,11 +162,11 @@ func (s *sometimes) Do(f func()) {
}
// GetAuthenticator returns an exec-based plugin for providing client credentials.
func GetAuthenticator(config *api.ExecConfig, cluster clientauthentication.Cluster) (*Authenticator, error) {
func GetAuthenticator(config *api.ExecConfig, cluster *clientauthentication.Cluster) (*Authenticator, error) {
return newAuthenticator(globalCache, config, cluster)
}
func newAuthenticator(c *cache, config *api.ExecConfig, cluster clientauthentication.Cluster) (*Authenticator, error) {
func newAuthenticator(c *cache, config *api.ExecConfig, cluster *clientauthentication.Cluster) (*Authenticator, error) {
key := cacheKey(config, cluster)
if a, ok := c.get(key); ok {
return a, nil
@@ -178,10 +178,11 @@ func newAuthenticator(c *cache, config *api.ExecConfig, cluster clientauthentica
}
a := &Authenticator{
cmd: config.Command,
args: config.Args,
group: gv,
cluster: cluster,
cmd: config.Command,
args: config.Args,
group: gv,
cluster: cluster,
provideClusterInfo: config.ProvideClusterInfo,
installHint: config.InstallHint,
sometimes: &sometimes{
@@ -208,11 +209,12 @@ func newAuthenticator(c *cache, config *api.ExecConfig, cluster clientauthentica
// The plugin input and output are defined by the API group client.authentication.k8s.io.
type Authenticator struct {
// Set by the config
cmd string
args []string
group schema.GroupVersion
env []string
cluster clientauthentication.Cluster
cmd string
args []string
group schema.GroupVersion
env []string
cluster *clientauthentication.Cluster
provideClusterInfo bool
// Used to avoid log spew by rate limiting install hint printing. We didn't do
// this by interval based rate limiting alone since that way may have prevented
@@ -374,9 +376,11 @@ func (a *Authenticator) refreshCredsLocked(r *clientauthentication.Response) err
Spec: clientauthentication.ExecCredentialSpec{
Response: r,
Interactive: a.interactive,
Cluster: a.cluster,
},
}
if a.provideClusterInfo {
cred.Spec.Cluster = a.cluster
}
env := append(a.environ(), a.env...)
data, err := runtime.Encode(codecs.LegacyCodec(a.group), cred)

View File

@@ -115,12 +115,13 @@ func TestCacheKey(t *testing.T) {
{Name: "5", Value: "6"},
{Name: "7", Value: "8"},
},
APIVersion: "client.authentication.k8s.io/v1alpha1",
APIVersion: "client.authentication.k8s.io/v1alpha1",
ProvideClusterInfo: true,
}
c1c := clientauthentication.Cluster{
Server: "foo",
ServerName: "bar",
CAData: []byte("baz"),
c1c := &clientauthentication.Cluster{
Server: "foo",
TLSServerName: "bar",
CertificateAuthorityData: []byte("baz"),
Config: &runtime.Unknown{
TypeMeta: runtime.TypeMeta{
APIVersion: "",
@@ -140,12 +141,13 @@ func TestCacheKey(t *testing.T) {
{Name: "5", Value: "6"},
{Name: "7", Value: "8"},
},
APIVersion: "client.authentication.k8s.io/v1alpha1",
APIVersion: "client.authentication.k8s.io/v1alpha1",
ProvideClusterInfo: true,
}
c2c := clientauthentication.Cluster{
Server: "foo",
ServerName: "bar",
CAData: []byte("baz"),
c2c := &clientauthentication.Cluster{
Server: "foo",
TLSServerName: "bar",
CertificateAuthorityData: []byte("baz"),
Config: &runtime.Unknown{
TypeMeta: runtime.TypeMeta{
APIVersion: "",
@@ -166,10 +168,10 @@ func TestCacheKey(t *testing.T) {
},
APIVersion: "client.authentication.k8s.io/v1alpha1",
}
c3c := clientauthentication.Cluster{
Server: "foo",
ServerName: "bar",
CAData: []byte("baz"),
c3c := &clientauthentication.Cluster{
Server: "foo",
TLSServerName: "bar",
CertificateAuthorityData: []byte("baz"),
Config: &runtime.Unknown{
TypeMeta: runtime.TypeMeta{
APIVersion: "",
@@ -190,10 +192,10 @@ func TestCacheKey(t *testing.T) {
},
APIVersion: "client.authentication.k8s.io/v1alpha1",
}
c4c := clientauthentication.Cluster{
Server: "foo",
ServerName: "bar",
CAData: []byte("baz"),
c4c := &clientauthentication.Cluster{
Server: "foo",
TLSServerName: "bar",
CertificateAuthorityData: []byte("baz"),
Config: &runtime.Unknown{
TypeMeta: runtime.TypeMeta{
APIVersion: "",
@@ -205,10 +207,49 @@ func TestCacheKey(t *testing.T) {
},
}
// c5/c5c should be the same as c4/c4c, except c5 has ProvideClusterInfo set to true.
c5 := &api.ExecConfig{
Command: "foo-bar",
Args: []string{"1", "2"},
Env: []api.ExecEnvVar{
{Name: "3", Value: "4"},
{Name: "5", Value: "6"},
},
APIVersion: "client.authentication.k8s.io/v1alpha1",
ProvideClusterInfo: true,
}
c5c := &clientauthentication.Cluster{
Server: "foo",
TLSServerName: "bar",
CertificateAuthorityData: []byte("baz"),
Config: &runtime.Unknown{
TypeMeta: runtime.TypeMeta{
APIVersion: "",
Kind: "",
},
Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"panda"}}`),
ContentEncoding: "",
ContentType: "application/json",
},
}
// c6 should be the same as c4, except c6 is passed with a nil cluster
c6 := &api.ExecConfig{
Command: "foo-bar",
Args: []string{"1", "2"},
Env: []api.ExecEnvVar{
{Name: "3", Value: "4"},
{Name: "5", Value: "6"},
},
APIVersion: "client.authentication.k8s.io/v1alpha1",
}
key1 := cacheKey(c1, c1c)
key2 := cacheKey(c2, c2c)
key3 := cacheKey(c3, c3c)
key4 := cacheKey(c4, c4c)
key5 := cacheKey(c5, c5c)
key6 := cacheKey(c6, nil)
if key1 != key2 {
t.Error("key1 and key2 didn't match")
}
@@ -221,6 +262,12 @@ func TestCacheKey(t *testing.T) {
if key3 == key4 {
t.Error("key3 and key4 matched")
}
if key4 == key5 {
t.Error("key3 and key4 matched")
}
if key6 == key4 {
t.Error("key6 and key4 matched")
}
}
func compJSON(t *testing.T, got, want []byte) {
@@ -246,7 +293,7 @@ func TestRefreshCreds(t *testing.T) {
name string
config api.ExecConfig
exitCode int
cluster clientauthentication.Cluster
cluster *clientauthentication.Cluster
output string
interactive bool
response *clientauthentication.Response
@@ -470,12 +517,7 @@ func TestRefreshCreds(t *testing.T) {
wantInput: `{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"spec": {
"cluster": {
"server": "",
"config": null
}
}
"spec": {}
}`,
output: `{
"kind": "ExecCredential",
@@ -494,12 +536,7 @@ func TestRefreshCreds(t *testing.T) {
wantInput: `{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"spec": {
"cluster": {
"server": "",
"config": null
}
}
"spec": {}
}`,
output: `{
"kind": "ExecCredential",
@@ -572,10 +609,10 @@ func TestRefreshCreds(t *testing.T) {
config: api.ExecConfig{
APIVersion: "client.authentication.k8s.io/v1alpha1",
},
cluster: clientauthentication.Cluster{
Server: "foo",
ServerName: "bar",
CAData: []byte("baz"),
cluster: &clientauthentication.Cluster{
Server: "foo",
TLSServerName: "bar",
CertificateAuthorityData: []byte("baz"),
Config: &runtime.Unknown{
TypeMeta: runtime.TypeMeta{
APIVersion: "",
@@ -616,14 +653,15 @@ func TestRefreshCreds(t *testing.T) {
wantCreds: credentials{token: "foo-bar"},
},
{
name: "beta-with-cluster-is-serialized",
name: "beta-with-cluster-and-provide-cluster-info-is-serialized",
config: api.ExecConfig{
APIVersion: "client.authentication.k8s.io/v1beta1",
APIVersion: "client.authentication.k8s.io/v1beta1",
ProvideClusterInfo: true,
},
cluster: clientauthentication.Cluster{
Server: "foo",
ServerName: "bar",
CAData: []byte("baz"),
cluster: &clientauthentication.Cluster{
Server: "foo",
TLSServerName: "bar",
CertificateAuthorityData: []byte("baz"),
Config: &runtime.Unknown{
TypeMeta: runtime.TypeMeta{
APIVersion: "",
@@ -646,8 +684,8 @@ func TestRefreshCreds(t *testing.T) {
"spec": {
"cluster": {
"server": "foo",
"serverName": "bar",
"caData": "YmF6",
"tls-server-name": "bar",
"certificate-authority-data": "YmF6",
"config": {
"apiVersion": "group/v1",
"kind": "PluginConfig",
@@ -667,6 +705,45 @@ func TestRefreshCreds(t *testing.T) {
}`,
wantCreds: credentials{token: "foo-bar"},
},
{
name: "beta-with-cluster-and-without-provide-cluster-info-is-not-serialized",
config: api.ExecConfig{
APIVersion: "client.authentication.k8s.io/v1beta1",
},
cluster: &clientauthentication.Cluster{
Server: "foo",
TLSServerName: "bar",
CertificateAuthorityData: []byte("baz"),
Config: &runtime.Unknown{
TypeMeta: runtime.TypeMeta{
APIVersion: "",
Kind: "",
},
Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"snorlax"}}`),
ContentEncoding: "",
ContentType: "application/json",
},
},
response: &clientauthentication.Response{
Header: map[string][]string{
"WWW-Authenticate": {`Basic realm="Access to the staging site", charset="UTF-8"`},
},
Code: 401,
},
wantInput: `{
"kind":"ExecCredential",
"apiVersion":"client.authentication.k8s.io/v1beta1",
"spec": {}
}`,
output: `{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"status": {
"token": "foo-bar"
}
}`,
wantCreds: credentials{token: "foo-bar"},
},
}
for _, test := range tests {
@@ -763,7 +840,7 @@ func TestRoundTripper(t *testing.T) {
Command: "./testdata/test-plugin.sh",
APIVersion: "client.authentication.k8s.io/v1alpha1",
}
a, err := newAuthenticator(newCache(), &c, clientauthentication.Cluster{})
a, err := newAuthenticator(newCache(), &c, nil)
if err != nil {
t.Fatal(err)
}
@@ -849,7 +926,7 @@ func TestTokenPresentCancelsExecAction(t *testing.T) {
a, err := newAuthenticator(newCache(), &api.ExecConfig{
Command: "./testdata/test-plugin.sh",
APIVersion: "client.authentication.k8s.io/v1alpha1",
})
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -888,7 +965,7 @@ func TestTLSCredentials(t *testing.T) {
a, err := newAuthenticator(newCache(), &api.ExecConfig{
Command: "./testdata/test-plugin.sh",
APIVersion: "client.authentication.k8s.io/v1alpha1",
}, clientauthentication.Cluster{})
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -978,7 +1055,7 @@ func TestConcurrentUpdateTransportConfig(t *testing.T) {
Command: "./testdata/test-plugin.sh",
APIVersion: "client.authentication.k8s.io/v1alpha1",
}
a, err := newAuthenticator(newCache(), &c, clientauthentication.Cluster{})
a, err := newAuthenticator(newCache(), &c, nil)
if err != nil {
t.Fatal(err)
}
@@ -1045,7 +1122,7 @@ func TestInstallHintRateLimit(t *testing.T) {
APIVersion: "client.authentication.k8s.io/v1alpha1",
InstallHint: "some install hint",
}
a, err := newAuthenticator(newCache(), &c)
a, err := newAuthenticator(newCache(), &c, nil)
if err != nil {
t.Fatal(err)
}