mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Merge pull request #95489 from ankeesler/ankeesler/enj/f/exec_plugin_cluster
exec credential provider: wire in cluster info (superset of #91192)
This commit is contained in:
		@@ -468,6 +468,10 @@ API rule violation: names_match,k8s.io/apimachinery/pkg/runtime,Unknown,Raw
 | 
			
		||||
API rule violation: names_match,k8s.io/apimachinery/pkg/util/intstr,IntOrString,IntVal
 | 
			
		||||
API rule violation: names_match,k8s.io/apimachinery/pkg/util/intstr,IntOrString,StrVal
 | 
			
		||||
API rule violation: names_match,k8s.io/apimachinery/pkg/util/intstr,IntOrString,Type
 | 
			
		||||
API rule violation: names_match,k8s.io/client-go/pkg/apis/clientauthentication/v1beta1,Cluster,CertificateAuthorityData
 | 
			
		||||
API rule violation: names_match,k8s.io/client-go/pkg/apis/clientauthentication/v1beta1,Cluster,InsecureSkipTLSVerify
 | 
			
		||||
API rule violation: names_match,k8s.io/client-go/pkg/apis/clientauthentication/v1beta1,Cluster,ProxyURL
 | 
			
		||||
API rule violation: names_match,k8s.io/client-go/pkg/apis/clientauthentication/v1beta1,Cluster,TLSServerName
 | 
			
		||||
API rule violation: names_match,k8s.io/cloud-provider/app/apis/config/v1alpha1,CloudControllerManagerConfiguration,Generic
 | 
			
		||||
API rule violation: names_match,k8s.io/cloud-provider/app/apis/config/v1alpha1,CloudControllerManagerConfiguration,KubeCloudShared
 | 
			
		||||
API rule violation: names_match,k8s.io/cloud-provider/app/apis/config/v1alpha1,CloudControllerManagerConfiguration,NodeStatusUpdateFrequency
 | 
			
		||||
 
 | 
			
		||||
@@ -18,11 +18,12 @@ package clientauthentication
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
			
		||||
 | 
			
		||||
// ExecCredentials is used by exec-based plugins to communicate credentials to
 | 
			
		||||
// ExecCredential is used by exec-based plugins to communicate credentials to
 | 
			
		||||
// HTTP transports.
 | 
			
		||||
type ExecCredential struct {
 | 
			
		||||
	metav1.TypeMeta
 | 
			
		||||
@@ -37,7 +38,7 @@ type ExecCredential struct {
 | 
			
		||||
	Status *ExecCredentialStatus
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecCredenitalSpec holds request and runtime specific information provided by
 | 
			
		||||
// ExecCredentialSpec holds request and runtime specific information provided by
 | 
			
		||||
// the transport.
 | 
			
		||||
type ExecCredentialSpec struct {
 | 
			
		||||
	// Response is populated when the transport encounters HTTP status codes, such as 401,
 | 
			
		||||
@@ -49,6 +50,13 @@ type ExecCredentialSpec struct {
 | 
			
		||||
	// interactive prompt.
 | 
			
		||||
	// +optional
 | 
			
		||||
	Interactive bool
 | 
			
		||||
 | 
			
		||||
	// Cluster contains information to allow an exec plugin to communicate with the
 | 
			
		||||
	// kubernetes cluster being authenticated to. Note that Cluster is non-nil only
 | 
			
		||||
	// when provideClusterInfo is set to true in the exec provider config (i.e.,
 | 
			
		||||
	// ExecConfig.ProvideClusterInfo).
 | 
			
		||||
	// +optional
 | 
			
		||||
	Cluster *Cluster
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecCredentialStatus holds credentials for the transport to use.
 | 
			
		||||
@@ -75,3 +83,56 @@ type Response struct {
 | 
			
		||||
	// Code is the HTTP status code returned by the server.
 | 
			
		||||
	Code int32
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cluster contains information to allow an exec plugin to communicate
 | 
			
		||||
// with the kubernetes cluster being authenticated to.
 | 
			
		||||
//
 | 
			
		||||
// To ensure that this struct contains everything someone would need to communicate
 | 
			
		||||
// with a kubernetes cluster (just like they would via a kubeconfig), the fields
 | 
			
		||||
// should shadow "k8s.io/client-go/tools/clientcmd/api/v1".Cluster, with the exception
 | 
			
		||||
// of CertificateAuthority, since CA data will always be passed to the plugin as bytes.
 | 
			
		||||
type Cluster struct {
 | 
			
		||||
	// Server is the address of the kubernetes cluster (https://hostname:port).
 | 
			
		||||
	Server string
 | 
			
		||||
	// TLSServerName is passed to the server for SNI and is used in the client to
 | 
			
		||||
	// check server certificates against. If ServerName is empty, the hostname
 | 
			
		||||
	// used to contact the server is used.
 | 
			
		||||
	// +optional
 | 
			
		||||
	TLSServerName string
 | 
			
		||||
	// InsecureSkipTLSVerify skips the validity check for the server's certificate.
 | 
			
		||||
	// This will make your HTTPS connections insecure.
 | 
			
		||||
	// +optional
 | 
			
		||||
	InsecureSkipTLSVerify bool
 | 
			
		||||
	// CAData contains PEM-encoded certificate authority certificates.
 | 
			
		||||
	// If empty, system roots should be used.
 | 
			
		||||
	// +listType=atomic
 | 
			
		||||
	// +optional
 | 
			
		||||
	CertificateAuthorityData []byte
 | 
			
		||||
	// ProxyURL is the URL to the proxy to be used for all requests to this
 | 
			
		||||
	// cluster.
 | 
			
		||||
	// +optional
 | 
			
		||||
	ProxyURL string
 | 
			
		||||
	// Config holds additional config data that is specific to the exec
 | 
			
		||||
	// plugin with regards to the cluster being authenticated to.
 | 
			
		||||
	//
 | 
			
		||||
	// This data is sourced from the clientcmd Cluster object's
 | 
			
		||||
	// extensions[client.authentication.k8s.io/exec] field:
 | 
			
		||||
	//
 | 
			
		||||
	// clusters:
 | 
			
		||||
	// - name: my-cluster
 | 
			
		||||
	//   cluster:
 | 
			
		||||
	//     ...
 | 
			
		||||
	//     extensions:
 | 
			
		||||
	//     - name: client.authentication.k8s.io/exec  # reserved extension name for per cluster exec config
 | 
			
		||||
	//       extension:
 | 
			
		||||
	//         audience: 06e3fbd18de8  # arbitrary config
 | 
			
		||||
	//
 | 
			
		||||
	// In some environments, the user config may be exactly the same across many clusters
 | 
			
		||||
	// (i.e. call this exec plugin) minus some details that are specific to each cluster
 | 
			
		||||
	// such as the audience.  This field allows the per cluster config to be directly
 | 
			
		||||
	// specified with the cluster info.  Using this field to store secret data is not
 | 
			
		||||
	// recommended as one of the prime benefits of exec plugins is that no secrets need
 | 
			
		||||
	// to be stored directly in the kubeconfig.
 | 
			
		||||
	// +optional
 | 
			
		||||
	Config runtime.Object
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ load(
 | 
			
		||||
go_library(
 | 
			
		||||
    name = "go_default_library",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "conversion.go",
 | 
			
		||||
        "doc.go",
 | 
			
		||||
        "register.go",
 | 
			
		||||
        "types.go",
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2020 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 v1alpha1
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/apimachinery/pkg/conversion"
 | 
			
		||||
	"k8s.io/client-go/pkg/apis/clientauthentication"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Convert_clientauthentication_ExecCredentialSpec_To_v1alpha1_ExecCredentialSpec(in *clientauthentication.ExecCredentialSpec, out *ExecCredentialSpec, s conversion.Scope) error {
 | 
			
		||||
	// This conversion intentionally omits the Cluster field which is only supported in newer versions.
 | 
			
		||||
	return autoConvert_clientauthentication_ExecCredentialSpec_To_v1alpha1_ExecCredentialSpec(in, out, s)
 | 
			
		||||
}
 | 
			
		||||
@@ -37,7 +37,7 @@ type ExecCredential struct {
 | 
			
		||||
	Status *ExecCredentialStatus `json:"status,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecCredenitalSpec holds request and runtime specific information provided by
 | 
			
		||||
// ExecCredentialSpec holds request and runtime specific information provided by
 | 
			
		||||
// the transport.
 | 
			
		||||
type ExecCredentialSpec struct {
 | 
			
		||||
	// Response is populated when the transport encounters HTTP status codes, such as 401,
 | 
			
		||||
 
 | 
			
		||||
@@ -51,11 +51,6 @@ func RegisterConversions(s *runtime.Scheme) error {
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := s.AddGeneratedConversionFunc((*clientauthentication.ExecCredentialSpec)(nil), (*ExecCredentialSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
			
		||||
		return Convert_clientauthentication_ExecCredentialSpec_To_v1alpha1_ExecCredentialSpec(a.(*clientauthentication.ExecCredentialSpec), b.(*ExecCredentialSpec), scope)
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := s.AddGeneratedConversionFunc((*ExecCredentialStatus)(nil), (*clientauthentication.ExecCredentialStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
			
		||||
		return Convert_v1alpha1_ExecCredentialStatus_To_clientauthentication_ExecCredentialStatus(a.(*ExecCredentialStatus), b.(*clientauthentication.ExecCredentialStatus), scope)
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
@@ -76,6 +71,11 @@ func RegisterConversions(s *runtime.Scheme) error {
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := s.AddConversionFunc((*clientauthentication.ExecCredentialSpec)(nil), (*ExecCredentialSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
			
		||||
		return Convert_clientauthentication_ExecCredentialSpec_To_v1alpha1_ExecCredentialSpec(a.(*clientauthentication.ExecCredentialSpec), b.(*ExecCredentialSpec), scope)
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -119,14 +119,10 @@ func Convert_v1alpha1_ExecCredentialSpec_To_clientauthentication_ExecCredentialS
 | 
			
		||||
func autoConvert_clientauthentication_ExecCredentialSpec_To_v1alpha1_ExecCredentialSpec(in *clientauthentication.ExecCredentialSpec, out *ExecCredentialSpec, s conversion.Scope) error {
 | 
			
		||||
	out.Response = (*Response)(unsafe.Pointer(in.Response))
 | 
			
		||||
	out.Interactive = in.Interactive
 | 
			
		||||
	// WARNING: in.Cluster requires manual conversion: does not exist in peer-type
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Convert_clientauthentication_ExecCredentialSpec_To_v1alpha1_ExecCredentialSpec is an autogenerated conversion function.
 | 
			
		||||
func Convert_clientauthentication_ExecCredentialSpec_To_v1alpha1_ExecCredentialSpec(in *clientauthentication.ExecCredentialSpec, out *ExecCredentialSpec, s conversion.Scope) error {
 | 
			
		||||
	return autoConvert_clientauthentication_ExecCredentialSpec_To_v1alpha1_ExecCredentialSpec(in, out, s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func autoConvert_v1alpha1_ExecCredentialStatus_To_clientauthentication_ExecCredentialStatus(in *ExecCredentialStatus, out *clientauthentication.ExecCredentialStatus, s conversion.Scope) error {
 | 
			
		||||
	out.ExpirationTimestamp = (*v1.Time)(unsafe.Pointer(in.ExpirationTimestamp))
 | 
			
		||||
	out.Token = in.Token
 | 
			
		||||
 
 | 
			
		||||
@@ -17,10 +17,12 @@ limitations under the License.
 | 
			
		||||
package v1beta1
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	conversion "k8s.io/apimachinery/pkg/conversion"
 | 
			
		||||
	clientauthentication "k8s.io/client-go/pkg/apis/clientauthentication"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/conversion"
 | 
			
		||||
	"k8s.io/client-go/pkg/apis/clientauthentication"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Convert_clientauthentication_ExecCredentialSpec_To_v1beta1_ExecCredentialSpec(in *clientauthentication.ExecCredentialSpec, out *ExecCredentialSpec, s conversion.Scope) error {
 | 
			
		||||
	return nil
 | 
			
		||||
	// This conversion intentionally omits the Response and Interactive fields, which were only
 | 
			
		||||
	// supported in v1alpha1.
 | 
			
		||||
	return autoConvert_clientauthentication_ExecCredentialSpec_To_v1beta1_ExecCredentialSpec(in, out, s)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,17 +18,17 @@ package v1beta1
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
			
		||||
 | 
			
		||||
// ExecCredentials is used by exec-based plugins to communicate credentials to
 | 
			
		||||
// ExecCredential is used by exec-based plugins to communicate credentials to
 | 
			
		||||
// HTTP transports.
 | 
			
		||||
type ExecCredential struct {
 | 
			
		||||
	metav1.TypeMeta `json:",inline"`
 | 
			
		||||
 | 
			
		||||
	// Spec holds information passed to the plugin by the transport. This contains
 | 
			
		||||
	// request and runtime specific information, such as if the session is interactive.
 | 
			
		||||
	// Spec holds information passed to the plugin by the transport.
 | 
			
		||||
	Spec ExecCredentialSpec `json:"spec,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Status is filled in by the plugin and holds the credentials that the transport
 | 
			
		||||
@@ -37,9 +37,16 @@ type ExecCredential struct {
 | 
			
		||||
	Status *ExecCredentialStatus `json:"status,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecCredenitalSpec holds request and runtime specific information provided by
 | 
			
		||||
// ExecCredentialSpec holds request and runtime specific information provided by
 | 
			
		||||
// the transport.
 | 
			
		||||
type ExecCredentialSpec struct{}
 | 
			
		||||
type ExecCredentialSpec struct {
 | 
			
		||||
	// Cluster contains information to allow an exec plugin to communicate with the
 | 
			
		||||
	// kubernetes cluster being authenticated to. Note that Cluster is non-nil only
 | 
			
		||||
	// when provideClusterInfo is set to true in the exec provider config (i.e.,
 | 
			
		||||
	// ExecConfig.ProvideClusterInfo).
 | 
			
		||||
	// +optional
 | 
			
		||||
	Cluster *Cluster `json:"cluster,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecCredentialStatus holds credentials for the transport to use.
 | 
			
		||||
//
 | 
			
		||||
@@ -57,3 +64,56 @@ type ExecCredentialStatus struct {
 | 
			
		||||
	// PEM-encoded private key for the above certificate.
 | 
			
		||||
	ClientKeyData string `json:"clientKeyData,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cluster contains information to allow an exec plugin to communicate
 | 
			
		||||
// with the kubernetes cluster being authenticated to.
 | 
			
		||||
//
 | 
			
		||||
// To ensure that this struct contains everything someone would need to communicate
 | 
			
		||||
// with a kubernetes cluster (just like they would via a kubeconfig), the fields
 | 
			
		||||
// should shadow "k8s.io/client-go/tools/clientcmd/api/v1".Cluster, with the exception
 | 
			
		||||
// of CertificateAuthority, since CA data will always be passed to the plugin as bytes.
 | 
			
		||||
type Cluster struct {
 | 
			
		||||
	// Server is the address of the kubernetes cluster (https://hostname:port).
 | 
			
		||||
	Server string `json:"server"`
 | 
			
		||||
	// TLSServerName is passed to the server for SNI and is used in the client to
 | 
			
		||||
	// check server certificates against. If ServerName is empty, the hostname
 | 
			
		||||
	// used to contact the server is used.
 | 
			
		||||
	// +optional
 | 
			
		||||
	TLSServerName string `json:"tls-server-name,omitempty"`
 | 
			
		||||
	// InsecureSkipTLSVerify skips the validity check for the server's certificate.
 | 
			
		||||
	// This will make your HTTPS connections insecure.
 | 
			
		||||
	// +optional
 | 
			
		||||
	InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
 | 
			
		||||
	// CAData contains PEM-encoded certificate authority certificates.
 | 
			
		||||
	// If empty, system roots should be used.
 | 
			
		||||
	// +listType=atomic
 | 
			
		||||
	// +optional
 | 
			
		||||
	CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
 | 
			
		||||
	// ProxyURL is the URL to the proxy to be used for all requests to this
 | 
			
		||||
	// cluster.
 | 
			
		||||
	// +optional
 | 
			
		||||
	ProxyURL string `json:"proxy-url,omitempty"`
 | 
			
		||||
	// Config holds additional config data that is specific to the exec
 | 
			
		||||
	// plugin with regards to the cluster being authenticated to.
 | 
			
		||||
	//
 | 
			
		||||
	// This data is sourced from the clientcmd Cluster object's
 | 
			
		||||
	// extensions[client.authentication.k8s.io/exec] field:
 | 
			
		||||
	//
 | 
			
		||||
	// clusters:
 | 
			
		||||
	// - name: my-cluster
 | 
			
		||||
	//   cluster:
 | 
			
		||||
	//     ...
 | 
			
		||||
	//     extensions:
 | 
			
		||||
	//     - name: client.authentication.k8s.io/exec  # reserved extension name for per cluster exec config
 | 
			
		||||
	//       extension:
 | 
			
		||||
	//         audience: 06e3fbd18de8  # arbitrary config
 | 
			
		||||
	//
 | 
			
		||||
	// In some environments, the user config may be exactly the same across many clusters
 | 
			
		||||
	// (i.e. call this exec plugin) minus some details that are specific to each cluster
 | 
			
		||||
	// such as the audience.  This field allows the per cluster config to be directly
 | 
			
		||||
	// specified with the cluster info.  Using this field to store secret data is not
 | 
			
		||||
	// recommended as one of the prime benefits of exec plugins is that no secrets need
 | 
			
		||||
	// to be stored directly in the kubeconfig.
 | 
			
		||||
	// +optional
 | 
			
		||||
	Config runtime.RawExtension `json:"config,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,16 @@ func init() {
 | 
			
		||||
// RegisterConversions adds conversion functions to the given scheme.
 | 
			
		||||
// Public to allow building arbitrary schemes.
 | 
			
		||||
func RegisterConversions(s *runtime.Scheme) error {
 | 
			
		||||
	if err := s.AddGeneratedConversionFunc((*Cluster)(nil), (*clientauthentication.Cluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
			
		||||
		return Convert_v1beta1_Cluster_To_clientauthentication_Cluster(a.(*Cluster), b.(*clientauthentication.Cluster), scope)
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := s.AddGeneratedConversionFunc((*clientauthentication.Cluster)(nil), (*Cluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
			
		||||
		return Convert_clientauthentication_Cluster_To_v1beta1_Cluster(a.(*clientauthentication.Cluster), b.(*Cluster), scope)
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := s.AddGeneratedConversionFunc((*ExecCredential)(nil), (*clientauthentication.ExecCredential)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
			
		||||
		return Convert_v1beta1_ExecCredential_To_clientauthentication_ExecCredential(a.(*ExecCredential), b.(*clientauthentication.ExecCredential), scope)
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
@@ -69,6 +79,40 @@ func RegisterConversions(s *runtime.Scheme) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func autoConvert_v1beta1_Cluster_To_clientauthentication_Cluster(in *Cluster, out *clientauthentication.Cluster, s conversion.Scope) error {
 | 
			
		||||
	out.Server = in.Server
 | 
			
		||||
	out.TLSServerName = in.TLSServerName
 | 
			
		||||
	out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
 | 
			
		||||
	out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
 | 
			
		||||
	out.ProxyURL = in.ProxyURL
 | 
			
		||||
	if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Config, &out.Config, s); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Convert_v1beta1_Cluster_To_clientauthentication_Cluster is an autogenerated conversion function.
 | 
			
		||||
func Convert_v1beta1_Cluster_To_clientauthentication_Cluster(in *Cluster, out *clientauthentication.Cluster, s conversion.Scope) error {
 | 
			
		||||
	return autoConvert_v1beta1_Cluster_To_clientauthentication_Cluster(in, out, s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func autoConvert_clientauthentication_Cluster_To_v1beta1_Cluster(in *clientauthentication.Cluster, out *Cluster, s conversion.Scope) error {
 | 
			
		||||
	out.Server = in.Server
 | 
			
		||||
	out.TLSServerName = in.TLSServerName
 | 
			
		||||
	out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
 | 
			
		||||
	out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
 | 
			
		||||
	out.ProxyURL = in.ProxyURL
 | 
			
		||||
	if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Config, &out.Config, s); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Convert_clientauthentication_Cluster_To_v1beta1_Cluster is an autogenerated conversion function.
 | 
			
		||||
func Convert_clientauthentication_Cluster_To_v1beta1_Cluster(in *clientauthentication.Cluster, out *Cluster, s conversion.Scope) error {
 | 
			
		||||
	return autoConvert_clientauthentication_Cluster_To_v1beta1_Cluster(in, out, s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func autoConvert_v1beta1_ExecCredential_To_clientauthentication_ExecCredential(in *ExecCredential, out *clientauthentication.ExecCredential, s conversion.Scope) error {
 | 
			
		||||
	if err := Convert_v1beta1_ExecCredentialSpec_To_clientauthentication_ExecCredentialSpec(&in.Spec, &out.Spec, s); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -96,6 +140,15 @@ func Convert_clientauthentication_ExecCredential_To_v1beta1_ExecCredential(in *c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func autoConvert_v1beta1_ExecCredentialSpec_To_clientauthentication_ExecCredentialSpec(in *ExecCredentialSpec, out *clientauthentication.ExecCredentialSpec, s conversion.Scope) error {
 | 
			
		||||
	if in.Cluster != nil {
 | 
			
		||||
		in, out := &in.Cluster, &out.Cluster
 | 
			
		||||
		*out = new(clientauthentication.Cluster)
 | 
			
		||||
		if err := Convert_v1beta1_Cluster_To_clientauthentication_Cluster(*in, *out, s); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		out.Cluster = nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -107,6 +160,15 @@ func Convert_v1beta1_ExecCredentialSpec_To_clientauthentication_ExecCredentialSp
 | 
			
		||||
func autoConvert_clientauthentication_ExecCredentialSpec_To_v1beta1_ExecCredentialSpec(in *clientauthentication.ExecCredentialSpec, out *ExecCredentialSpec, s conversion.Scope) error {
 | 
			
		||||
	// WARNING: in.Response requires manual conversion: does not exist in peer-type
 | 
			
		||||
	// WARNING: in.Interactive requires manual conversion: does not exist in peer-type
 | 
			
		||||
	if in.Cluster != nil {
 | 
			
		||||
		in, out := &in.Cluster, &out.Cluster
 | 
			
		||||
		*out = new(Cluster)
 | 
			
		||||
		if err := Convert_clientauthentication_Cluster_To_v1beta1_Cluster(*in, *out, s); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		out.Cluster = nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,33 @@ import (
 | 
			
		||||
	runtime "k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
			
		||||
func (in *Cluster) DeepCopyInto(out *Cluster) {
 | 
			
		||||
	*out = *in
 | 
			
		||||
	if in.CertificateAuthorityData != nil {
 | 
			
		||||
		in, out := &in.CertificateAuthorityData, &out.CertificateAuthorityData
 | 
			
		||||
		*out = make([]byte, len(*in))
 | 
			
		||||
		copy(*out, *in)
 | 
			
		||||
	}
 | 
			
		||||
	in.Config.DeepCopyInto(&out.Config)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.
 | 
			
		||||
func (in *Cluster) DeepCopy() *Cluster {
 | 
			
		||||
	if in == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	out := new(Cluster)
 | 
			
		||||
	in.DeepCopyInto(out)
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
			
		||||
func (in *ExecCredential) DeepCopyInto(out *ExecCredential) {
 | 
			
		||||
	*out = *in
 | 
			
		||||
	out.TypeMeta = in.TypeMeta
 | 
			
		||||
	out.Spec = in.Spec
 | 
			
		||||
	in.Spec.DeepCopyInto(&out.Spec)
 | 
			
		||||
	if in.Status != nil {
 | 
			
		||||
		in, out := &in.Status, &out.Status
 | 
			
		||||
		*out = new(ExecCredentialStatus)
 | 
			
		||||
@@ -58,6 +80,11 @@ func (in *ExecCredential) DeepCopyObject() runtime.Object {
 | 
			
		||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
			
		||||
func (in *ExecCredentialSpec) DeepCopyInto(out *ExecCredentialSpec) {
 | 
			
		||||
	*out = *in
 | 
			
		||||
	if in.Cluster != nil {
 | 
			
		||||
		in, out := &in.Cluster, &out.Cluster
 | 
			
		||||
		*out = new(Cluster)
 | 
			
		||||
		(*in).DeepCopyInto(*out)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,30 @@ import (
 | 
			
		||||
	runtime "k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
			
		||||
func (in *Cluster) DeepCopyInto(out *Cluster) {
 | 
			
		||||
	*out = *in
 | 
			
		||||
	if in.CertificateAuthorityData != nil {
 | 
			
		||||
		in, out := &in.CertificateAuthorityData, &out.CertificateAuthorityData
 | 
			
		||||
		*out = make([]byte, len(*in))
 | 
			
		||||
		copy(*out, *in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Config != nil {
 | 
			
		||||
		out.Config = in.Config.DeepCopyObject()
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.
 | 
			
		||||
func (in *Cluster) DeepCopy() *Cluster {
 | 
			
		||||
	if in == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	out := new(Cluster)
 | 
			
		||||
	in.DeepCopyInto(out)
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
			
		||||
func (in *ExecCredential) DeepCopyInto(out *ExecCredential) {
 | 
			
		||||
	*out = *in
 | 
			
		||||
@@ -63,6 +87,11 @@ func (in *ExecCredentialSpec) DeepCopyInto(out *ExecCredentialSpec) {
 | 
			
		||||
		*out = new(Response)
 | 
			
		||||
		(*in).DeepCopyInto(*out)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Cluster != nil {
 | 
			
		||||
		in, out := &in.Cluster, &out.Cluster
 | 
			
		||||
		*out = new(Cluster)
 | 
			
		||||
		(*in).DeepCopyInto(*out)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -87,8 +87,15 @@ func newCache() *cache {
 | 
			
		||||
 | 
			
		||||
var spewConfig = &spew.ConfigState{DisableMethods: true, Indent: " "}
 | 
			
		||||
 | 
			
		||||
func cacheKey(c *api.ExecConfig) string {
 | 
			
		||||
	return spewConfig.Sprint(c)
 | 
			
		||||
func cacheKey(conf *api.ExecConfig, cluster *clientauthentication.Cluster) string {
 | 
			
		||||
	key := struct {
 | 
			
		||||
		conf    *api.ExecConfig
 | 
			
		||||
		cluster *clientauthentication.Cluster
 | 
			
		||||
	}{
 | 
			
		||||
		conf:    conf,
 | 
			
		||||
		cluster: cluster,
 | 
			
		||||
	}
 | 
			
		||||
	return spewConfig.Sprint(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type cache struct {
 | 
			
		||||
@@ -155,12 +162,12 @@ func (s *sometimes) Do(f func()) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAuthenticator returns an exec-based plugin for providing client credentials.
 | 
			
		||||
func GetAuthenticator(config *api.ExecConfig) (*Authenticator, error) {
 | 
			
		||||
	return newAuthenticator(globalCache, config)
 | 
			
		||||
func GetAuthenticator(config *api.ExecConfig, cluster *clientauthentication.Cluster) (*Authenticator, error) {
 | 
			
		||||
	return newAuthenticator(globalCache, config, cluster)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newAuthenticator(c *cache, config *api.ExecConfig) (*Authenticator, error) {
 | 
			
		||||
	key := cacheKey(config)
 | 
			
		||||
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
 | 
			
		||||
	}
 | 
			
		||||
@@ -174,6 +181,8 @@ func newAuthenticator(c *cache, config *api.ExecConfig) (*Authenticator, error)
 | 
			
		||||
		cmd:                config.Command,
 | 
			
		||||
		args:               config.Args,
 | 
			
		||||
		group:              gv,
 | 
			
		||||
		cluster:            cluster,
 | 
			
		||||
		provideClusterInfo: config.ProvideClusterInfo,
 | 
			
		||||
 | 
			
		||||
		installHint: config.InstallHint,
 | 
			
		||||
		sometimes: &sometimes{
 | 
			
		||||
@@ -204,6 +213,8 @@ type Authenticator struct {
 | 
			
		||||
	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
 | 
			
		||||
@@ -367,19 +378,16 @@ func (a *Authenticator) refreshCredsLocked(r *clientauthentication.Response) err
 | 
			
		||||
			Interactive: a.interactive,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	if a.provideClusterInfo {
 | 
			
		||||
		cred.Spec.Cluster = a.cluster
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	env := append(a.environ(), a.env...)
 | 
			
		||||
	if a.group == v1alpha1.SchemeGroupVersion {
 | 
			
		||||
		// Input spec disabled for beta due to lack of use. Possibly re-enable this later if
 | 
			
		||||
		// someone wants it back.
 | 
			
		||||
		//
 | 
			
		||||
		// See: https://github.com/kubernetes/kubernetes/issues/61796
 | 
			
		||||
	data, err := runtime.Encode(codecs.LegacyCodec(a.group), cred)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("encode ExecCredentials: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	env = append(env, fmt.Sprintf("%s=%s", execInfoEnv, data))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stdout := &bytes.Buffer{}
 | 
			
		||||
	cmd := exec.Command(a.cmd, a.args...)
 | 
			
		||||
 
 | 
			
		||||
@@ -116,7 +116,23 @@ func TestCacheKey(t *testing.T) {
 | 
			
		||||
			{Name: "7", Value: "8"},
 | 
			
		||||
		},
 | 
			
		||||
		APIVersion:         "client.authentication.k8s.io/v1alpha1",
 | 
			
		||||
		ProvideClusterInfo: true,
 | 
			
		||||
	}
 | 
			
		||||
	c1c := &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",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c2 := &api.ExecConfig{
 | 
			
		||||
		Command: "foo-bar",
 | 
			
		||||
		Args:    []string{"1", "2"},
 | 
			
		||||
@@ -126,7 +142,23 @@ func TestCacheKey(t *testing.T) {
 | 
			
		||||
			{Name: "7", Value: "8"},
 | 
			
		||||
		},
 | 
			
		||||
		APIVersion:         "client.authentication.k8s.io/v1alpha1",
 | 
			
		||||
		ProvideClusterInfo: true,
 | 
			
		||||
	}
 | 
			
		||||
	c2c := &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",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c3 := &api.ExecConfig{
 | 
			
		||||
		Command: "foo-bar",
 | 
			
		||||
		Args:    []string{"1", "2"},
 | 
			
		||||
@@ -136,9 +168,88 @@ func TestCacheKey(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
		APIVersion: "client.authentication.k8s.io/v1alpha1",
 | 
			
		||||
	}
 | 
			
		||||
	key1 := cacheKey(c1)
 | 
			
		||||
	key2 := cacheKey(c2)
 | 
			
		||||
	key3 := cacheKey(c3)
 | 
			
		||||
	c3c := &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",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c4 := &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",
 | 
			
		||||
	}
 | 
			
		||||
	c4c := &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",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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")
 | 
			
		||||
	}
 | 
			
		||||
@@ -148,6 +259,15 @@ func TestCacheKey(t *testing.T) {
 | 
			
		||||
	if key2 == key3 {
 | 
			
		||||
		t.Error("key2 and key3 matched")
 | 
			
		||||
	}
 | 
			
		||||
	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) {
 | 
			
		||||
@@ -173,6 +293,7 @@ func TestRefreshCreds(t *testing.T) {
 | 
			
		||||
		name          string
 | 
			
		||||
		config        api.ExecConfig
 | 
			
		||||
		exitCode      int
 | 
			
		||||
		cluster       *clientauthentication.Cluster
 | 
			
		||||
		output        string
 | 
			
		||||
		interactive   bool
 | 
			
		||||
		response      *clientauthentication.Response
 | 
			
		||||
@@ -393,6 +514,11 @@ func TestRefreshCreds(t *testing.T) {
 | 
			
		||||
			config: api.ExecConfig{
 | 
			
		||||
				APIVersion: "client.authentication.k8s.io/v1beta1",
 | 
			
		||||
			},
 | 
			
		||||
			wantInput: `{
 | 
			
		||||
				"kind": "ExecCredential",
 | 
			
		||||
				"apiVersion": "client.authentication.k8s.io/v1beta1",
 | 
			
		||||
				"spec": {}
 | 
			
		||||
			}`,
 | 
			
		||||
			output: `{
 | 
			
		||||
				"kind": "ExecCredential",
 | 
			
		||||
				"apiVersion": "client.authentication.k8s.io/v1beta1",
 | 
			
		||||
@@ -407,6 +533,11 @@ func TestRefreshCreds(t *testing.T) {
 | 
			
		||||
			config: api.ExecConfig{
 | 
			
		||||
				APIVersion: "client.authentication.k8s.io/v1beta1",
 | 
			
		||||
			},
 | 
			
		||||
			wantInput: `{
 | 
			
		||||
				"kind": "ExecCredential",
 | 
			
		||||
				"apiVersion": "client.authentication.k8s.io/v1beta1",
 | 
			
		||||
				"spec": {}
 | 
			
		||||
			}`,
 | 
			
		||||
			output: `{
 | 
			
		||||
				"kind": "ExecCredential",
 | 
			
		||||
				"apiVersion": "client.authentication.k8s.io/v1beta1",
 | 
			
		||||
@@ -473,6 +604,146 @@ func TestRefreshCreds(t *testing.T) {
 | 
			
		||||
			wantErr:       true,
 | 
			
		||||
			wantErrSubstr: "73",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "alpha-with-cluster-is-ignored",
 | 
			
		||||
			config: api.ExecConfig{
 | 
			
		||||
				APIVersion: "client.authentication.k8s.io/v1alpha1",
 | 
			
		||||
			},
 | 
			
		||||
			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":"panda"}}`),
 | 
			
		||||
					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/v1alpha1",
 | 
			
		||||
				"spec": {
 | 
			
		||||
					"response": {
 | 
			
		||||
						"header": {
 | 
			
		||||
							"WWW-Authenticate": [
 | 
			
		||||
								"Basic realm=\"Access to the staging site\", charset=\"UTF-8\""
 | 
			
		||||
							]
 | 
			
		||||
						},
 | 
			
		||||
						"code": 401
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}`,
 | 
			
		||||
			output: `{
 | 
			
		||||
				"kind": "ExecCredential",
 | 
			
		||||
				"apiVersion": "client.authentication.k8s.io/v1alpha1",
 | 
			
		||||
				"status": {
 | 
			
		||||
					"token": "foo-bar"
 | 
			
		||||
				}
 | 
			
		||||
			}`,
 | 
			
		||||
			wantCreds: credentials{token: "foo-bar"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "beta-with-cluster-and-provide-cluster-info-is-serialized",
 | 
			
		||||
			config: api.ExecConfig{
 | 
			
		||||
				APIVersion:         "client.authentication.k8s.io/v1beta1",
 | 
			
		||||
				ProvideClusterInfo: true,
 | 
			
		||||
			},
 | 
			
		||||
			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": {
 | 
			
		||||
					"cluster": {
 | 
			
		||||
						"server": "foo",
 | 
			
		||||
						"tls-server-name": "bar",
 | 
			
		||||
						"certificate-authority-data": "YmF6",
 | 
			
		||||
						"config": {
 | 
			
		||||
							"apiVersion": "group/v1",
 | 
			
		||||
							"kind": "PluginConfig",
 | 
			
		||||
							"spec": {
 | 
			
		||||
								"audience": "snorlax"
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}`,
 | 
			
		||||
			output: `{
 | 
			
		||||
				"kind": "ExecCredential",
 | 
			
		||||
				"apiVersion": "client.authentication.k8s.io/v1beta1",
 | 
			
		||||
				"status": {
 | 
			
		||||
					"token": "foo-bar"
 | 
			
		||||
				}
 | 
			
		||||
			}`,
 | 
			
		||||
			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 {
 | 
			
		||||
@@ -491,7 +762,7 @@ func TestRefreshCreds(t *testing.T) {
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			a, err := newAuthenticator(newCache(), &c)
 | 
			
		||||
			a, err := newAuthenticator(newCache(), &c, test.cluster)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
@@ -569,7 +840,7 @@ func TestRoundTripper(t *testing.T) {
 | 
			
		||||
		Command:    "./testdata/test-plugin.sh",
 | 
			
		||||
		APIVersion: "client.authentication.k8s.io/v1alpha1",
 | 
			
		||||
	}
 | 
			
		||||
	a, err := newAuthenticator(newCache(), &c)
 | 
			
		||||
	a, err := newAuthenticator(newCache(), &c, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -655,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)
 | 
			
		||||
	}
 | 
			
		||||
@@ -694,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",
 | 
			
		||||
	})
 | 
			
		||||
	}, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -784,7 +1055,7 @@ func TestConcurrentUpdateTransportConfig(t *testing.T) {
 | 
			
		||||
		Command:    "./testdata/test-plugin.sh",
 | 
			
		||||
		APIVersion: "client.authentication.k8s.io/v1alpha1",
 | 
			
		||||
	}
 | 
			
		||||
	a, err := newAuthenticator(newCache(), &c)
 | 
			
		||||
	a, err := newAuthenticator(newCache(), &c, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -851,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)
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,11 +11,13 @@ go_test(
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "client_test.go",
 | 
			
		||||
        "config_test.go",
 | 
			
		||||
        "exec_test.go",
 | 
			
		||||
        "plugin_test.go",
 | 
			
		||||
        "request_test.go",
 | 
			
		||||
        "url_utils_test.go",
 | 
			
		||||
        "urlbackoff_test.go",
 | 
			
		||||
    ],
 | 
			
		||||
    data = glob(["testdata/**"]),
 | 
			
		||||
    embed = [":go_default_library"],
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
@@ -33,6 +35,7 @@ go_test(
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/rest/watch:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/transport:go_default_library",
 | 
			
		||||
@@ -50,6 +53,7 @@ go_library(
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "client.go",
 | 
			
		||||
        "config.go",
 | 
			
		||||
        "exec.go",
 | 
			
		||||
        "plugin.go",
 | 
			
		||||
        "request.go",
 | 
			
		||||
        "transport.go",
 | 
			
		||||
@@ -71,6 +75,7 @@ go_library(
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/pkg/version:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/rest/watch:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -160,6 +160,15 @@ func (sanitizedAuthConfigPersister) String() string {
 | 
			
		||||
	return "rest.AuthProviderConfigPersister(--- REDACTED ---)"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type sanitizedObject struct{ runtime.Object }
 | 
			
		||||
 | 
			
		||||
func (sanitizedObject) GoString() string {
 | 
			
		||||
	return "runtime.Object(--- REDACTED ---)"
 | 
			
		||||
}
 | 
			
		||||
func (sanitizedObject) String() string {
 | 
			
		||||
	return "runtime.Object(--- REDACTED ---)"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GoString implements fmt.GoStringer and sanitizes sensitive fields of Config
 | 
			
		||||
// to prevent accidental leaking via logs.
 | 
			
		||||
func (c *Config) GoString() string {
 | 
			
		||||
@@ -183,7 +192,9 @@ func (c *Config) String() string {
 | 
			
		||||
	if cc.AuthConfigPersister != nil {
 | 
			
		||||
		cc.AuthConfigPersister = sanitizedAuthConfigPersister{cc.AuthConfigPersister}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cc.ExecProvider != nil && cc.ExecProvider.Config != nil {
 | 
			
		||||
		cc.ExecProvider.Config = sanitizedObject{Object: cc.ExecProvider.Config}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%#v", cc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -588,7 +599,7 @@ func AnonymousClientConfig(config *Config) *Config {
 | 
			
		||||
 | 
			
		||||
// CopyConfig returns a copy of the given config
 | 
			
		||||
func CopyConfig(config *Config) *Config {
 | 
			
		||||
	return &Config{
 | 
			
		||||
	c := &Config{
 | 
			
		||||
		Host:            config.Host,
 | 
			
		||||
		APIPath:         config.APIPath,
 | 
			
		||||
		ContentConfig:   config.ContentConfig,
 | 
			
		||||
@@ -627,4 +638,8 @@ func CopyConfig(config *Config) *Config {
 | 
			
		||||
		Dial:               config.Dial,
 | 
			
		||||
		Proxy:              config.Proxy,
 | 
			
		||||
	}
 | 
			
		||||
	if config.ExecProvider != nil && config.ExecProvider.Config != nil {
 | 
			
		||||
		c.ExecProvider.Config = config.ExecProvider.Config.DeepCopyObject()
 | 
			
		||||
	}
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -337,6 +337,11 @@ func TestAnonymousConfig(t *testing.T) {
 | 
			
		||||
		func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
 | 
			
		||||
			*r = fakeProxyFunc
 | 
			
		||||
		},
 | 
			
		||||
		func(r *runtime.Object, f fuzz.Continue) {
 | 
			
		||||
			unknown := &runtime.Unknown{}
 | 
			
		||||
			f.Fuzz(unknown)
 | 
			
		||||
			*r = unknown
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
	for i := 0; i < 20; i++ {
 | 
			
		||||
		original := &Config{}
 | 
			
		||||
@@ -428,6 +433,11 @@ func TestCopyConfig(t *testing.T) {
 | 
			
		||||
		func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
 | 
			
		||||
			*r = fakeProxyFunc
 | 
			
		||||
		},
 | 
			
		||||
		func(r *runtime.Object, f fuzz.Continue) {
 | 
			
		||||
			unknown := &runtime.Unknown{}
 | 
			
		||||
			f.Fuzz(unknown)
 | 
			
		||||
			*r = unknown
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
	for i := 0; i < 20; i++ {
 | 
			
		||||
		original := &Config{}
 | 
			
		||||
@@ -527,6 +537,7 @@ func TestConfigStringer(t *testing.T) {
 | 
			
		||||
				ExecProvider: &clientcmdapi.ExecConfig{
 | 
			
		||||
					Args:   []string{"secret"},
 | 
			
		||||
					Env:    []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
 | 
			
		||||
					Config: &runtime.Unknown{Raw: []byte("here is some config data")},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectContent: []string{
 | 
			
		||||
@@ -545,6 +556,8 @@ func TestConfigStringer(t *testing.T) {
 | 
			
		||||
				formatBytes([]byte("fake key")),
 | 
			
		||||
				"secret",
 | 
			
		||||
				"s3cr3t",
 | 
			
		||||
				"here is some config data",
 | 
			
		||||
				formatBytes([]byte("super secret password")),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -591,6 +604,8 @@ func TestConfigSprint(t *testing.T) {
 | 
			
		||||
			Command:            "sudo",
 | 
			
		||||
			Args:               []string{"secret"},
 | 
			
		||||
			Env:                []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
 | 
			
		||||
			ProvideClusterInfo: true,
 | 
			
		||||
			Config:             &runtime.Unknown{Raw: []byte("super secret password")},
 | 
			
		||||
		},
 | 
			
		||||
		TLSClientConfig: TLSClientConfig{
 | 
			
		||||
			CertFile:   "a.crt",
 | 
			
		||||
@@ -611,7 +626,7 @@ func TestConfigSprint(t *testing.T) {
 | 
			
		||||
		Proxy:          fakeProxyFunc,
 | 
			
		||||
	}
 | 
			
		||||
	want := fmt.Sprintf(
 | 
			
		||||
		`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.AuthProviderConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: ""}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), WarningHandler:rest.fakeWarningHandler{}, Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`,
 | 
			
		||||
		`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.ExecConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: "", ProvideClusterInfo: true, Config: runtime.Object(--- REDACTED ---)}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), WarningHandler:rest.fakeWarningHandler{}, Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`,
 | 
			
		||||
		c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										85
									
								
								staging/src/k8s.io/client-go/rest/exec.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								staging/src/k8s.io/client-go/rest/exec.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2020 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 rest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/client-go/pkg/apis/clientauthentication"
 | 
			
		||||
	clientauthenticationapi "k8s.io/client-go/pkg/apis/clientauthentication"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// This file contains Config logic related to exec credential plugins.
 | 
			
		||||
 | 
			
		||||
// ConfigToExecCluster creates a clientauthenticationapi.Cluster with the corresponding fields from
 | 
			
		||||
// the provided Config.
 | 
			
		||||
func ConfigToExecCluster(config *Config) (*clientauthenticationapi.Cluster, error) {
 | 
			
		||||
	caData, err := dataFromSliceOrFile(config.CAData, config.CAFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to load CA bundle for execProvider: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var proxyURL string
 | 
			
		||||
	if config.Proxy != nil {
 | 
			
		||||
		req, err := http.NewRequest("", config.Host, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("failed to create proxy URL request for execProvider: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		url, err := config.Proxy(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("failed to get proxy URL for execProvider: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		if url != nil {
 | 
			
		||||
			proxyURL = url.String()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &clientauthentication.Cluster{
 | 
			
		||||
		Server:                   config.Host,
 | 
			
		||||
		TLSServerName:            config.ServerName,
 | 
			
		||||
		InsecureSkipTLSVerify:    config.Insecure,
 | 
			
		||||
		CertificateAuthorityData: caData,
 | 
			
		||||
		ProxyURL:                 proxyURL,
 | 
			
		||||
		Config:                   config.ExecProvider.Config,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecClusterToConfig creates a Config with the corresponding fields from the provided
 | 
			
		||||
// clientauthenticationapi.Cluster. The returned Config will be anonymous (i.e., it will not have
 | 
			
		||||
// any authentication-related fields set).
 | 
			
		||||
func ExecClusterToConfig(cluster *clientauthentication.Cluster) (*Config, error) {
 | 
			
		||||
	var proxy func(*http.Request) (*url.URL, error)
 | 
			
		||||
	if cluster.ProxyURL != "" {
 | 
			
		||||
		proxyURL, err := url.Parse(cluster.ProxyURL)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("cannot parse proxy URL: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		proxy = http.ProxyURL(proxyURL)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &Config{
 | 
			
		||||
		Host: cluster.Server,
 | 
			
		||||
		TLSClientConfig: TLSClientConfig{
 | 
			
		||||
			Insecure:   cluster.InsecureSkipTLSVerify,
 | 
			
		||||
			ServerName: cluster.TLSServerName,
 | 
			
		||||
			CAData:     cluster.CertificateAuthorityData,
 | 
			
		||||
		},
 | 
			
		||||
		Proxy: proxy,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										384
									
								
								staging/src/k8s.io/client-go/rest/exec_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										384
									
								
								staging/src/k8s.io/client-go/rest/exec_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,384 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2020 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 rest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-cmp/cmp"
 | 
			
		||||
	fuzz "github.com/google/gofuzz"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/client-go/pkg/apis/clientauthentication"
 | 
			
		||||
	clientauthenticationapi "k8s.io/client-go/pkg/apis/clientauthentication"
 | 
			
		||||
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
 | 
			
		||||
	"k8s.io/client-go/transport"
 | 
			
		||||
	"k8s.io/client-go/util/flowcontrol"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestConfigToExecCluster(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	const proxyURL = "https://some-proxy-url.com/tuna/fish"
 | 
			
		||||
	proxy := func(r *http.Request) (*url.URL, error) {
 | 
			
		||||
		return url.Parse(proxyURL)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name            string
 | 
			
		||||
		in              Config
 | 
			
		||||
		wantOut         clientauthenticationapi.Cluster
 | 
			
		||||
		wantErrorPrefix string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "CA data from memory",
 | 
			
		||||
			in: Config{
 | 
			
		||||
				ExecProvider: &clientcmdapi.ExecConfig{
 | 
			
		||||
					ProvideClusterInfo: true,
 | 
			
		||||
					Config: &runtime.Unknown{
 | 
			
		||||
						Raw: []byte("stuff"),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Host: "some-host",
 | 
			
		||||
				TLSClientConfig: TLSClientConfig{
 | 
			
		||||
					ServerName: "some-server-name",
 | 
			
		||||
					Insecure:   true,
 | 
			
		||||
					CAData:     []byte("some-ca-data"),
 | 
			
		||||
				},
 | 
			
		||||
				Proxy: proxy,
 | 
			
		||||
			},
 | 
			
		||||
			wantOut: clientauthenticationapi.Cluster{
 | 
			
		||||
				Server:                   "some-host",
 | 
			
		||||
				TLSServerName:            "some-server-name",
 | 
			
		||||
				InsecureSkipTLSVerify:    true,
 | 
			
		||||
				CertificateAuthorityData: []byte("some-ca-data"),
 | 
			
		||||
				ProxyURL:                 proxyURL,
 | 
			
		||||
				Config: &runtime.Unknown{
 | 
			
		||||
					Raw: []byte("stuff"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "CA data from file",
 | 
			
		||||
			in: Config{
 | 
			
		||||
				ExecProvider: &clientcmdapi.ExecConfig{
 | 
			
		||||
					ProvideClusterInfo: true,
 | 
			
		||||
					Config: &runtime.Unknown{
 | 
			
		||||
						Raw: []byte("stuff"),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Host: "some-host",
 | 
			
		||||
				TLSClientConfig: TLSClientConfig{
 | 
			
		||||
					ServerName: "some-server-name",
 | 
			
		||||
					Insecure:   true,
 | 
			
		||||
					CAFile:     "testdata/ca.pem",
 | 
			
		||||
				},
 | 
			
		||||
				Proxy: proxy,
 | 
			
		||||
			},
 | 
			
		||||
			wantOut: clientauthenticationapi.Cluster{
 | 
			
		||||
				Server:                   "some-host",
 | 
			
		||||
				TLSServerName:            "some-server-name",
 | 
			
		||||
				InsecureSkipTLSVerify:    true,
 | 
			
		||||
				CertificateAuthorityData: []byte("a CA bundle lives here"),
 | 
			
		||||
				ProxyURL:                 proxyURL,
 | 
			
		||||
				Config: &runtime.Unknown{
 | 
			
		||||
					Raw: []byte("stuff"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "no CA data",
 | 
			
		||||
			in: Config{
 | 
			
		||||
				ExecProvider: &clientcmdapi.ExecConfig{
 | 
			
		||||
					ProvideClusterInfo: true,
 | 
			
		||||
				},
 | 
			
		||||
				TLSClientConfig: TLSClientConfig{
 | 
			
		||||
					CAFile: "this-file-does-not-exist",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			wantErrorPrefix: "failed to load CA bundle for execProvider: ",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "nil proxy",
 | 
			
		||||
			in: Config{
 | 
			
		||||
				ExecProvider: &clientcmdapi.ExecConfig{
 | 
			
		||||
					ProvideClusterInfo: true,
 | 
			
		||||
					Config: &runtime.Unknown{
 | 
			
		||||
						Raw: []byte("stuff"),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Host: "some-host",
 | 
			
		||||
				TLSClientConfig: TLSClientConfig{
 | 
			
		||||
					ServerName: "some-server-name",
 | 
			
		||||
					Insecure:   true,
 | 
			
		||||
					CAFile:     "testdata/ca.pem",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			wantOut: clientauthenticationapi.Cluster{
 | 
			
		||||
				Server:                   "some-host",
 | 
			
		||||
				TLSServerName:            "some-server-name",
 | 
			
		||||
				InsecureSkipTLSVerify:    true,
 | 
			
		||||
				CertificateAuthorityData: []byte("a CA bundle lives here"),
 | 
			
		||||
				Config: &runtime.Unknown{
 | 
			
		||||
					Raw: []byte("stuff"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "bad proxy",
 | 
			
		||||
			in: Config{
 | 
			
		||||
				ExecProvider: &clientcmdapi.ExecConfig{
 | 
			
		||||
					ProvideClusterInfo: true,
 | 
			
		||||
				},
 | 
			
		||||
				Proxy: func(_ *http.Request) (*url.URL, error) {
 | 
			
		||||
					return nil, errors.New("some proxy error")
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			wantErrorPrefix: "failed to get proxy URL for execProvider: some proxy error",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "proxy returns nil",
 | 
			
		||||
			in: Config{
 | 
			
		||||
				ExecProvider: &clientcmdapi.ExecConfig{
 | 
			
		||||
					ProvideClusterInfo: true,
 | 
			
		||||
				},
 | 
			
		||||
				Proxy: func(_ *http.Request) (*url.URL, error) {
 | 
			
		||||
					return nil, nil
 | 
			
		||||
				},
 | 
			
		||||
				Host: "some-host",
 | 
			
		||||
				TLSClientConfig: TLSClientConfig{
 | 
			
		||||
					ServerName: "some-server-name",
 | 
			
		||||
					Insecure:   true,
 | 
			
		||||
					CAFile:     "testdata/ca.pem",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			wantOut: clientauthenticationapi.Cluster{
 | 
			
		||||
				Server:                   "some-host",
 | 
			
		||||
				TLSServerName:            "some-server-name",
 | 
			
		||||
				InsecureSkipTLSVerify:    true,
 | 
			
		||||
				CertificateAuthorityData: []byte("a CA bundle lives here"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "invalid config host",
 | 
			
		||||
			in: Config{
 | 
			
		||||
				ExecProvider: &clientcmdapi.ExecConfig{
 | 
			
		||||
					ProvideClusterInfo: true,
 | 
			
		||||
				},
 | 
			
		||||
				Proxy: func(_ *http.Request) (*url.URL, error) {
 | 
			
		||||
					return nil, nil
 | 
			
		||||
				},
 | 
			
		||||
				Host: "invalid-config-host\n",
 | 
			
		||||
			},
 | 
			
		||||
			wantErrorPrefix: "failed to create proxy URL request for execProvider: ",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		test := test
 | 
			
		||||
		t.Run(test.name, func(t *testing.T) {
 | 
			
		||||
			out, err := ConfigToExecCluster(&test.in)
 | 
			
		||||
			if test.wantErrorPrefix != "" {
 | 
			
		||||
				if err == nil {
 | 
			
		||||
					t.Error("wanted error")
 | 
			
		||||
				} else if !strings.HasPrefix(err.Error(), test.wantErrorPrefix) {
 | 
			
		||||
					t.Errorf("wanted error prefix %q, got %q", test.wantErrorPrefix, err.Error())
 | 
			
		||||
				}
 | 
			
		||||
			} else if diff := cmp.Diff(&test.wantOut, out); diff != "" {
 | 
			
		||||
				t.Errorf("unexpected returned cluster: -got, +want:\n %s", diff)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestConfigToExecClusterRoundtrip(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	f := fuzz.New().NilChance(0.5).NumElements(1, 1)
 | 
			
		||||
	f.Funcs(
 | 
			
		||||
		func(r *runtime.Codec, f fuzz.Continue) {
 | 
			
		||||
			codec := &fakeCodec{}
 | 
			
		||||
			f.Fuzz(codec)
 | 
			
		||||
			*r = codec
 | 
			
		||||
		},
 | 
			
		||||
		func(r *http.RoundTripper, f fuzz.Continue) {
 | 
			
		||||
			roundTripper := &fakeRoundTripper{}
 | 
			
		||||
			f.Fuzz(roundTripper)
 | 
			
		||||
			*r = roundTripper
 | 
			
		||||
		},
 | 
			
		||||
		func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) {
 | 
			
		||||
			*fn = fakeWrapperFunc
 | 
			
		||||
		},
 | 
			
		||||
		func(fn *transport.WrapperFunc, f fuzz.Continue) {
 | 
			
		||||
			*fn = fakeWrapperFunc
 | 
			
		||||
		},
 | 
			
		||||
		func(r *runtime.NegotiatedSerializer, f fuzz.Continue) {
 | 
			
		||||
			serializer := &fakeNegotiatedSerializer{}
 | 
			
		||||
			f.Fuzz(serializer)
 | 
			
		||||
			*r = serializer
 | 
			
		||||
		},
 | 
			
		||||
		func(r *flowcontrol.RateLimiter, f fuzz.Continue) {
 | 
			
		||||
			limiter := &fakeLimiter{}
 | 
			
		||||
			f.Fuzz(limiter)
 | 
			
		||||
			*r = limiter
 | 
			
		||||
		},
 | 
			
		||||
		func(h *WarningHandler, f fuzz.Continue) {
 | 
			
		||||
			*h = &fakeWarningHandler{}
 | 
			
		||||
		},
 | 
			
		||||
		// Authentication does not require fuzzer
 | 
			
		||||
		func(r *AuthProviderConfigPersister, f fuzz.Continue) {},
 | 
			
		||||
		func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) {
 | 
			
		||||
			r.Config = map[string]string{}
 | 
			
		||||
		},
 | 
			
		||||
		func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
 | 
			
		||||
			*r = fakeDialFunc
 | 
			
		||||
		},
 | 
			
		||||
		func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
 | 
			
		||||
			*r = fakeProxyFunc
 | 
			
		||||
		},
 | 
			
		||||
		func(r *runtime.Object, f fuzz.Continue) {
 | 
			
		||||
			unknown := &runtime.Unknown{}
 | 
			
		||||
			f.Fuzz(unknown)
 | 
			
		||||
			*r = unknown
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
	for i := 0; i < 100; i++ {
 | 
			
		||||
		expected := &Config{}
 | 
			
		||||
		f.Fuzz(expected)
 | 
			
		||||
 | 
			
		||||
		// This is the list of known fields that this roundtrip doesn't care about. We should add new
 | 
			
		||||
		// fields to this list if we don't want to roundtrip them on exec cluster conversion.
 | 
			
		||||
		expected.APIPath = ""
 | 
			
		||||
		expected.ContentConfig = ContentConfig{}
 | 
			
		||||
		expected.Username = ""
 | 
			
		||||
		expected.Password = ""
 | 
			
		||||
		expected.BearerToken = ""
 | 
			
		||||
		expected.BearerTokenFile = ""
 | 
			
		||||
		expected.Impersonate = ImpersonationConfig{}
 | 
			
		||||
		expected.AuthProvider = nil
 | 
			
		||||
		expected.AuthConfigPersister = nil
 | 
			
		||||
		expected.ExecProvider = &clientcmdapi.ExecConfig{} // ConfigToExecCluster assumes != nil.
 | 
			
		||||
		expected.TLSClientConfig.CertFile = ""
 | 
			
		||||
		expected.TLSClientConfig.KeyFile = ""
 | 
			
		||||
		expected.TLSClientConfig.CAFile = ""
 | 
			
		||||
		expected.TLSClientConfig.CertData = nil
 | 
			
		||||
		expected.TLSClientConfig.KeyData = nil
 | 
			
		||||
		expected.TLSClientConfig.NextProtos = nil
 | 
			
		||||
		expected.UserAgent = ""
 | 
			
		||||
		expected.DisableCompression = false
 | 
			
		||||
		expected.Transport = nil
 | 
			
		||||
		expected.WrapTransport = nil
 | 
			
		||||
		expected.QPS = 0.0
 | 
			
		||||
		expected.Burst = 0
 | 
			
		||||
		expected.RateLimiter = nil
 | 
			
		||||
		expected.WarningHandler = nil
 | 
			
		||||
		expected.Timeout = 0
 | 
			
		||||
		expected.Dial = nil
 | 
			
		||||
 | 
			
		||||
		// Manually set URLs so we don't get an error when parsing these during the roundtrip.
 | 
			
		||||
		if expected.Host != "" {
 | 
			
		||||
			expected.Host = "https://some-server-url.com/tuna/fish"
 | 
			
		||||
		}
 | 
			
		||||
		if expected.Proxy != nil {
 | 
			
		||||
			expected.Proxy = func(_ *http.Request) (*url.URL, error) {
 | 
			
		||||
				return url.Parse("https://some-proxy-url.com/tuna/fish")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cluster, err := ConfigToExecCluster(expected)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		actual, err := ExecClusterToConfig(cluster)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if actual.Proxy != nil {
 | 
			
		||||
			actualURL, actualErr := actual.Proxy(nil)
 | 
			
		||||
			expectedURL, expectedErr := expected.Proxy(nil)
 | 
			
		||||
			if actualErr != nil {
 | 
			
		||||
				t.Fatalf("failed to get url from actual proxy func: %s", actualErr.Error())
 | 
			
		||||
			}
 | 
			
		||||
			if expectedErr != nil {
 | 
			
		||||
				t.Fatalf("failed to get url from expected proxy func: %s", actualErr.Error())
 | 
			
		||||
			}
 | 
			
		||||
			if diff := cmp.Diff(actualURL, expectedURL); diff != "" {
 | 
			
		||||
				t.Fatal("we dropped the Config.Proxy field during conversion")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		actual.Proxy = nil
 | 
			
		||||
		expected.Proxy = nil
 | 
			
		||||
 | 
			
		||||
		if actual.ExecProvider != nil {
 | 
			
		||||
			t.Fatal("expected actual Config.ExecProvider field to be set to nil")
 | 
			
		||||
		}
 | 
			
		||||
		actual.ExecProvider = nil
 | 
			
		||||
		expected.ExecProvider = nil
 | 
			
		||||
 | 
			
		||||
		if diff := cmp.Diff(actual, expected); diff != "" {
 | 
			
		||||
			t.Fatalf("we dropped some Config fields during roundtrip, -got, +want:\n %s", diff)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExecClusterToConfigRoundtrip(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	f := fuzz.New().NilChance(0.5).NumElements(1, 1)
 | 
			
		||||
	f.Funcs(
 | 
			
		||||
		func(r *runtime.Object, f fuzz.Continue) {
 | 
			
		||||
			// We don't expect the clientauthentication.Cluster.Config to show up in the Config that
 | 
			
		||||
			// comes back from the roundtrip, so just set it to nil.
 | 
			
		||||
			*r = nil
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
	for i := 0; i < 100; i++ {
 | 
			
		||||
		expected := &clientauthentication.Cluster{}
 | 
			
		||||
		f.Fuzz(expected)
 | 
			
		||||
 | 
			
		||||
		// Manually set URLs so we don't get an error when parsing these during the roundtrip.
 | 
			
		||||
		if expected.Server != "" {
 | 
			
		||||
			expected.Server = "https://some-server-url.com/tuna/fish"
 | 
			
		||||
		}
 | 
			
		||||
		if expected.ProxyURL != "" {
 | 
			
		||||
			expected.ProxyURL = "https://some-proxy-url.com/tuna/fish"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		config, err := ExecClusterToConfig(expected)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// ConfigToExecCluster assumes config.ExecProvider is not nil.
 | 
			
		||||
		config.ExecProvider = &clientcmdapi.ExecConfig{}
 | 
			
		||||
 | 
			
		||||
		actual, err := ConfigToExecCluster(config)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if diff := cmp.Diff(actual, expected); diff != "" {
 | 
			
		||||
			t.Fatalf("we dropped some Cluster fields during roundtrip: -got, +want:\n %s", diff)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								staging/src/k8s.io/client-go/rest/testdata/ca.pem
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								staging/src/k8s.io/client-go/rest/testdata/ca.pem
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
a CA bundle lives here
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/client-go/pkg/apis/clientauthentication"
 | 
			
		||||
	"k8s.io/client-go/plugin/pkg/client/auth/exec"
 | 
			
		||||
	"k8s.io/client-go/transport"
 | 
			
		||||
)
 | 
			
		||||
@@ -94,7 +95,15 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.ExecProvider != nil {
 | 
			
		||||
		provider, err := exec.GetAuthenticator(c.ExecProvider)
 | 
			
		||||
		var cluster *clientauthentication.Cluster
 | 
			
		||||
		if c.ExecProvider.ProvideClusterInfo {
 | 
			
		||||
			var err error
 | 
			
		||||
			cluster, err = ConfigToExecCluster(c)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		provider, err := exec.GetAuthenticator(c.ExecProvider, cluster)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,9 @@ filegroup(
 | 
			
		||||
 | 
			
		||||
filegroup(
 | 
			
		||||
    name = "all-srcs",
 | 
			
		||||
    srcs = [":package-srcs"],
 | 
			
		||||
    srcs = [
 | 
			
		||||
        ":package-srcs",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/tools/auth/exec:all-srcs",
 | 
			
		||||
    ],
 | 
			
		||||
    tags = ["automanaged"],
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								staging/src/k8s.io/client-go/tools/auth/exec/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								staging/src/k8s.io/client-go/tools/auth/exec/BUILD
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 | 
			
		||||
 | 
			
		||||
go_library(
 | 
			
		||||
    name = "go_default_library",
 | 
			
		||||
    srcs = ["exec.go"],
 | 
			
		||||
    importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/tools/auth/exec",
 | 
			
		||||
    importpath = "k8s.io/client-go/tools/auth/exec",
 | 
			
		||||
    visibility = ["//visibility:public"],
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/rest:go_default_library",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
go_test(
 | 
			
		||||
    name = "go_default_test",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "exec_test.go",
 | 
			
		||||
        "types_test.go",
 | 
			
		||||
    ],
 | 
			
		||||
    embed = [":go_default_library"],
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/rest:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library",
 | 
			
		||||
        "//vendor/github.com/google/go-cmp/cmp:go_default_library",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
filegroup(
 | 
			
		||||
    name = "package-srcs",
 | 
			
		||||
    srcs = glob(["**"]),
 | 
			
		||||
    tags = ["automanaged"],
 | 
			
		||||
    visibility = ["//visibility:private"],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
filegroup(
 | 
			
		||||
    name = "all-srcs",
 | 
			
		||||
    srcs = [":package-srcs"],
 | 
			
		||||
    tags = ["automanaged"],
 | 
			
		||||
    visibility = ["//visibility:public"],
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										110
									
								
								staging/src/k8s.io/client-go/tools/auth/exec/exec.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								staging/src/k8s.io/client-go/tools/auth/exec/exec.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2020 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 exec contains helper utilities for exec credential plugins.
 | 
			
		||||
package exec
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/serializer"
 | 
			
		||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
			
		||||
	"k8s.io/client-go/pkg/apis/clientauthentication"
 | 
			
		||||
	"k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1"
 | 
			
		||||
	"k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
 | 
			
		||||
	"k8s.io/client-go/rest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const execInfoEnv = "KUBERNETES_EXEC_INFO"
 | 
			
		||||
 | 
			
		||||
var scheme = runtime.NewScheme()
 | 
			
		||||
var codecs = serializer.NewCodecFactory(scheme)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
 | 
			
		||||
	utilruntime.Must(v1alpha1.AddToScheme(scheme))
 | 
			
		||||
	utilruntime.Must(v1beta1.AddToScheme(scheme))
 | 
			
		||||
	utilruntime.Must(clientauthentication.AddToScheme(scheme))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadExecCredentialFromEnv is a helper-wrapper around LoadExecCredential that loads from the
 | 
			
		||||
// well-known KUBERNETES_EXEC_INFO environment variable.
 | 
			
		||||
//
 | 
			
		||||
// When the KUBERNETES_EXEC_INFO environment variable is not set or is empty, then this function
 | 
			
		||||
// will immediately return an error.
 | 
			
		||||
func LoadExecCredentialFromEnv() (runtime.Object, *rest.Config, error) {
 | 
			
		||||
	env := os.Getenv(execInfoEnv)
 | 
			
		||||
	if env == "" {
 | 
			
		||||
		return nil, nil, errors.New("KUBERNETES_EXEC_INFO env var is unset or empty")
 | 
			
		||||
	}
 | 
			
		||||
	return LoadExecCredential([]byte(env))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadExecCredential loads the configuration needed for an exec plugin to communicate with a
 | 
			
		||||
// cluster.
 | 
			
		||||
//
 | 
			
		||||
// LoadExecCredential expects the provided data to be a serialized client.authentication.k8s.io
 | 
			
		||||
// ExecCredential object (of any version). If the provided data is invalid (i.e., it cannot be
 | 
			
		||||
// unmarshalled into any known client.authentication.k8s.io ExecCredential version), an error will
 | 
			
		||||
// be returned. A successfully unmarshalled ExecCredential will be returned as the first return
 | 
			
		||||
// value.
 | 
			
		||||
//
 | 
			
		||||
// If the provided data is successfully unmarshalled, but it does not contain cluster information
 | 
			
		||||
// (i.e., ExecCredential.Spec.Cluster == nil), then the returned rest.Config and error will be nil.
 | 
			
		||||
//
 | 
			
		||||
// Note that the returned rest.Config will use anonymous authentication, since the exec plugin has
 | 
			
		||||
// not returned credentials for this cluster yet.
 | 
			
		||||
func LoadExecCredential(data []byte) (runtime.Object, *rest.Config, error) {
 | 
			
		||||
	obj, gvk, err := codecs.UniversalDeserializer().Decode(data, nil, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("decode: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expectedGK := schema.GroupKind{
 | 
			
		||||
		Group: clientauthentication.SchemeGroupVersion.Group,
 | 
			
		||||
		Kind:  "ExecCredential",
 | 
			
		||||
	}
 | 
			
		||||
	if gvk.GroupKind() != expectedGK {
 | 
			
		||||
		return nil, nil, fmt.Errorf(
 | 
			
		||||
			"invalid group/kind: wanted %s, got %s",
 | 
			
		||||
			expectedGK.String(),
 | 
			
		||||
			gvk.GroupKind().String(),
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Explicitly convert object here so that we can return a nicer error message above for when the
 | 
			
		||||
	// data represents an invalid type.
 | 
			
		||||
	var execCredential clientauthentication.ExecCredential
 | 
			
		||||
	if err := scheme.Convert(obj, &execCredential, nil); err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("cannot convert to ExecCredential: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if execCredential.Spec.Cluster == nil {
 | 
			
		||||
		return nil, nil, errors.New("ExecCredential does not contain cluster information")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	restConfig, err := rest.ExecClusterToConfig(execCredential.Spec.Cluster)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("cannot create rest.Config: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return obj, restConfig, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										210
									
								
								staging/src/k8s.io/client-go/tools/auth/exec/exec_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								staging/src/k8s.io/client-go/tools/auth/exec/exec_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,210 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2020 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 exec
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-cmp/cmp"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	clientauthenticationv1alpha1 "k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1"
 | 
			
		||||
	clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
 | 
			
		||||
	"k8s.io/client-go/rest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// restInfo holds the rest.Client fields that we care about for test assertions.
 | 
			
		||||
type restInfo struct {
 | 
			
		||||
	host            string
 | 
			
		||||
	tlsClientConfig rest.TLSClientConfig
 | 
			
		||||
	proxyURL        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadExecCredential(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name               string
 | 
			
		||||
		data               []byte
 | 
			
		||||
		wantExecCredential runtime.Object
 | 
			
		||||
		wantRESTInfo       restInfo
 | 
			
		||||
		wantErrorPrefix    string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "v1beta1 happy path",
 | 
			
		||||
			data: marshal(t, clientauthenticationv1beta1.SchemeGroupVersion, &clientauthenticationv1beta1.ExecCredential{
 | 
			
		||||
				Spec: clientauthenticationv1beta1.ExecCredentialSpec{
 | 
			
		||||
					Cluster: &clientauthenticationv1beta1.Cluster{
 | 
			
		||||
						Server:                   "https://some-server/some/path",
 | 
			
		||||
						TLSServerName:            "some-server-name",
 | 
			
		||||
						InsecureSkipTLSVerify:    true,
 | 
			
		||||
						CertificateAuthorityData: []byte("some-ca-data"),
 | 
			
		||||
						ProxyURL:                 "https://some-proxy-url:12345",
 | 
			
		||||
						Config: runtime.RawExtension{
 | 
			
		||||
							Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"names":["marshmallow","zelda"]}}`),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			}),
 | 
			
		||||
			wantExecCredential: &clientauthenticationv1beta1.ExecCredential{
 | 
			
		||||
				TypeMeta: metav1.TypeMeta{
 | 
			
		||||
					Kind:       "ExecCredential",
 | 
			
		||||
					APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
 | 
			
		||||
				},
 | 
			
		||||
				Spec: clientauthenticationv1beta1.ExecCredentialSpec{
 | 
			
		||||
					Cluster: &clientauthenticationv1beta1.Cluster{
 | 
			
		||||
						Server:                   "https://some-server/some/path",
 | 
			
		||||
						TLSServerName:            "some-server-name",
 | 
			
		||||
						InsecureSkipTLSVerify:    true,
 | 
			
		||||
						CertificateAuthorityData: []byte("some-ca-data"),
 | 
			
		||||
						ProxyURL:                 "https://some-proxy-url:12345",
 | 
			
		||||
						Config: runtime.RawExtension{
 | 
			
		||||
							Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"names":["marshmallow","zelda"]}}`),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			wantRESTInfo: restInfo{
 | 
			
		||||
				host: "https://some-server/some/path",
 | 
			
		||||
				tlsClientConfig: rest.TLSClientConfig{
 | 
			
		||||
					Insecure:   true,
 | 
			
		||||
					ServerName: "some-server-name",
 | 
			
		||||
					CAData:     []byte("some-ca-data"),
 | 
			
		||||
				},
 | 
			
		||||
				proxyURL: "https://some-proxy-url:12345",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "v1beta1 nil config",
 | 
			
		||||
			data: marshal(t, clientauthenticationv1beta1.SchemeGroupVersion, &clientauthenticationv1beta1.ExecCredential{
 | 
			
		||||
				Spec: clientauthenticationv1beta1.ExecCredentialSpec{
 | 
			
		||||
					Cluster: &clientauthenticationv1beta1.Cluster{
 | 
			
		||||
						Server:                   "https://some-server/some/path",
 | 
			
		||||
						TLSServerName:            "some-server-name",
 | 
			
		||||
						InsecureSkipTLSVerify:    true,
 | 
			
		||||
						CertificateAuthorityData: []byte("some-ca-data"),
 | 
			
		||||
						ProxyURL:                 "https://some-proxy-url:12345",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			}),
 | 
			
		||||
			wantExecCredential: &clientauthenticationv1beta1.ExecCredential{
 | 
			
		||||
				TypeMeta: metav1.TypeMeta{
 | 
			
		||||
					Kind:       "ExecCredential",
 | 
			
		||||
					APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
 | 
			
		||||
				},
 | 
			
		||||
				Spec: clientauthenticationv1beta1.ExecCredentialSpec{
 | 
			
		||||
					Cluster: &clientauthenticationv1beta1.Cluster{
 | 
			
		||||
						Server:                   "https://some-server/some/path",
 | 
			
		||||
						TLSServerName:            "some-server-name",
 | 
			
		||||
						InsecureSkipTLSVerify:    true,
 | 
			
		||||
						CertificateAuthorityData: []byte("some-ca-data"),
 | 
			
		||||
						ProxyURL:                 "https://some-proxy-url:12345",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			wantRESTInfo: restInfo{
 | 
			
		||||
				host: "https://some-server/some/path",
 | 
			
		||||
				tlsClientConfig: rest.TLSClientConfig{
 | 
			
		||||
					Insecure:   true,
 | 
			
		||||
					ServerName: "some-server-name",
 | 
			
		||||
					CAData:     []byte("some-ca-data"),
 | 
			
		||||
				},
 | 
			
		||||
				proxyURL: "https://some-proxy-url:12345",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "v1beta1 invalid cluster",
 | 
			
		||||
			data: marshal(t, clientauthenticationv1beta1.SchemeGroupVersion, &clientauthenticationv1beta1.ExecCredential{
 | 
			
		||||
				Spec: clientauthenticationv1beta1.ExecCredentialSpec{
 | 
			
		||||
					Cluster: &clientauthenticationv1beta1.Cluster{
 | 
			
		||||
						ProxyURL: "invalid- url\n",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			}),
 | 
			
		||||
			wantErrorPrefix: "cannot create rest.Config",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:            "v1beta1 nil cluster",
 | 
			
		||||
			data:            marshal(t, clientauthenticationv1beta1.SchemeGroupVersion, &clientauthenticationv1beta1.ExecCredential{}),
 | 
			
		||||
			wantErrorPrefix: "ExecCredential does not contain cluster information",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:            "v1alpha1",
 | 
			
		||||
			data:            marshal(t, clientauthenticationv1alpha1.SchemeGroupVersion, &clientauthenticationv1alpha1.ExecCredential{}),
 | 
			
		||||
			wantErrorPrefix: "ExecCredential does not contain cluster information",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:            "invalid object kind",
 | 
			
		||||
			data:            marshal(t, metav1.SchemeGroupVersion, &metav1.Status{}),
 | 
			
		||||
			wantErrorPrefix: "invalid group/kind: wanted ExecCredential.client.authentication.k8s.io, got Status",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:            "bad data",
 | 
			
		||||
			data:            []byte("bad data"),
 | 
			
		||||
			wantErrorPrefix: "decode: ",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		test := test
 | 
			
		||||
		t.Run(test.name, func(t *testing.T) {
 | 
			
		||||
			t.Parallel()
 | 
			
		||||
 | 
			
		||||
			execCredential, restConfig, err := LoadExecCredential(test.data)
 | 
			
		||||
			if test.wantErrorPrefix != "" {
 | 
			
		||||
				if err == nil {
 | 
			
		||||
					t.Error("wanted error, got success")
 | 
			
		||||
				} else if !strings.HasPrefix(err.Error(), test.wantErrorPrefix) {
 | 
			
		||||
					t.Errorf("wanted '%s', got '%s'", test.wantErrorPrefix, err.Error())
 | 
			
		||||
				}
 | 
			
		||||
			} else if err != nil {
 | 
			
		||||
				t.Error(err)
 | 
			
		||||
			} else {
 | 
			
		||||
				if diff := cmp.Diff(test.wantExecCredential, execCredential); diff != "" {
 | 
			
		||||
					t.Error(diff)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if diff := cmp.Diff(test.wantRESTInfo.host, restConfig.Host); diff != "" {
 | 
			
		||||
					t.Error(diff)
 | 
			
		||||
				}
 | 
			
		||||
				if diff := cmp.Diff(test.wantRESTInfo.tlsClientConfig, restConfig.TLSClientConfig); diff != "" {
 | 
			
		||||
					t.Error(diff)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				proxyURL, err := restConfig.Proxy(nil)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					t.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
				if diff := cmp.Diff(test.wantRESTInfo.proxyURL, proxyURL.String()); diff != "" {
 | 
			
		||||
					t.Error(diff)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func marshal(t *testing.T, gv schema.GroupVersion, obj runtime.Object) []byte {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
 | 
			
		||||
	data, err := runtime.Encode(codecs.LegacyCodec(gv), obj)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										151
									
								
								staging/src/k8s.io/client-go/tools/auth/exec/types_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								staging/src/k8s.io/client-go/tools/auth/exec/types_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2020 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 exec
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	clientauthenticationv1alpha1 "k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1"
 | 
			
		||||
	clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
 | 
			
		||||
	clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TestV1beta1ClusterTypesAreSynced ensures that clientauthenticationv1beta1.Cluster stays in sync
 | 
			
		||||
// with clientcmdv1.Cluster.
 | 
			
		||||
//
 | 
			
		||||
// We want clientauthenticationv1beta1.Cluster to offer the same knobs as clientcmdv1.Cluster to
 | 
			
		||||
// allow someone to connect to the kubernetes API. This test should fail if a new field is added to
 | 
			
		||||
// one of the structs without updating the other.
 | 
			
		||||
func TestV1beta1ClusterTypesAreSynced(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	execType := reflect.TypeOf(clientauthenticationv1beta1.Cluster{})
 | 
			
		||||
	clientcmdType := reflect.TypeOf(clientcmdv1.Cluster{})
 | 
			
		||||
 | 
			
		||||
	t.Run("exec cluster fields match clientcmd cluster fields", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
 | 
			
		||||
		// These are fields that are specific to Cluster and shouldn't be in clientcmdv1.Cluster.
 | 
			
		||||
		execSkippedFieldNames := sets.NewString(
 | 
			
		||||
			// Cluster uses Config to provide its cluster-specific configuration object.
 | 
			
		||||
			"Config",
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		for i := 0; i < execType.NumField(); i++ {
 | 
			
		||||
			execField := execType.Field(i)
 | 
			
		||||
			if execSkippedFieldNames.Has(execField.Name) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			t.Run(execField.Name, func(t *testing.T) {
 | 
			
		||||
				t.Parallel()
 | 
			
		||||
				clientcmdField, ok := clientcmdType.FieldByName(execField.Name)
 | 
			
		||||
				if !ok {
 | 
			
		||||
					t.Errorf("unknown field (please add field to clientcmdv1.Cluster): '%s'", execField.Name)
 | 
			
		||||
				} else if execField.Type != clientcmdField.Type {
 | 
			
		||||
					t.Errorf(
 | 
			
		||||
						"type mismatch (please update Cluster.%s field type to match clientcmdv1.Cluster.%s field type): %q != %q",
 | 
			
		||||
						execField.Name,
 | 
			
		||||
						clientcmdField.Name,
 | 
			
		||||
						execField.Type,
 | 
			
		||||
						clientcmdField.Type,
 | 
			
		||||
					)
 | 
			
		||||
				} else if execField.Tag != clientcmdField.Tag {
 | 
			
		||||
					t.Errorf(
 | 
			
		||||
						"tag mismatch (please update Cluster.%s tag to match clientcmdv1.Cluster.%s tag): %q != %q",
 | 
			
		||||
						execField.Name,
 | 
			
		||||
						clientcmdField.Name,
 | 
			
		||||
						execField.Tag,
 | 
			
		||||
						clientcmdField.Tag,
 | 
			
		||||
					)
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("clientcmd cluster fields match exec cluster fields", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
 | 
			
		||||
		// These are the fields that we don't want to shadow from clientcmdv1.Cluster.
 | 
			
		||||
		clientcmdSkippedFieldNames := sets.NewString(
 | 
			
		||||
			// CA data will be passed via CertificateAuthorityData, so we don't need this field.
 | 
			
		||||
			"CertificateAuthority",
 | 
			
		||||
			// Cluster uses Config to provide its cluster-specific configuration object.
 | 
			
		||||
			"Extensions",
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		for i := 0; i < clientcmdType.NumField(); i++ {
 | 
			
		||||
			clientcmdField := clientcmdType.Field(i)
 | 
			
		||||
			if clientcmdSkippedFieldNames.Has(clientcmdField.Name) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			t.Run(clientcmdField.Name, func(t *testing.T) {
 | 
			
		||||
				t.Parallel()
 | 
			
		||||
				execField, ok := execType.FieldByName(clientcmdField.Name)
 | 
			
		||||
				if !ok {
 | 
			
		||||
					t.Errorf("unknown field (please add field to Cluster): '%s'", clientcmdField.Name)
 | 
			
		||||
				} else if clientcmdField.Type != execField.Type {
 | 
			
		||||
					t.Errorf(
 | 
			
		||||
						"type mismatch (please update clientcmdv1.Cluster.%s field type to match Cluster.%s field type): %q != %q",
 | 
			
		||||
						clientcmdField.Name,
 | 
			
		||||
						execField.Name,
 | 
			
		||||
						clientcmdField.Type,
 | 
			
		||||
						execField.Type,
 | 
			
		||||
					)
 | 
			
		||||
				} else if clientcmdField.Tag != execField.Tag {
 | 
			
		||||
					t.Errorf(
 | 
			
		||||
						"tag mismatch (please update clientcmdv1.Cluster.%s tag to match Cluster.%s tag): %q != %q",
 | 
			
		||||
						clientcmdField.Name,
 | 
			
		||||
						execField.Name,
 | 
			
		||||
						clientcmdField.Tag,
 | 
			
		||||
						execField.Tag,
 | 
			
		||||
					)
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestAllClusterTypesAreSynced is a TODO so that we remember to write a test similar to
 | 
			
		||||
// TestV1beta1ClusterTypesAreSynced for any future ExecCredential version. It should start failing
 | 
			
		||||
// when someone adds support for any other ExecCredential type to this package.
 | 
			
		||||
func TestAllClusterTypesAreSynced(t *testing.T) {
 | 
			
		||||
	versionsThatDontNeedTests := sets.NewString(
 | 
			
		||||
		// The internal Cluster type should only be used...internally...and therefore doesn't
 | 
			
		||||
		// necessarily need to be synced with clientcmdv1.
 | 
			
		||||
		runtime.APIVersionInternal,
 | 
			
		||||
		// V1alpha1 does not contain a Cluster type.
 | 
			
		||||
		clientauthenticationv1alpha1.SchemeGroupVersion.Version,
 | 
			
		||||
		// We have a test for v1beta1 above.
 | 
			
		||||
		clientauthenticationv1beta1.SchemeGroupVersion.Version,
 | 
			
		||||
	)
 | 
			
		||||
	for gvk := range scheme.AllKnownTypes() {
 | 
			
		||||
		if gvk.Group == clientauthenticationv1beta1.SchemeGroupVersion.Group &&
 | 
			
		||||
			gvk.Kind == "ExecCredential" {
 | 
			
		||||
			if !versionsThatDontNeedTests.Has(gvk.Version) {
 | 
			
		||||
				t.Errorf(
 | 
			
		||||
					"TODO: add test similar to TestV1beta1ClusterTypesAreSynced for client.authentication.k8s.io/%s",
 | 
			
		||||
					gvk.Version,
 | 
			
		||||
				)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -215,6 +215,36 @@ type ExecConfig struct {
 | 
			
		||||
	// present. For example, `brew install foo-cli` might be a good InstallHint for
 | 
			
		||||
	// foo-cli on Mac OS systems.
 | 
			
		||||
	InstallHint string `json:"installHint,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// ProvideClusterInfo determines whether or not to provide cluster information,
 | 
			
		||||
	// which could potentially contain very large CA data, to this exec plugin as a
 | 
			
		||||
	// part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set
 | 
			
		||||
	// to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for
 | 
			
		||||
	// reading this environment variable.
 | 
			
		||||
	ProvideClusterInfo bool `json:"provideClusterInfo"`
 | 
			
		||||
 | 
			
		||||
	// Config holds additional config data that is specific to the exec
 | 
			
		||||
	// plugin with regards to the cluster being authenticated to.
 | 
			
		||||
	//
 | 
			
		||||
	// This data is sourced from the clientcmd Cluster object's extensions[exec] field:
 | 
			
		||||
	//
 | 
			
		||||
	// clusters:
 | 
			
		||||
	// - name: my-cluster
 | 
			
		||||
	//   cluster:
 | 
			
		||||
	//     ...
 | 
			
		||||
	//     extensions:
 | 
			
		||||
	//     - name: client.authentication.k8s.io/exec  # reserved extension name for per cluster exec config
 | 
			
		||||
	//       extension:
 | 
			
		||||
	//         audience: 06e3fbd18de8  # arbitrary config
 | 
			
		||||
	//
 | 
			
		||||
	// In some environments, the user config may be exactly the same across many clusters
 | 
			
		||||
	// (i.e. call this exec plugin) minus some details that are specific to each cluster
 | 
			
		||||
	// such as the audience.  This field allows the per cluster config to be directly
 | 
			
		||||
	// specified with the cluster info.  Using this field to store secret data is not
 | 
			
		||||
	// recommended as one of the prime benefits of exec plugins is that no secrets need
 | 
			
		||||
	// to be stored directly in the kubeconfig.
 | 
			
		||||
	// +k8s:conversion-gen=false
 | 
			
		||||
	Config runtime.Object
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ fmt.Stringer = new(ExecConfig)
 | 
			
		||||
@@ -237,7 +267,11 @@ func (c ExecConfig) String() string {
 | 
			
		||||
	if len(c.Env) > 0 {
 | 
			
		||||
		env = "[]ExecEnvVar{--- REDACTED ---}"
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("api.AuthProviderConfig{Command: %q, Args: %#v, Env: %s, APIVersion: %q}", c.Command, args, env, c.APIVersion)
 | 
			
		||||
	config := "runtime.Object(nil)"
 | 
			
		||||
	if c.Config != nil {
 | 
			
		||||
		config = "runtime.Object(--- REDACTED ---)"
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("api.ExecConfig{Command: %q, Args: %#v, Env: %s, APIVersion: %q, ProvideClusterInfo: %t, Config: %s}", c.Command, args, env, c.APIVersion, c.ProvideClusterInfo, config)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecEnvVar is used for setting environment variables when executing an exec-based
 | 
			
		||||
 
 | 
			
		||||
@@ -214,6 +214,13 @@ type ExecConfig struct {
 | 
			
		||||
	// present. For example, `brew install foo-cli` might be a good InstallHint for
 | 
			
		||||
	// foo-cli on Mac OS systems.
 | 
			
		||||
	InstallHint string `json:"installHint,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// ProvideClusterInfo determines whether or not to provide cluster information,
 | 
			
		||||
	// which could potentially contain very large CA data, to this exec plugin as a
 | 
			
		||||
	// part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set
 | 
			
		||||
	// to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for
 | 
			
		||||
	// reading this environment variable.
 | 
			
		||||
	ProvideClusterInfo bool `json:"provideClusterInfo"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecEnvVar is used for setting environment variables when executing an exec-based
 | 
			
		||||
 
 | 
			
		||||
@@ -171,7 +171,15 @@ func autoConvert_v1_AuthInfo_To_api_AuthInfo(in *AuthInfo, out *api.AuthInfo, s
 | 
			
		||||
	out.Username = in.Username
 | 
			
		||||
	out.Password = in.Password
 | 
			
		||||
	out.AuthProvider = (*api.AuthProviderConfig)(unsafe.Pointer(in.AuthProvider))
 | 
			
		||||
	out.Exec = (*api.ExecConfig)(unsafe.Pointer(in.Exec))
 | 
			
		||||
	if in.Exec != nil {
 | 
			
		||||
		in, out := &in.Exec, &out.Exec
 | 
			
		||||
		*out = new(api.ExecConfig)
 | 
			
		||||
		if err := Convert_v1_ExecConfig_To_api_ExecConfig(*in, *out, s); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		out.Exec = nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(&in.Extensions, &out.Extensions, s); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -197,7 +205,15 @@ func autoConvert_api_AuthInfo_To_v1_AuthInfo(in *api.AuthInfo, out *AuthInfo, s
 | 
			
		||||
	out.Username = in.Username
 | 
			
		||||
	out.Password = in.Password
 | 
			
		||||
	out.AuthProvider = (*AuthProviderConfig)(unsafe.Pointer(in.AuthProvider))
 | 
			
		||||
	out.Exec = (*ExecConfig)(unsafe.Pointer(in.Exec))
 | 
			
		||||
	if in.Exec != nil {
 | 
			
		||||
		in, out := &in.Exec, &out.Exec
 | 
			
		||||
		*out = new(ExecConfig)
 | 
			
		||||
		if err := Convert_api_ExecConfig_To_v1_ExecConfig(*in, *out, s); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		out.Exec = nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(&in.Extensions, &out.Extensions, s); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -359,6 +375,7 @@ func autoConvert_v1_ExecConfig_To_api_ExecConfig(in *ExecConfig, out *api.ExecCo
 | 
			
		||||
	out.Env = *(*[]api.ExecEnvVar)(unsafe.Pointer(&in.Env))
 | 
			
		||||
	out.APIVersion = in.APIVersion
 | 
			
		||||
	out.InstallHint = in.InstallHint
 | 
			
		||||
	out.ProvideClusterInfo = in.ProvideClusterInfo
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -373,6 +390,8 @@ func autoConvert_api_ExecConfig_To_v1_ExecConfig(in *api.ExecConfig, out *ExecCo
 | 
			
		||||
	out.Env = *(*[]ExecEnvVar)(unsafe.Pointer(&in.Env))
 | 
			
		||||
	out.APIVersion = in.APIVersion
 | 
			
		||||
	out.InstallHint = in.InstallHint
 | 
			
		||||
	out.ProvideClusterInfo = in.ProvideClusterInfo
 | 
			
		||||
	// INFO: in.Config opted out of conversion generation
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -267,6 +267,9 @@ func (in *ExecConfig) DeepCopyInto(out *ExecConfig) {
 | 
			
		||||
		*out = make([]ExecEnvVar, len(*in))
 | 
			
		||||
		copy(*out, *in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Config != nil {
 | 
			
		||||
		out.Config = in.Config.DeepCopyObject()
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,11 @@ import (
 | 
			
		||||
	"github.com/imdario/mergo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// clusterExtensionKey is reserved in the cluster extensions list for exec plugin config.
 | 
			
		||||
	clusterExtensionKey = "client.authentication.k8s.io/exec"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// ClusterDefaults has the same behavior as the old EnvVar and DefaultCluster fields
 | 
			
		||||
	// DEPRECATED will be replaced
 | 
			
		||||
@@ -189,7 +194,7 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
 | 
			
		||||
			authInfoName, _ := config.getAuthInfoName()
 | 
			
		||||
			persister = PersisterForUser(config.configAccess, authInfoName)
 | 
			
		||||
		}
 | 
			
		||||
		userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
 | 
			
		||||
		userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister, configClusterInfo)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -232,7 +237,7 @@ func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo,
 | 
			
		||||
// 2.  configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
 | 
			
		||||
// 3.  if there is not enough information to identify the user, load try the ~/.kubernetes_auth file
 | 
			
		||||
// 4.  if there is not enough information to identify the user, prompt if possible
 | 
			
		||||
func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
 | 
			
		||||
func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
 | 
			
		||||
	mergedConfig := &restclient.Config{}
 | 
			
		||||
 | 
			
		||||
	// blindly overwrite existing values based on precedence
 | 
			
		||||
@@ -271,6 +276,7 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
 | 
			
		||||
	if configAuthInfo.Exec != nil {
 | 
			
		||||
		mergedConfig.ExecProvider = configAuthInfo.Exec
 | 
			
		||||
		mergedConfig.ExecProvider.InstallHint = cleanANSIEscapeCodes(mergedConfig.ExecProvider.InstallHint)
 | 
			
		||||
		mergedConfig.ExecProvider.Config = configClusterInfo.Extensions[clusterExtensionKey]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if there still isn't enough information to authenticate the user, try prompting
 | 
			
		||||
 
 | 
			
		||||
@@ -23,10 +23,11 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/imdario/mergo"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	restclient "k8s.io/client-go/rest"
 | 
			
		||||
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
 | 
			
		||||
 | 
			
		||||
	"github.com/imdario/mergo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMergoSemantics(t *testing.T) {
 | 
			
		||||
@@ -834,6 +835,11 @@ apiVersion: v1
 | 
			
		||||
clusters:
 | 
			
		||||
- cluster:
 | 
			
		||||
    server: https://localhost:8080
 | 
			
		||||
    extensions:
 | 
			
		||||
    - name: client.authentication.k8s.io/exec
 | 
			
		||||
      extension:
 | 
			
		||||
        audience: foo
 | 
			
		||||
        other: bar
 | 
			
		||||
  name: foo-cluster
 | 
			
		||||
contexts:
 | 
			
		||||
- context:
 | 
			
		||||
@@ -852,6 +858,7 @@ users:
 | 
			
		||||
      - arg-1
 | 
			
		||||
      - arg-2
 | 
			
		||||
      command: foo-command
 | 
			
		||||
      provideClusterInfo: true
 | 
			
		||||
`
 | 
			
		||||
	tmpfile, err := ioutil.TempFile("", "kubeconfig")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -868,7 +875,16 @@ users:
 | 
			
		||||
	if !reflect.DeepEqual(config.ExecProvider.Args, []string{"arg-1", "arg-2"}) {
 | 
			
		||||
		t.Errorf("Got args %v when they should be %v\n", config.ExecProvider.Args, []string{"arg-1", "arg-2"})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !config.ExecProvider.ProvideClusterInfo {
 | 
			
		||||
		t.Error("Wanted provider cluster info to be true")
 | 
			
		||||
	}
 | 
			
		||||
	want := &runtime.Unknown{
 | 
			
		||||
		Raw:         []byte(`{"audience":"foo","other":"bar"}`),
 | 
			
		||||
		ContentType: "application/json",
 | 
			
		||||
	}
 | 
			
		||||
	if !reflect.DeepEqual(config.ExecProvider.Config, want) {
 | 
			
		||||
		t.Errorf("Got config %v when it should be %v\n", config.ExecProvider.Config, want)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCleanANSIEscapeCodes(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user