diff --git a/api/api-rules/violation_exceptions.list b/api/api-rules/violation_exceptions.list index 6f9110e99c9..fa0ccc7e902 100644 --- a/api/api-rules/violation_exceptions.list +++ b/api/api-rules/violation_exceptions.list @@ -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 diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/types.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/types.go index 6fb53cecf94..bdae85b1e47 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/types.go +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/types.go @@ -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 +} diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/BUILD b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/BUILD index d3d66e5d2f4..8ad512b7cc8 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/BUILD +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/BUILD @@ -8,6 +8,7 @@ load( go_library( name = "go_default_library", srcs = [ + "conversion.go", "doc.go", "register.go", "types.go", diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/conversion.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/conversion.go new file mode 100644 index 00000000000..572e049f81d --- /dev/null +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/conversion.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) +} diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/types.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/types.go index c714e2457ac..1080fddb7ed 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/types.go +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/types.go @@ -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, diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/zz_generated.conversion.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/zz_generated.conversion.go index 461c20b2982..b0e503af493 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/zz_generated.conversion.go +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1/zz_generated.conversion.go @@ -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 diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/conversion.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/conversion.go index f543806ac95..441b7c44bd9 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/conversion.go +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/conversion.go @@ -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) } diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/types.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/types.go index d6e267452e9..4c3cbc8b2bc 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/types.go +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/types.go @@ -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"` +} diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.conversion.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.conversion.go index 0e533e46576..90f7935fefe 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.conversion.go +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.conversion.go @@ -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 } diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.deepcopy.go index 736b8cf00dd..3a72ece0c6f 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/zz_generated.deepcopy.go @@ -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 } diff --git a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/zz_generated.deepcopy.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/zz_generated.deepcopy.go index c568a6fc8a4..045b07f5b05 100644 --- a/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/zz_generated.deepcopy.go @@ -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 } diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go index 627bb2de94b..08c59a8c2e5 100644 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go +++ b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go @@ -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 } @@ -171,9 +178,11 @@ func newAuthenticator(c *cache, config *api.ExecConfig) (*Authenticator, error) } a := &Authenticator{ - cmd: config.Command, - args: config.Args, - group: gv, + cmd: config.Command, + args: config.Args, + group: gv, + cluster: cluster, + provideClusterInfo: config.ProvideClusterInfo, installHint: config.InstallHint, sometimes: &sometimes{ @@ -200,10 +209,12 @@ func newAuthenticator(c *cache, config *api.ExecConfig) (*Authenticator, error) // The plugin input and output are defined by the API group client.authentication.k8s.io. type Authenticator struct { // Set by the config - cmd string - args []string - group schema.GroupVersion - env []string + cmd string + args []string + group schema.GroupVersion + env []string + cluster *clientauthentication.Cluster + provideClusterInfo bool // Used to avoid log spew by rate limiting install hint printing. We didn't do // this by interval based rate limiting alone since that way may have prevented @@ -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)) + 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...) diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go index 43b9c08701a..6aee2e25216 100644 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go +++ b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go @@ -115,8 +115,24 @@ func TestCacheKey(t *testing.T) { {Name: "5", Value: "6"}, {Name: "7", Value: "8"}, }, - APIVersion: "client.authentication.k8s.io/v1alpha1", + APIVersion: "client.authentication.k8s.io/v1alpha1", + ProvideClusterInfo: true, } + c1c := &clientauthentication.Cluster{ + Server: "foo", + 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"}, @@ -125,8 +141,24 @@ func TestCacheKey(t *testing.T) { {Name: "5", Value: "6"}, {Name: "7", Value: "8"}, }, - APIVersion: "client.authentication.k8s.io/v1alpha1", + APIVersion: "client.authentication.k8s.io/v1alpha1", + ProvideClusterInfo: true, } + c2c := &clientauthentication.Cluster{ + Server: "foo", + 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) } diff --git a/staging/src/k8s.io/client-go/rest/BUILD b/staging/src/k8s.io/client-go/rest/BUILD index 17d6b3e1656..e2607d93b13 100644 --- a/staging/src/k8s.io/client-go/rest/BUILD +++ b/staging/src/k8s.io/client-go/rest/BUILD @@ -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", diff --git a/staging/src/k8s.io/client-go/rest/config.go b/staging/src/k8s.io/client-go/rest/config.go index fe132342efe..c9d72288776 100644 --- a/staging/src/k8s.io/client-go/rest/config.go +++ b/staging/src/k8s.io/client-go/rest/config.go @@ -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 } diff --git a/staging/src/k8s.io/client-go/rest/config_test.go b/staging/src/k8s.io/client-go/rest/config_test.go index 1ccd14023c5..bd857e0adfa 100644 --- a/staging/src/k8s.io/client-go/rest/config_test.go +++ b/staging/src/k8s.io/client-go/rest/config_test.go @@ -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{} @@ -525,8 +535,9 @@ func TestConfigStringer(t *testing.T) { Config: map[string]string{"secret": "s3cr3t"}, }, ExecProvider: &clientcmdapi.ExecConfig{ - Args: []string{"secret"}, - Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}}, + 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")), }, }, } @@ -588,9 +601,11 @@ func TestConfigSprint(t *testing.T) { }, AuthConfigPersister: fakeAuthProviderConfigPersister{}, ExecProvider: &clientcmdapi.ExecConfig{ - Command: "sudo", - Args: []string{"secret"}, - Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}}, + 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, ) diff --git a/staging/src/k8s.io/client-go/rest/exec.go b/staging/src/k8s.io/client-go/rest/exec.go new file mode 100644 index 00000000000..5f3b43c55a8 --- /dev/null +++ b/staging/src/k8s.io/client-go/rest/exec.go @@ -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 +} diff --git a/staging/src/k8s.io/client-go/rest/exec_test.go b/staging/src/k8s.io/client-go/rest/exec_test.go new file mode 100644 index 00000000000..705b776aa2e --- /dev/null +++ b/staging/src/k8s.io/client-go/rest/exec_test.go @@ -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) + } + } +} diff --git a/staging/src/k8s.io/client-go/rest/testdata/ca.pem b/staging/src/k8s.io/client-go/rest/testdata/ca.pem new file mode 100644 index 00000000000..4c8eaeb7384 --- /dev/null +++ b/staging/src/k8s.io/client-go/rest/testdata/ca.pem @@ -0,0 +1 @@ +a CA bundle lives here \ No newline at end of file diff --git a/staging/src/k8s.io/client-go/rest/transport.go b/staging/src/k8s.io/client-go/rest/transport.go index 450edc6edde..87792750ad3 100644 --- a/staging/src/k8s.io/client-go/rest/transport.go +++ b/staging/src/k8s.io/client-go/rest/transport.go @@ -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 } diff --git a/staging/src/k8s.io/client-go/tools/auth/BUILD b/staging/src/k8s.io/client-go/tools/auth/BUILD index 1f44e2db68b..1ecf2715dfd 100644 --- a/staging/src/k8s.io/client-go/tools/auth/BUILD +++ b/staging/src/k8s.io/client-go/tools/auth/BUILD @@ -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"], ) diff --git a/staging/src/k8s.io/client-go/tools/auth/exec/BUILD b/staging/src/k8s.io/client-go/tools/auth/exec/BUILD new file mode 100644 index 00000000000..bcc6bfb0174 --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/auth/exec/BUILD @@ -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"], +) diff --git a/staging/src/k8s.io/client-go/tools/auth/exec/exec.go b/staging/src/k8s.io/client-go/tools/auth/exec/exec.go new file mode 100644 index 00000000000..246de2ef103 --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/auth/exec/exec.go @@ -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 +} diff --git a/staging/src/k8s.io/client-go/tools/auth/exec/exec_test.go b/staging/src/k8s.io/client-go/tools/auth/exec/exec_test.go new file mode 100644 index 00000000000..fd1b1b0b5dd --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/auth/exec/exec_test.go @@ -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 +} diff --git a/staging/src/k8s.io/client-go/tools/auth/exec/types_test.go b/staging/src/k8s.io/client-go/tools/auth/exec/types_test.go new file mode 100644 index 00000000000..e4b95a34643 --- /dev/null +++ b/staging/src/k8s.io/client-go/tools/auth/exec/types_test.go @@ -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, + ) + } + } + } +} diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go b/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go index 829424dcf3f..d3d39530497 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/api/types.go @@ -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 diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go b/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go index 0395f860f36..70d8c20d5e9 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/types.go @@ -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 diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.conversion.go b/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.conversion.go index bf9eaeca322..26e96529d38 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.conversion.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.conversion.go @@ -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 } diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/api/zz_generated.deepcopy.go b/staging/src/k8s.io/client-go/tools/clientcmd/api/zz_generated.deepcopy.go index 3240a7a98dc..a04de6260ba 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/api/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/api/zz_generated.deepcopy.go @@ -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 } diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/client_config.go b/staging/src/k8s.io/client-go/tools/clientcmd/client_config.go index 690afce0cd6..8a8b80363b4 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/client_config.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/client_config.go @@ -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 diff --git a/staging/src/k8s.io/client-go/tools/clientcmd/client_config_test.go b/staging/src/k8s.io/client-go/tools/clientcmd/client_config_test.go index 0819ed53477..7ed3a1d7c2e 100644 --- a/staging/src/k8s.io/client-go/tools/clientcmd/client_config_test.go +++ b/staging/src/k8s.io/client-go/tools/clientcmd/client_config_test.go @@ -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) {