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