diff --git a/api/api-rules/violation_exceptions.list b/api/api-rules/violation_exceptions.list index cfe891e4ab7..5adee6a3dd3 100644 --- a/api/api-rules/violation_exceptions.list +++ b/api/api-rules/violation_exceptions.list @@ -466,6 +466,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/apiserver/pkg/util/webhook/authentication.go b/staging/src/k8s.io/apiserver/pkg/util/webhook/authentication.go index 7d1a67c5070..042879dade9 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/webhook/authentication.go +++ b/staging/src/k8s.io/apiserver/pkg/util/webhook/authentication.go @@ -246,7 +246,7 @@ func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Conf config.Password = configAuthInfo.Password } if configAuthInfo.Exec != nil { - config.Exec.ExecProvider = configAuthInfo.Exec.DeepCopy() + config.ExecProvider = configAuthInfo.Exec.DeepCopy() } if configAuthInfo.AuthProvider != nil { return nil, fmt.Errorf("auth provider not supported") 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 ec6f4f28cb5..d99ca818792 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 @@ -23,7 +23,7 @@ import ( // +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 @@ -38,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, @@ -51,9 +51,12 @@ type ExecCredentialSpec struct { // +optional Interactive bool - // Cluster contains information to allow an exec plugin to communicate - // with the kubernetes cluster being authenticated to. - Cluster Cluster + // 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. @@ -83,19 +86,32 @@ type Response struct { // 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 - // ServerName 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. + // 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 - ServerName string + 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 - CAData []byte + 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. // @@ -106,7 +122,7 @@ type Cluster struct { // cluster: // ... // extensions: - // - name: exec # reserved extension name for per cluster exec config + // - name: client.authentication.k8s.io/exec # reserved extension name for per cluster exec config // extension: // audience: 06e3fbd18de8 # arbitrary config // 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 index 07e3acc26de..572e049f81d 100644 --- 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 @@ -22,5 +22,6 @@ import ( ) 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/v1beta1/conversion.go b/staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1/conversion.go index 4bfc05f3d75..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 @@ -22,5 +22,7 @@ import ( ) func Convert_clientauthentication_ExecCredentialSpec_To_v1beta1_ExecCredentialSpec(in *clientauthentication.ExecCredentialSpec, out *ExecCredentialSpec, s conversion.Scope) error { + // 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 e575ded23f6..a3119f154ad 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 @@ -40,9 +40,12 @@ type ExecCredential struct { // ExecCredentialSpec holds request and runtime specific information provided by // the transport. type ExecCredentialSpec struct { - // Cluster contains information to allow an exec plugin to communicate - // with the kubernetes cluster being authenticated to. - Cluster Cluster `json:"cluster"` + // 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. @@ -64,19 +67,32 @@ type ExecCredentialStatus struct { // 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"` - // ServerName 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. + // 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 - ServerName string `json:"serverName,omitempty"` + 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 - CAData []byte `json:"caData,omitempty"` + 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. // @@ -87,7 +103,7 @@ type Cluster struct { // cluster: // ... // extensions: - // - name: exec # reserved extension name for per cluster exec config + // - name: client.authentication.k8s.io/exec # reserved extension name for per cluster exec config // extension: // audience: 06e3fbd18de8 # arbitrary config // 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 2432af55760..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 @@ -81,8 +81,10 @@ func RegisterConversions(s *runtime.Scheme) error { func autoConvert_v1beta1_Cluster_To_clientauthentication_Cluster(in *Cluster, out *clientauthentication.Cluster, s conversion.Scope) error { out.Server = in.Server - out.ServerName = in.ServerName - out.CAData = *(*[]byte)(unsafe.Pointer(&in.CAData)) + 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 } @@ -96,8 +98,10 @@ func Convert_v1beta1_Cluster_To_clientauthentication_Cluster(in *Cluster, out *c func autoConvert_clientauthentication_Cluster_To_v1beta1_Cluster(in *clientauthentication.Cluster, out *Cluster, s conversion.Scope) error { out.Server = in.Server - out.ServerName = in.ServerName - out.CAData = *(*[]byte)(unsafe.Pointer(&in.CAData)) + 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 } @@ -136,8 +140,14 @@ 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 err := Convert_v1beta1_Cluster_To_clientauthentication_Cluster(&in.Cluster, &out.Cluster, s); err != nil { - return err + 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 } @@ -150,8 +160,14 @@ 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 err := Convert_clientauthentication_Cluster_To_v1beta1_Cluster(&in.Cluster, &out.Cluster, s); err != nil { - return err + 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 5fe29d47518..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 @@ -27,8 +27,8 @@ import ( // 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.CAData != nil { - in, out := &in.CAData, &out.CAData + if in.CertificateAuthorityData != nil { + in, out := &in.CertificateAuthorityData, &out.CertificateAuthorityData *out = make([]byte, len(*in)) copy(*out, *in) } @@ -80,7 +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 - in.Cluster.DeepCopyInto(&out.Cluster) + 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 f0c23b2f2fc..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 @@ -27,8 +27,8 @@ import ( // 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.CAData != nil { - in, out := &in.CAData, &out.CAData + if in.CertificateAuthorityData != nil { + in, out := &in.CertificateAuthorityData, &out.CertificateAuthorityData *out = make([]byte, len(*in)) copy(*out, *in) } @@ -87,7 +87,11 @@ func (in *ExecCredentialSpec) DeepCopyInto(out *ExecCredentialSpec) { *out = new(Response) (*in).DeepCopyInto(*out) } - in.Cluster.DeepCopyInto(&out.Cluster) + 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 dcf7c3877e6..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,10 +87,10 @@ func newCache() *cache { var spewConfig = &spew.ConfigState{DisableMethods: true, Indent: " "} -func cacheKey(conf *api.ExecConfig, cluster clientauthentication.Cluster) string { +func cacheKey(conf *api.ExecConfig, cluster *clientauthentication.Cluster) string { key := struct { conf *api.ExecConfig - cluster clientauthentication.Cluster + cluster *clientauthentication.Cluster }{ conf: conf, cluster: cluster, @@ -162,11 +162,11 @@ func (s *sometimes) Do(f func()) { } // GetAuthenticator returns an exec-based plugin for providing client credentials. -func GetAuthenticator(config *api.ExecConfig, cluster clientauthentication.Cluster) (*Authenticator, error) { +func GetAuthenticator(config *api.ExecConfig, cluster *clientauthentication.Cluster) (*Authenticator, error) { return newAuthenticator(globalCache, config, cluster) } -func newAuthenticator(c *cache, config *api.ExecConfig, cluster clientauthentication.Cluster) (*Authenticator, error) { +func newAuthenticator(c *cache, config *api.ExecConfig, cluster *clientauthentication.Cluster) (*Authenticator, error) { key := cacheKey(config, cluster) if a, ok := c.get(key); ok { return a, nil @@ -178,10 +178,11 @@ func newAuthenticator(c *cache, config *api.ExecConfig, cluster clientauthentica } a := &Authenticator{ - cmd: config.Command, - args: config.Args, - group: gv, - cluster: cluster, + cmd: config.Command, + args: config.Args, + group: gv, + cluster: cluster, + provideClusterInfo: config.ProvideClusterInfo, installHint: config.InstallHint, sometimes: &sometimes{ @@ -208,11 +209,12 @@ func newAuthenticator(c *cache, config *api.ExecConfig, cluster clientauthentica // The plugin input and output are defined by the API group client.authentication.k8s.io. type Authenticator struct { // Set by the config - cmd string - args []string - group schema.GroupVersion - env []string - cluster clientauthentication.Cluster + cmd string + args []string + group schema.GroupVersion + env []string + cluster *clientauthentication.Cluster + provideClusterInfo bool // Used to avoid log spew by rate limiting install hint printing. We didn't do // this by interval based rate limiting alone since that way may have prevented @@ -374,9 +376,11 @@ func (a *Authenticator) refreshCredsLocked(r *clientauthentication.Response) err Spec: clientauthentication.ExecCredentialSpec{ Response: r, Interactive: a.interactive, - Cluster: a.cluster, }, } + if a.provideClusterInfo { + cred.Spec.Cluster = a.cluster + } env := append(a.environ(), a.env...) data, err := runtime.Encode(codecs.LegacyCodec(a.group), cred) 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 b575cfae3f6..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,12 +115,13 @@ func TestCacheKey(t *testing.T) { {Name: "5", Value: "6"}, {Name: "7", Value: "8"}, }, - APIVersion: "client.authentication.k8s.io/v1alpha1", + APIVersion: "client.authentication.k8s.io/v1alpha1", + ProvideClusterInfo: true, } - c1c := clientauthentication.Cluster{ - Server: "foo", - ServerName: "bar", - CAData: []byte("baz"), + c1c := &clientauthentication.Cluster{ + Server: "foo", + TLSServerName: "bar", + CertificateAuthorityData: []byte("baz"), Config: &runtime.Unknown{ TypeMeta: runtime.TypeMeta{ APIVersion: "", @@ -140,12 +141,13 @@ func TestCacheKey(t *testing.T) { {Name: "5", Value: "6"}, {Name: "7", Value: "8"}, }, - APIVersion: "client.authentication.k8s.io/v1alpha1", + APIVersion: "client.authentication.k8s.io/v1alpha1", + ProvideClusterInfo: true, } - c2c := clientauthentication.Cluster{ - Server: "foo", - ServerName: "bar", - CAData: []byte("baz"), + c2c := &clientauthentication.Cluster{ + Server: "foo", + TLSServerName: "bar", + CertificateAuthorityData: []byte("baz"), Config: &runtime.Unknown{ TypeMeta: runtime.TypeMeta{ APIVersion: "", @@ -166,10 +168,10 @@ func TestCacheKey(t *testing.T) { }, APIVersion: "client.authentication.k8s.io/v1alpha1", } - c3c := clientauthentication.Cluster{ - Server: "foo", - ServerName: "bar", - CAData: []byte("baz"), + c3c := &clientauthentication.Cluster{ + Server: "foo", + TLSServerName: "bar", + CertificateAuthorityData: []byte("baz"), Config: &runtime.Unknown{ TypeMeta: runtime.TypeMeta{ APIVersion: "", @@ -190,10 +192,10 @@ func TestCacheKey(t *testing.T) { }, APIVersion: "client.authentication.k8s.io/v1alpha1", } - c4c := clientauthentication.Cluster{ - Server: "foo", - ServerName: "bar", - CAData: []byte("baz"), + c4c := &clientauthentication.Cluster{ + Server: "foo", + TLSServerName: "bar", + CertificateAuthorityData: []byte("baz"), Config: &runtime.Unknown{ TypeMeta: runtime.TypeMeta{ APIVersion: "", @@ -205,10 +207,49 @@ func TestCacheKey(t *testing.T) { }, } + // c5/c5c should be the same as c4/c4c, except c5 has ProvideClusterInfo set to true. + c5 := &api.ExecConfig{ + Command: "foo-bar", + Args: []string{"1", "2"}, + Env: []api.ExecEnvVar{ + {Name: "3", Value: "4"}, + {Name: "5", Value: "6"}, + }, + APIVersion: "client.authentication.k8s.io/v1alpha1", + ProvideClusterInfo: true, + } + c5c := &clientauthentication.Cluster{ + Server: "foo", + TLSServerName: "bar", + CertificateAuthorityData: []byte("baz"), + Config: &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "", + Kind: "", + }, + Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"panda"}}`), + ContentEncoding: "", + ContentType: "application/json", + }, + } + + // c6 should be the same as c4, except c6 is passed with a nil cluster + c6 := &api.ExecConfig{ + Command: "foo-bar", + Args: []string{"1", "2"}, + Env: []api.ExecEnvVar{ + {Name: "3", Value: "4"}, + {Name: "5", Value: "6"}, + }, + APIVersion: "client.authentication.k8s.io/v1alpha1", + } + key1 := cacheKey(c1, c1c) key2 := cacheKey(c2, c2c) key3 := cacheKey(c3, c3c) key4 := cacheKey(c4, c4c) + key5 := cacheKey(c5, c5c) + key6 := cacheKey(c6, nil) if key1 != key2 { t.Error("key1 and key2 didn't match") } @@ -221,6 +262,12 @@ func TestCacheKey(t *testing.T) { if key3 == key4 { t.Error("key3 and key4 matched") } + if key4 == key5 { + t.Error("key3 and key4 matched") + } + if key6 == key4 { + t.Error("key6 and key4 matched") + } } func compJSON(t *testing.T, got, want []byte) { @@ -246,7 +293,7 @@ func TestRefreshCreds(t *testing.T) { name string config api.ExecConfig exitCode int - cluster clientauthentication.Cluster + cluster *clientauthentication.Cluster output string interactive bool response *clientauthentication.Response @@ -470,12 +517,7 @@ func TestRefreshCreds(t *testing.T) { wantInput: `{ "kind": "ExecCredential", "apiVersion": "client.authentication.k8s.io/v1beta1", - "spec": { - "cluster": { - "server": "", - "config": null - } - } + "spec": {} }`, output: `{ "kind": "ExecCredential", @@ -494,12 +536,7 @@ func TestRefreshCreds(t *testing.T) { wantInput: `{ "kind": "ExecCredential", "apiVersion": "client.authentication.k8s.io/v1beta1", - "spec": { - "cluster": { - "server": "", - "config": null - } - } + "spec": {} }`, output: `{ "kind": "ExecCredential", @@ -572,10 +609,10 @@ func TestRefreshCreds(t *testing.T) { config: api.ExecConfig{ APIVersion: "client.authentication.k8s.io/v1alpha1", }, - cluster: clientauthentication.Cluster{ - Server: "foo", - ServerName: "bar", - CAData: []byte("baz"), + cluster: &clientauthentication.Cluster{ + Server: "foo", + TLSServerName: "bar", + CertificateAuthorityData: []byte("baz"), Config: &runtime.Unknown{ TypeMeta: runtime.TypeMeta{ APIVersion: "", @@ -616,14 +653,15 @@ func TestRefreshCreds(t *testing.T) { wantCreds: credentials{token: "foo-bar"}, }, { - name: "beta-with-cluster-is-serialized", + name: "beta-with-cluster-and-provide-cluster-info-is-serialized", config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1beta1", + APIVersion: "client.authentication.k8s.io/v1beta1", + ProvideClusterInfo: true, }, - cluster: clientauthentication.Cluster{ - Server: "foo", - ServerName: "bar", - CAData: []byte("baz"), + cluster: &clientauthentication.Cluster{ + Server: "foo", + TLSServerName: "bar", + CertificateAuthorityData: []byte("baz"), Config: &runtime.Unknown{ TypeMeta: runtime.TypeMeta{ APIVersion: "", @@ -646,8 +684,8 @@ func TestRefreshCreds(t *testing.T) { "spec": { "cluster": { "server": "foo", - "serverName": "bar", - "caData": "YmF6", + "tls-server-name": "bar", + "certificate-authority-data": "YmF6", "config": { "apiVersion": "group/v1", "kind": "PluginConfig", @@ -667,6 +705,45 @@ func TestRefreshCreds(t *testing.T) { }`, wantCreds: credentials{token: "foo-bar"}, }, + { + name: "beta-with-cluster-and-without-provide-cluster-info-is-not-serialized", + config: api.ExecConfig{ + APIVersion: "client.authentication.k8s.io/v1beta1", + }, + cluster: &clientauthentication.Cluster{ + Server: "foo", + TLSServerName: "bar", + CertificateAuthorityData: []byte("baz"), + Config: &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "", + Kind: "", + }, + Raw: []byte(`{"apiVersion":"group/v1","kind":"PluginConfig","spec":{"audience":"snorlax"}}`), + ContentEncoding: "", + ContentType: "application/json", + }, + }, + response: &clientauthentication.Response{ + Header: map[string][]string{ + "WWW-Authenticate": {`Basic realm="Access to the staging site", charset="UTF-8"`}, + }, + Code: 401, + }, + wantInput: `{ + "kind":"ExecCredential", + "apiVersion":"client.authentication.k8s.io/v1beta1", + "spec": {} + }`, + output: `{ + "kind": "ExecCredential", + "apiVersion": "client.authentication.k8s.io/v1beta1", + "status": { + "token": "foo-bar" + } + }`, + wantCreds: credentials{token: "foo-bar"}, + }, } for _, test := range tests { @@ -763,7 +840,7 @@ func TestRoundTripper(t *testing.T) { Command: "./testdata/test-plugin.sh", APIVersion: "client.authentication.k8s.io/v1alpha1", } - a, err := newAuthenticator(newCache(), &c, clientauthentication.Cluster{}) + a, err := newAuthenticator(newCache(), &c, nil) if err != nil { t.Fatal(err) } @@ -849,7 +926,7 @@ func TestTokenPresentCancelsExecAction(t *testing.T) { a, err := newAuthenticator(newCache(), &api.ExecConfig{ Command: "./testdata/test-plugin.sh", APIVersion: "client.authentication.k8s.io/v1alpha1", - }) + }, nil) if err != nil { t.Fatal(err) } @@ -888,7 +965,7 @@ func TestTLSCredentials(t *testing.T) { a, err := newAuthenticator(newCache(), &api.ExecConfig{ Command: "./testdata/test-plugin.sh", APIVersion: "client.authentication.k8s.io/v1alpha1", - }, clientauthentication.Cluster{}) + }, nil) if err != nil { t.Fatal(err) } @@ -978,7 +1055,7 @@ func TestConcurrentUpdateTransportConfig(t *testing.T) { Command: "./testdata/test-plugin.sh", APIVersion: "client.authentication.k8s.io/v1alpha1", } - a, err := newAuthenticator(newCache(), &c, clientauthentication.Cluster{}) + a, err := newAuthenticator(newCache(), &c, nil) if err != nil { t.Fatal(err) } @@ -1045,7 +1122,7 @@ func TestInstallHintRateLimit(t *testing.T) { APIVersion: "client.authentication.k8s.io/v1alpha1", InstallHint: "some install hint", } - a, err := newAuthenticator(newCache(), &c) + a, err := newAuthenticator(newCache(), &c, nil) if err != nil { t.Fatal(err) } diff --git a/staging/src/k8s.io/client-go/rest/BUILD b/staging/src/k8s.io/client-go/rest/BUILD index caf7eaa2b25..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", diff --git a/staging/src/k8s.io/client-go/rest/config.go b/staging/src/k8s.io/client-go/rest/config.go index 654cd1f9f44..c9d72288776 100644 --- a/staging/src/k8s.io/client-go/rest/config.go +++ b/staging/src/k8s.io/client-go/rest/config.go @@ -87,7 +87,7 @@ type Config struct { AuthConfigPersister AuthProviderConfigPersister // Exec-based authentication provider. - Exec Exec + ExecProvider *clientcmdapi.ExecConfig // TLSClientConfig contains settings to enable transport layer security TLSClientConfig @@ -192,40 +192,12 @@ func (c *Config) String() string { if cc.AuthConfigPersister != nil { cc.AuthConfigPersister = sanitizedAuthConfigPersister{cc.AuthConfigPersister} } - if cc.Exec.Config != nil { - cc.Exec.Config = sanitizedObject{Object: cc.Exec.Config} + if cc.ExecProvider != nil && cc.ExecProvider.Config != nil { + cc.ExecProvider.Config = sanitizedObject{Object: cc.ExecProvider.Config} } return fmt.Sprintf("%#v", cc) } -// Exec plugin authentication provider. -type Exec struct { - // ExecProvider provides the config needed to execute the exec plugin. - ExecProvider *clientcmdapi.ExecConfig - - // 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: 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. - Config runtime.Object -} - // ImpersonationConfig has all the available impersonation options type ImpersonationConfig struct { // UserName is the username to impersonate on each request. @@ -627,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, @@ -642,10 +614,7 @@ func CopyConfig(config *Config) *Config { }, AuthProvider: config.AuthProvider, AuthConfigPersister: config.AuthConfigPersister, - Exec: Exec{ - ExecProvider: config.Exec.ExecProvider, - Config: config.Exec.Config, - }, + ExecProvider: config.ExecProvider, TLSClientConfig: TLSClientConfig{ Insecure: config.TLSClientConfig.Insecure, ServerName: config.TLSClientConfig.ServerName, @@ -669,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 a691b13f06c..bd857e0adfa 100644 --- a/staging/src/k8s.io/client-go/rest/config_test.go +++ b/staging/src/k8s.io/client-go/rest/config_test.go @@ -358,8 +358,7 @@ func TestAnonymousConfig(t *testing.T) { expected.Password = "" expected.AuthProvider = nil expected.AuthConfigPersister = nil - expected.Exec.ExecProvider = nil - expected.Exec.Config = nil + expected.ExecProvider = nil expected.TLSClientConfig.CertData = nil expected.TLSClientConfig.CertFile = "" expected.TLSClientConfig.KeyData = nil @@ -535,12 +534,10 @@ func TestConfigStringer(t *testing.T) { AuthProvider: &clientcmdapi.AuthProviderConfig{ Config: map[string]string{"secret": "s3cr3t"}, }, - Exec: Exec{ - ExecProvider: &clientcmdapi.ExecConfig{ - Args: []string{"secret"}, - Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}}, - }, - Config: &runtime.Unknown{Raw: []byte("super secret password")}, + ExecProvider: &clientcmdapi.ExecConfig{ + Args: []string{"secret"}, + Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}}, + Config: &runtime.Unknown{Raw: []byte("here is some config data")}, }, }, expectContent: []string{ @@ -559,7 +556,7 @@ func TestConfigStringer(t *testing.T) { formatBytes([]byte("fake key")), "secret", "s3cr3t", - "super secret password", + "here is some config data", formatBytes([]byte("super secret password")), }, }, @@ -603,13 +600,12 @@ func TestConfigSprint(t *testing.T) { Config: map[string]string{"secret": "s3cr3t"}, }, AuthConfigPersister: fakeAuthProviderConfigPersister{}, - Exec: Exec{ - ExecProvider: &clientcmdapi.ExecConfig{ - Command: "sudo", - Args: []string{"secret"}, - Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}}, - }, - Config: &runtime.Unknown{Raw: []byte("super secret password")}, + ExecProvider: &clientcmdapi.ExecConfig{ + 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", @@ -630,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 ---), Exec:rest.Exec{ExecProvider:api.AuthProviderConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: ""}, 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)}`, + `&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 22407273685..87792750ad3 100644 --- a/staging/src/k8s.io/client-go/rest/transport.go +++ b/staging/src/k8s.io/client-go/rest/transport.go @@ -19,7 +19,6 @@ package rest import ( "crypto/tls" "errors" - "fmt" "net/http" "k8s.io/client-go/pkg/apis/clientauthentication" @@ -91,22 +90,20 @@ func (c *Config) TransportConfig() (*transport.Config, error) { Proxy: c.Proxy, } - if c.Exec.ExecProvider != nil && c.AuthProvider != nil { + if c.ExecProvider != nil && c.AuthProvider != nil { return nil, errors.New("execProvider and authProvider cannot be used in combination") } - if c.Exec.ExecProvider != nil { - caData, err := dataFromSliceOrFile(c.CAData, c.CAFile) - if err != nil { - return nil, fmt.Errorf("failed to load CA bundle for execProvider: %v", err) + if c.ExecProvider != nil { + var cluster *clientauthentication.Cluster + if c.ExecProvider.ProvideClusterInfo { + var err error + cluster, err = ConfigToExecCluster(c) + if err != nil { + return nil, err + } } - cluster := clientauthentication.Cluster{ - Server: c.Host, - ServerName: c.TLSClientConfig.ServerName, - CAData: caData, - Config: c.Exec.Config, - } - provider, err := exec.GetAuthenticator(c.Exec.ExecProvider, cluster) + provider, err := exec.GetAuthenticator(c.ExecProvider, cluster) if err != nil { return nil, err } 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 361ad43d7a9..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 @@ -269,9 +274,9 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI mergedConfig.AuthConfigPersister = persistAuthConfig } if configAuthInfo.Exec != nil { - mergedConfig.Exec.ExecProvider = configAuthInfo.Exec + mergedConfig.ExecProvider = configAuthInfo.Exec mergedConfig.ExecProvider.InstallHint = cleanANSIEscapeCodes(mergedConfig.ExecProvider.InstallHint) - mergedConfig.Exec.Config = configClusterInfo.Extensions["exec"] // this key is reserved in the extensions list for exec plugin config + mergedConfig.ExecProvider.Config = configClusterInfo.Extensions[clusterExtensionKey] } // if there still isn't enough information to authenticate the user, try prompting @@ -314,7 +319,7 @@ func canIdentifyUser(config restclient.Config) bool { (len(config.CertFile) > 0 || len(config.CertData) > 0) || len(config.BearerToken) > 0 || config.AuthProvider != nil || - config.Exec.ExecProvider != nil + config.ExecProvider != nil } // cleanANSIEscapeCodes takes an arbitrary string and ensures that there are no 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 08cf75d83e6..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 @@ -836,7 +836,7 @@ clusters: - cluster: server: https://localhost:8080 extensions: - - name: exec + - name: client.authentication.k8s.io/exec extension: audience: foo other: bar @@ -858,6 +858,7 @@ users: - arg-1 - arg-2 command: foo-command + provideClusterInfo: true ` tmpfile, err := ioutil.TempFile("", "kubeconfig") if err != nil { @@ -871,15 +872,18 @@ users: if err != nil { t.Error(err) } - if !reflect.DeepEqual(config.Exec.ExecProvider.Args, []string{"arg-1", "arg-2"}) { - t.Errorf("Got args %v when they should be %v\n", config.Exec.ExecProvider.Args, []string{"arg-1", "arg-2"}) + 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.Exec.Config, want) { - t.Errorf("Got config %v when it should be %v\n", config.Exec.Config, want) + if !reflect.DeepEqual(config.ExecProvider.Config, want) { + t.Errorf("Got config %v when it should be %v\n", config.ExecProvider.Config, want) } }