@@ -20,15 +20,19 @@ import (
20
20
"bytes"
21
21
"context"
22
22
"crypto/rand"
23
+ "crypto/tls"
23
24
"io"
24
25
"net/http"
25
26
"net/http/httptest"
26
27
"net/url"
28
+ "sync/atomic"
27
29
"testing"
28
30
"time"
29
31
30
32
"github.com/stretchr/testify/assert"
31
33
"github.com/stretchr/testify/require"
34
+ "k8s.io/apimachinery/pkg/util/httpstream"
35
+ utilnettesting "k8s.io/apimachinery/pkg/util/net/testing"
32
36
"k8s.io/apimachinery/pkg/util/remotecommand"
33
37
"k8s.io/apimachinery/pkg/util/wait"
34
38
"k8s.io/client-go/rest"
@@ -165,6 +169,178 @@ func TestFallbackClient_SPDYSecondarySucceeds(t *testing.T) {
165
169
}
166
170
}
167
171
172
+ // localhostCert was generated from crypto/tls/generate_cert.go with the following command:
173
+ //
174
+ // go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
175
+ var localhostCert = []byte (`-----BEGIN CERTIFICATE-----
176
+ MIIDGTCCAgGgAwIBAgIRALL5AZcefF4kkYV1SEG6YrMwDQYJKoZIhvcNAQELBQAw
177
+ EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
178
+ MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP
179
+ ADCCAQoCggEBALQ/FHcyVwdFHxARbbD2KBtDUT7Eni+8ioNdjtGcmtXqBv45EC1C
180
+ JOqqGJTroFGJ6Q9kQIZ9FqH5IJR2fOOJD9kOTueG4Vt1JY1rj1Kbpjefu8XleZ5L
181
+ SBwIWVnN/lEsEbuKmj7N2gLt5AH3zMZiBI1mg1u9Z5ZZHYbCiTpBrwsq6cTlvR9g
182
+ dyo1YkM5hRESCzsrL0aUByoo0qRMD8ZsgANJwgsiO0/M6idbxDwv1BnGwGmRYvOE
183
+ Hxpy3v0Jg7GJYrvnpnifJTs4nw91N5X9pXxR7FFzi/6HTYDWRljvTb0w6XciKYAz
184
+ bWZ0+cJr5F7wB7ovlbm7HrQIR7z7EIIu2d8CAwEAAaNoMGYwDgYDVR0PAQH/BAQD
185
+ AgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0R
186
+ BCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI
187
+ hvcNAQELBQADggEBAFPPWopNEJtIA2VFAQcqN6uJK+JVFOnjGRoCrM6Xgzdm0wxY
188
+ XCGjsxY5dl+V7KzdGqu858rCaq5osEBqypBpYAnS9C38VyCDA1vPS1PsN8SYv48z
189
+ DyBwj+7R2qar0ADBhnhWxvYO9M72lN/wuCqFKYMeFSnJdQLv3AsrrHe9lYqOa36s
190
+ 8wxSwVTFTYXBzljPEnSaaJMPqFD8JXaZK1ryJPkO5OsCNQNGtatNiWAf3DcmwHAT
191
+ MGYMzP0u4nw47aRz9shB8w+taPKHx2BVwE1m/yp3nHVioOjXqA1fwRQVGclCJSH1
192
+ D2iq3hWVHRENgjTjANBPICLo9AZ4JfN6PH19mnU=
193
+ -----END CERTIFICATE-----` )
194
+
195
+ // localhostKey is the private key for localhostCert.
196
+ var localhostKey = []byte (`-----BEGIN RSA PRIVATE KEY-----
197
+ MIIEogIBAAKCAQEAtD8UdzJXB0UfEBFtsPYoG0NRPsSeL7yKg12O0Zya1eoG/jkQ
198
+ LUIk6qoYlOugUYnpD2RAhn0WofkglHZ844kP2Q5O54bhW3UljWuPUpumN5+7xeV5
199
+ nktIHAhZWc3+USwRu4qaPs3aAu3kAffMxmIEjWaDW71nllkdhsKJOkGvCyrpxOW9
200
+ H2B3KjViQzmFERILOysvRpQHKijSpEwPxmyAA0nCCyI7T8zqJ1vEPC/UGcbAaZFi
201
+ 84QfGnLe/QmDsYliu+emeJ8lOzifD3U3lf2lfFHsUXOL/odNgNZGWO9NvTDpdyIp
202
+ gDNtZnT5wmvkXvAHui+VubsetAhHvPsQgi7Z3wIDAQABAoIBAGmw93IxjYCQ0ncc
203
+ kSKMJNZfsdtJdaxuNRZ0nNNirhQzR2h403iGaZlEpmdkhzxozsWcto1l+gh+SdFk
204
+ bTUK4MUZM8FlgO2dEqkLYh5BcMT7ICMZvSfJ4v21E5eqR68XVUqQKoQbNvQyxFk3
205
+ EddeEGdNrkb0GDK8DKlBlzAW5ep4gjG85wSTjR+J+muUv3R0BgLBFSuQnIDM/IMB
206
+ LWqsja/QbtB7yppe7jL5u8UCFdZG8BBKT9fcvFIu5PRLO3MO0uOI7LTc8+W1Xm23
207
+ uv+j3SY0+v+6POjK0UlJFFi/wkSPTFIfrQO1qFBkTDQHhQ6q/7GnILYYOiGbIRg2
208
+ NNuP52ECgYEAzXEoy50wSYh8xfFaBuxbm3ruuG2W49jgop7ZfoFrPWwOQKAZS441
209
+ VIwV4+e5IcA6KkuYbtGSdTYqK1SMkgnUyD/VevwAqH5TJoEIGu0pDuKGwVuwqioZ
210
+ frCIAV5GllKyUJ55VZNbRr2vY2fCsWbaCSCHETn6C16DNuTCe5C0JBECgYEA4JqY
211
+ 5GpNbMG8fOt4H7hU0Fbm2yd6SHJcQ3/9iimef7xG6ajxsYrIhg1ft+3IPHMjVI0+
212
+ 9brwHDnWg4bOOx/VO4VJBt6Dm/F33bndnZRkuIjfSNpLM51P+EnRdaFVHOJHwKqx
213
+ uF69kihifCAG7YATgCveeXImzBUSyZUz9UrETu8CgYARNBimdFNG1RcdvEg9rC0/
214
+ p9u1tfecvNySwZqU7WF9kz7eSonTueTdX521qAHowaAdSpdJMGODTTXaywm6cPhQ
215
+ jIfj9JZZhbqQzt1O4+08Qdvm9TamCUB5S28YLjza+bHU7nBaqixKkDfPqzCyilpX
216
+ yVGGL8SwjwmN3zop/sQXAQKBgC0JMsESQ6YcDsRpnrOVjYQc+LtW5iEitTdfsaID
217
+ iGGKihmOI7B66IxgoCHMTws39wycKdSyADVYr5e97xpR3rrJlgQHmBIrz+Iow7Q2
218
+ LiAGaec8xjl6QK/DdXmFuQBKqyKJ14rljFODP4QuE9WJid94bGqjpf3j99ltznZP
219
+ 4J8HAoGAJb4eb4lu4UGwifDzqfAPzLGCoi0fE1/hSx34lfuLcc1G+LEu9YDKoOVJ
220
+ 9suOh0b5K/bfEy9KrVMBBriduvdaERSD8S3pkIQaitIz0B029AbE4FLFf9lKQpP2
221
+ KR8NJEkK99Vh/tew6jAMll70xFrE7aF8VLXJVE7w4sQzuvHxl9Q=
222
+ -----END RSA PRIVATE KEY-----
223
+ ` )
224
+
225
+ // See (https://github.com/kubernetes/kubernetes/issues/126134).
226
+ func TestFallbackClient_WebSocketHTTPSProxyCausesSPDYFallback (t * testing.T ) {
227
+ cert , err := tls .X509KeyPair (localhostCert , localhostKey )
228
+ if err != nil {
229
+ t .Errorf ("https (valid hostname): proxy_test: %v" , err )
230
+ }
231
+
232
+ var proxyCalled atomic.Int64
233
+ proxyHandler := utilnettesting .NewHTTPProxyHandler (t , func (req * http.Request ) bool {
234
+ proxyCalled .Add (1 )
235
+ return true
236
+ })
237
+ defer proxyHandler .Wait ()
238
+
239
+ proxyServer := httptest .NewUnstartedServer (proxyHandler )
240
+ proxyServer .TLS = & tls.Config {Certificates : []tls.Certificate {cert }}
241
+ proxyServer .StartTLS ()
242
+ defer proxyServer .Close () //nolint:errcheck
243
+
244
+ proxyLocation , err := url .Parse (proxyServer .URL )
245
+ require .NoError (t , err )
246
+
247
+ // Create fake SPDY server. Copy received STDIN data back onto STDOUT stream.
248
+ spdyServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
249
+ var stdin , stdout bytes.Buffer
250
+ ctx , err := createHTTPStreams (w , req , & StreamOptions {
251
+ Stdin : & stdin ,
252
+ Stdout : & stdout ,
253
+ })
254
+ if err != nil {
255
+ w .WriteHeader (http .StatusForbidden )
256
+ return
257
+ }
258
+ defer ctx .conn .Close () //nolint:errcheck
259
+ _ , err = io .Copy (ctx .stdoutStream , ctx .stdinStream )
260
+ if err != nil {
261
+ t .Fatalf ("error copying STDIN to STDOUT: %v" , err )
262
+ }
263
+ }))
264
+ defer spdyServer .Close () //nolint:errcheck
265
+
266
+ backendLocation , err := url .Parse (spdyServer .URL )
267
+ require .NoError (t , err )
268
+
269
+ clientConfig := & rest.Config {
270
+ Host : spdyServer .URL ,
271
+ TLSClientConfig : rest.TLSClientConfig {CAData : localhostCert },
272
+ Proxy : func (req * http.Request ) (* url.URL , error ) {
273
+ return proxyLocation , nil
274
+ },
275
+ }
276
+
277
+ // Websocket with https proxy will fail in dialing (falling back to SPDY).
278
+ websocketExecutor , err := NewWebSocketExecutor (clientConfig , "GET" , backendLocation .String ())
279
+ require .NoError (t , err )
280
+ spdyExecutor , err := NewSPDYExecutor (clientConfig , "POST" , backendLocation )
281
+ require .NoError (t , err )
282
+ // Fallback to spdyExecutor with websocket https proxy error; spdyExecutor succeeds against fake spdy server.
283
+ sawHTTPSProxyError := false
284
+ exec , err := NewFallbackExecutor (websocketExecutor , spdyExecutor , func (err error ) bool {
285
+ if httpstream .IsUpgradeFailure (err ) {
286
+ t .Errorf ("saw upgrade failure: %v" , err )
287
+ return true
288
+ }
289
+ if httpstream .IsHTTPSProxyError (err ) {
290
+ sawHTTPSProxyError = true
291
+ t .Logf ("saw https proxy error: %v" , err )
292
+ return true
293
+ }
294
+ return false
295
+ })
296
+ require .NoError (t , err )
297
+
298
+ // Generate random data, and set it up to stream on STDIN. The data will be
299
+ // returned on the STDOUT buffer.
300
+ randomSize := 1024 * 1024
301
+ randomData := make ([]byte , randomSize )
302
+ if _ , err := rand .Read (randomData ); err != nil {
303
+ t .Errorf ("unexpected error reading random data: %v" , err )
304
+ }
305
+ var stdout bytes.Buffer
306
+ options := & StreamOptions {
307
+ Stdin : bytes .NewReader (randomData ),
308
+ Stdout : & stdout ,
309
+ }
310
+ errorChan := make (chan error )
311
+ go func () {
312
+ errorChan <- exec .StreamWithContext (context .Background (), * options )
313
+ }()
314
+
315
+ select {
316
+ case <- time .After (wait .ForeverTestTimeout ):
317
+ t .Fatalf ("expect stream to be closed after connection is closed." )
318
+ case err := <- errorChan :
319
+ if err != nil {
320
+ t .Errorf ("unexpected error" )
321
+ }
322
+ }
323
+
324
+ data , err := io .ReadAll (bytes .NewReader (stdout .Bytes ()))
325
+ if err != nil {
326
+ t .Errorf ("error reading the stream: %v" , err )
327
+ return
328
+ }
329
+ // Check the random data sent on STDIN was the same returned on STDOUT.
330
+ if ! bytes .Equal (randomData , data ) {
331
+ t .Errorf ("unexpected data received: %d sent: %d" , len (data ), len (randomData ))
332
+ }
333
+
334
+ // Ensure the https proxy error was observed
335
+ if ! sawHTTPSProxyError {
336
+ t .Errorf ("expected to see https proxy error" )
337
+ }
338
+ // Ensure the proxy was called once
339
+ if e , a := int64 (1 ), proxyCalled .Load (); e != a {
340
+ t .Errorf ("expected %d proxy call, got %d" , e , a )
341
+ }
342
+ }
343
+
168
344
func TestFallbackClient_PrimaryAndSecondaryFail (t * testing.T ) {
169
345
// Create fake WebSocket server. Copy received STDIN data back onto STDOUT stream.
170
346
websocketServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
0 commit comments