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 }