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