k8s.io/client-go@v0.22.2/rest/exec_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package rest
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"net"
    23  	"net/http"
    24  	"net/url"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	fuzz "github.com/google/gofuzz"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/client-go/pkg/apis/clientauthentication"
    32  	clientauthenticationapi "k8s.io/client-go/pkg/apis/clientauthentication"
    33  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    34  	"k8s.io/client-go/transport"
    35  	"k8s.io/client-go/util/flowcontrol"
    36  )
    37  
    38  func TestConfigToExecCluster(t *testing.T) {
    39  	t.Parallel()
    40  
    41  	const proxyURL = "https://some-proxy-url.com/tuna/fish"
    42  	proxy := func(r *http.Request) (*url.URL, error) {
    43  		return url.Parse(proxyURL)
    44  	}
    45  
    46  	tests := []struct {
    47  		name            string
    48  		in              Config
    49  		wantOut         clientauthenticationapi.Cluster
    50  		wantErrorPrefix string
    51  	}{
    52  		{
    53  			name: "CA data from memory",
    54  			in: Config{
    55  				ExecProvider: &clientcmdapi.ExecConfig{
    56  					ProvideClusterInfo: true,
    57  					Config: &runtime.Unknown{
    58  						Raw: []byte("stuff"),
    59  					},
    60  				},
    61  				Host: "some-host",
    62  				TLSClientConfig: TLSClientConfig{
    63  					ServerName: "some-server-name",
    64  					Insecure:   true,
    65  					CAData:     []byte("some-ca-data"),
    66  				},
    67  				Proxy: proxy,
    68  			},
    69  			wantOut: clientauthenticationapi.Cluster{
    70  				Server:                   "some-host",
    71  				TLSServerName:            "some-server-name",
    72  				InsecureSkipTLSVerify:    true,
    73  				CertificateAuthorityData: []byte("some-ca-data"),
    74  				ProxyURL:                 proxyURL,
    75  				Config: &runtime.Unknown{
    76  					Raw: []byte("stuff"),
    77  				},
    78  			},
    79  		},
    80  		{
    81  			name: "CA data from file",
    82  			in: Config{
    83  				ExecProvider: &clientcmdapi.ExecConfig{
    84  					ProvideClusterInfo: true,
    85  					Config: &runtime.Unknown{
    86  						Raw: []byte("stuff"),
    87  					},
    88  				},
    89  				Host: "some-host",
    90  				TLSClientConfig: TLSClientConfig{
    91  					ServerName: "some-server-name",
    92  					Insecure:   true,
    93  					CAFile:     "testdata/ca.pem",
    94  				},
    95  				Proxy: proxy,
    96  			},
    97  			wantOut: clientauthenticationapi.Cluster{
    98  				Server:                   "some-host",
    99  				TLSServerName:            "some-server-name",
   100  				InsecureSkipTLSVerify:    true,
   101  				CertificateAuthorityData: []byte("a CA bundle lives here"),
   102  				ProxyURL:                 proxyURL,
   103  				Config: &runtime.Unknown{
   104  					Raw: []byte("stuff"),
   105  				},
   106  			},
   107  		},
   108  		{
   109  			name: "no CA data",
   110  			in: Config{
   111  				ExecProvider: &clientcmdapi.ExecConfig{
   112  					ProvideClusterInfo: true,
   113  				},
   114  				TLSClientConfig: TLSClientConfig{
   115  					CAFile: "this-file-does-not-exist",
   116  				},
   117  			},
   118  			wantErrorPrefix: "failed to load CA bundle for execProvider: ",
   119  		},
   120  		{
   121  			name: "nil proxy",
   122  			in: Config{
   123  				ExecProvider: &clientcmdapi.ExecConfig{
   124  					ProvideClusterInfo: true,
   125  					Config: &runtime.Unknown{
   126  						Raw: []byte("stuff"),
   127  					},
   128  				},
   129  				Host: "some-host",
   130  				TLSClientConfig: TLSClientConfig{
   131  					ServerName: "some-server-name",
   132  					Insecure:   true,
   133  					CAFile:     "testdata/ca.pem",
   134  				},
   135  			},
   136  			wantOut: clientauthenticationapi.Cluster{
   137  				Server:                   "some-host",
   138  				TLSServerName:            "some-server-name",
   139  				InsecureSkipTLSVerify:    true,
   140  				CertificateAuthorityData: []byte("a CA bundle lives here"),
   141  				Config: &runtime.Unknown{
   142  					Raw: []byte("stuff"),
   143  				},
   144  			},
   145  		},
   146  		{
   147  			name: "bad proxy",
   148  			in: Config{
   149  				ExecProvider: &clientcmdapi.ExecConfig{
   150  					ProvideClusterInfo: true,
   151  				},
   152  				Proxy: func(_ *http.Request) (*url.URL, error) {
   153  					return nil, errors.New("some proxy error")
   154  				},
   155  			},
   156  			wantErrorPrefix: "failed to get proxy URL for execProvider: some proxy error",
   157  		},
   158  		{
   159  			name: "proxy returns nil",
   160  			in: Config{
   161  				ExecProvider: &clientcmdapi.ExecConfig{
   162  					ProvideClusterInfo: true,
   163  				},
   164  				Proxy: func(_ *http.Request) (*url.URL, error) {
   165  					return nil, nil
   166  				},
   167  				Host: "some-host",
   168  				TLSClientConfig: TLSClientConfig{
   169  					ServerName: "some-server-name",
   170  					Insecure:   true,
   171  					CAFile:     "testdata/ca.pem",
   172  				},
   173  			},
   174  			wantOut: clientauthenticationapi.Cluster{
   175  				Server:                   "some-host",
   176  				TLSServerName:            "some-server-name",
   177  				InsecureSkipTLSVerify:    true,
   178  				CertificateAuthorityData: []byte("a CA bundle lives here"),
   179  			},
   180  		},
   181  		{
   182  			name: "invalid config host",
   183  			in: Config{
   184  				ExecProvider: &clientcmdapi.ExecConfig{
   185  					ProvideClusterInfo: true,
   186  				},
   187  				Proxy: func(_ *http.Request) (*url.URL, error) {
   188  					return nil, nil
   189  				},
   190  				Host: "invalid-config-host\n",
   191  			},
   192  			wantErrorPrefix: "failed to create proxy URL request for execProvider: ",
   193  		},
   194  	}
   195  	for _, test := range tests {
   196  		test := test
   197  		t.Run(test.name, func(t *testing.T) {
   198  			out, err := ConfigToExecCluster(&test.in)
   199  			if test.wantErrorPrefix != "" {
   200  				if err == nil {
   201  					t.Error("wanted error")
   202  				} else if !strings.HasPrefix(err.Error(), test.wantErrorPrefix) {
   203  					t.Errorf("wanted error prefix %q, got %q", test.wantErrorPrefix, err.Error())
   204  				}
   205  			} else if diff := cmp.Diff(&test.wantOut, out); diff != "" {
   206  				t.Errorf("unexpected returned cluster: -got, +want:\n %s", diff)
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  func TestConfigToExecClusterRoundtrip(t *testing.T) {
   213  	t.Parallel()
   214  
   215  	f := fuzz.New().NilChance(0.5).NumElements(1, 1)
   216  	f.Funcs(
   217  		func(r *runtime.Codec, f fuzz.Continue) {
   218  			codec := &fakeCodec{}
   219  			f.Fuzz(codec)
   220  			*r = codec
   221  		},
   222  		func(r *http.RoundTripper, f fuzz.Continue) {
   223  			roundTripper := &fakeRoundTripper{}
   224  			f.Fuzz(roundTripper)
   225  			*r = roundTripper
   226  		},
   227  		func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) {
   228  			*fn = fakeWrapperFunc
   229  		},
   230  		func(fn *transport.WrapperFunc, f fuzz.Continue) {
   231  			*fn = fakeWrapperFunc
   232  		},
   233  		func(r *runtime.NegotiatedSerializer, f fuzz.Continue) {
   234  			serializer := &fakeNegotiatedSerializer{}
   235  			f.Fuzz(serializer)
   236  			*r = serializer
   237  		},
   238  		func(r *flowcontrol.RateLimiter, f fuzz.Continue) {
   239  			limiter := &fakeLimiter{}
   240  			f.Fuzz(limiter)
   241  			*r = limiter
   242  		},
   243  		func(h *WarningHandler, f fuzz.Continue) {
   244  			*h = &fakeWarningHandler{}
   245  		},
   246  		// Authentication does not require fuzzer
   247  		func(r *AuthProviderConfigPersister, f fuzz.Continue) {},
   248  		func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) {
   249  			r.Config = map[string]string{}
   250  		},
   251  		func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
   252  			*r = fakeDialFunc
   253  		},
   254  		func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
   255  			*r = fakeProxyFunc
   256  		},
   257  		func(r *runtime.Object, f fuzz.Continue) {
   258  			unknown := &runtime.Unknown{}
   259  			f.Fuzz(unknown)
   260  			*r = unknown
   261  		},
   262  	)
   263  	for i := 0; i < 100; i++ {
   264  		expected := &Config{}
   265  		f.Fuzz(expected)
   266  
   267  		// This is the list of known fields that this roundtrip doesn't care about. We should add new
   268  		// fields to this list if we don't want to roundtrip them on exec cluster conversion.
   269  		expected.APIPath = ""
   270  		expected.ContentConfig = ContentConfig{}
   271  		expected.Username = ""
   272  		expected.Password = ""
   273  		expected.BearerToken = ""
   274  		expected.BearerTokenFile = ""
   275  		expected.Impersonate = ImpersonationConfig{}
   276  		expected.AuthProvider = nil
   277  		expected.AuthConfigPersister = nil
   278  		expected.ExecProvider = &clientcmdapi.ExecConfig{} // ConfigToExecCluster assumes != nil.
   279  		expected.TLSClientConfig.CertFile = ""
   280  		expected.TLSClientConfig.KeyFile = ""
   281  		expected.TLSClientConfig.CAFile = ""
   282  		expected.TLSClientConfig.CertData = nil
   283  		expected.TLSClientConfig.KeyData = nil
   284  		expected.TLSClientConfig.NextProtos = nil
   285  		expected.UserAgent = ""
   286  		expected.DisableCompression = false
   287  		expected.Transport = nil
   288  		expected.WrapTransport = nil
   289  		expected.QPS = 0.0
   290  		expected.Burst = 0
   291  		expected.RateLimiter = nil
   292  		expected.WarningHandler = nil
   293  		expected.Timeout = 0
   294  		expected.Dial = nil
   295  
   296  		// Manually set URLs so we don't get an error when parsing these during the roundtrip.
   297  		if expected.Host != "" {
   298  			expected.Host = "https://some-server-url.com/tuna/fish"
   299  		}
   300  		if expected.Proxy != nil {
   301  			expected.Proxy = func(_ *http.Request) (*url.URL, error) {
   302  				return url.Parse("https://some-proxy-url.com/tuna/fish")
   303  			}
   304  		}
   305  
   306  		cluster, err := ConfigToExecCluster(expected)
   307  		if err != nil {
   308  			t.Fatal(err)
   309  		}
   310  
   311  		actual, err := ExecClusterToConfig(cluster)
   312  		if err != nil {
   313  			t.Fatal(err)
   314  		}
   315  
   316  		if actual.Proxy != nil {
   317  			actualURL, actualErr := actual.Proxy(nil)
   318  			expectedURL, expectedErr := expected.Proxy(nil)
   319  			if actualErr != nil {
   320  				t.Fatalf("failed to get url from actual proxy func: %s", actualErr.Error())
   321  			}
   322  			if expectedErr != nil {
   323  				t.Fatalf("failed to get url from expected proxy func: %s", actualErr.Error())
   324  			}
   325  			if diff := cmp.Diff(actualURL, expectedURL); diff != "" {
   326  				t.Fatal("we dropped the Config.Proxy field during conversion")
   327  			}
   328  		}
   329  		actual.Proxy = nil
   330  		expected.Proxy = nil
   331  
   332  		if actual.ExecProvider != nil {
   333  			t.Fatal("expected actual Config.ExecProvider field to be set to nil")
   334  		}
   335  		actual.ExecProvider = nil
   336  		expected.ExecProvider = nil
   337  
   338  		if diff := cmp.Diff(actual, expected); diff != "" {
   339  			t.Fatalf("we dropped some Config fields during roundtrip, -got, +want:\n %s", diff)
   340  		}
   341  	}
   342  }
   343  
   344  func TestExecClusterToConfigRoundtrip(t *testing.T) {
   345  	t.Parallel()
   346  
   347  	f := fuzz.New().NilChance(0.5).NumElements(1, 1)
   348  	f.Funcs(
   349  		func(r *runtime.Object, f fuzz.Continue) {
   350  			// We don't expect the clientauthentication.Cluster.Config to show up in the Config that
   351  			// comes back from the roundtrip, so just set it to nil.
   352  			*r = nil
   353  		},
   354  	)
   355  	for i := 0; i < 100; i++ {
   356  		expected := &clientauthentication.Cluster{}
   357  		f.Fuzz(expected)
   358  
   359  		// Manually set URLs so we don't get an error when parsing these during the roundtrip.
   360  		if expected.Server != "" {
   361  			expected.Server = "https://some-server-url.com/tuna/fish"
   362  		}
   363  		if expected.ProxyURL != "" {
   364  			expected.ProxyURL = "https://some-proxy-url.com/tuna/fish"
   365  		}
   366  
   367  		config, err := ExecClusterToConfig(expected)
   368  		if err != nil {
   369  			t.Fatal(err)
   370  		}
   371  
   372  		// ConfigToExecCluster assumes config.ExecProvider is not nil.
   373  		config.ExecProvider = &clientcmdapi.ExecConfig{}
   374  
   375  		actual, err := ConfigToExecCluster(config)
   376  		if err != nil {
   377  			t.Fatal(err)
   378  		}
   379  
   380  		if diff := cmp.Diff(actual, expected); diff != "" {
   381  			t.Fatalf("we dropped some Cluster fields during roundtrip: -got, +want:\n %s", diff)
   382  		}
   383  	}
   384  }