mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +08:00 
			
		
		
		
	Bump github.com/gogo/googleapis to v1.3.2 Bump github.com/docker/cli to master Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
		
			
				
	
	
		
			336 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			336 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2015 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 spdy
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"crypto/tls"
 | 
						|
	"encoding/base64"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"net/http/httputil"
 | 
						|
	"net/url"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime/serializer"
 | 
						|
	"k8s.io/apimachinery/pkg/util/httpstream"
 | 
						|
	utilnet "k8s.io/apimachinery/pkg/util/net"
 | 
						|
	"k8s.io/apimachinery/third_party/forked/golang/netutil"
 | 
						|
)
 | 
						|
 | 
						|
// SpdyRoundTripper knows how to upgrade an HTTP request to one that supports
 | 
						|
// multiplexed streams. After RoundTrip() is invoked, Conn will be set
 | 
						|
// and usable. SpdyRoundTripper implements the UpgradeRoundTripper interface.
 | 
						|
type SpdyRoundTripper struct {
 | 
						|
	//tlsConfig holds the TLS configuration settings to use when connecting
 | 
						|
	//to the remote server.
 | 
						|
	tlsConfig *tls.Config
 | 
						|
 | 
						|
	/* TODO according to http://golang.org/pkg/net/http/#RoundTripper, a RoundTripper
 | 
						|
	   must be safe for use by multiple concurrent goroutines. If this is absolutely
 | 
						|
	   necessary, we could keep a map from http.Request to net.Conn. In practice,
 | 
						|
	   a client will create an http.Client, set the transport to a new insteace of
 | 
						|
	   SpdyRoundTripper, and use it a single time, so this hopefully won't be an issue.
 | 
						|
	*/
 | 
						|
	// conn is the underlying network connection to the remote server.
 | 
						|
	conn net.Conn
 | 
						|
 | 
						|
	// Dialer is the dialer used to connect.  Used if non-nil.
 | 
						|
	Dialer *net.Dialer
 | 
						|
 | 
						|
	// proxier knows which proxy to use given a request, defaults to http.ProxyFromEnvironment
 | 
						|
	// Used primarily for mocking the proxy discovery in tests.
 | 
						|
	proxier func(req *http.Request) (*url.URL, error)
 | 
						|
 | 
						|
	// followRedirects indicates if the round tripper should examine responses for redirects and
 | 
						|
	// follow them.
 | 
						|
	followRedirects bool
 | 
						|
	// requireSameHostRedirects restricts redirect following to only follow redirects to the same host
 | 
						|
	// as the original request.
 | 
						|
	requireSameHostRedirects bool
 | 
						|
}
 | 
						|
 | 
						|
var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{}
 | 
						|
var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{}
 | 
						|
var _ utilnet.Dialer = &SpdyRoundTripper{}
 | 
						|
 | 
						|
// NewRoundTripper creates a new SpdyRoundTripper that will use
 | 
						|
// the specified tlsConfig.
 | 
						|
func NewRoundTripper(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool) httpstream.UpgradeRoundTripper {
 | 
						|
	return NewSpdyRoundTripper(tlsConfig, followRedirects, requireSameHostRedirects)
 | 
						|
}
 | 
						|
 | 
						|
// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use
 | 
						|
// the specified tlsConfig. This function is mostly meant for unit tests.
 | 
						|
func NewSpdyRoundTripper(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool) *SpdyRoundTripper {
 | 
						|
	return &SpdyRoundTripper{
 | 
						|
		tlsConfig:                tlsConfig,
 | 
						|
		followRedirects:          followRedirects,
 | 
						|
		requireSameHostRedirects: requireSameHostRedirects,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during
 | 
						|
// proxying with a spdy roundtripper.
 | 
						|
func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config {
 | 
						|
	return s.tlsConfig
 | 
						|
}
 | 
						|
 | 
						|
// Dial implements k8s.io/apimachinery/pkg/util/net.Dialer.
 | 
						|
func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) {
 | 
						|
	conn, err := s.dial(req)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if err := req.Write(conn); err != nil {
 | 
						|
		conn.Close()
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return conn, nil
 | 
						|
}
 | 
						|
 | 
						|
// dial dials the host specified by req, using TLS if appropriate, optionally
 | 
						|
// using a proxy server if one is configured via environment variables.
 | 
						|
func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) {
 | 
						|
	proxier := s.proxier
 | 
						|
	if proxier == nil {
 | 
						|
		proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
 | 
						|
	}
 | 
						|
	proxyURL, err := proxier(req)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if proxyURL == nil {
 | 
						|
		return s.dialWithoutProxy(req.Context(), req.URL)
 | 
						|
	}
 | 
						|
 | 
						|
	// ensure we use a canonical host with proxyReq
 | 
						|
	targetHost := netutil.CanonicalAddr(req.URL)
 | 
						|
 | 
						|
	// proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support
 | 
						|
	proxyReq := http.Request{
 | 
						|
		Method: "CONNECT",
 | 
						|
		URL:    &url.URL{},
 | 
						|
		Host:   targetHost,
 | 
						|
	}
 | 
						|
 | 
						|
	if pa := s.proxyAuth(proxyURL); pa != "" {
 | 
						|
		proxyReq.Header = http.Header{}
 | 
						|
		proxyReq.Header.Set("Proxy-Authorization", pa)
 | 
						|
	}
 | 
						|
 | 
						|
	proxyDialConn, err := s.dialWithoutProxy(req.Context(), proxyURL)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	proxyClientConn := httputil.NewProxyClientConn(proxyDialConn, nil)
 | 
						|
	_, err = proxyClientConn.Do(&proxyReq)
 | 
						|
	if err != nil && err != httputil.ErrPersistEOF {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	rwc, _ := proxyClientConn.Hijack()
 | 
						|
 | 
						|
	if req.URL.Scheme != "https" {
 | 
						|
		return rwc, nil
 | 
						|
	}
 | 
						|
 | 
						|
	host, _, err := net.SplitHostPort(targetHost)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	tlsConfig := s.tlsConfig
 | 
						|
	switch {
 | 
						|
	case tlsConfig == nil:
 | 
						|
		tlsConfig = &tls.Config{ServerName: host}
 | 
						|
	case len(tlsConfig.ServerName) == 0:
 | 
						|
		tlsConfig = tlsConfig.Clone()
 | 
						|
		tlsConfig.ServerName = host
 | 
						|
	}
 | 
						|
 | 
						|
	tlsConn := tls.Client(rwc, tlsConfig)
 | 
						|
 | 
						|
	// need to manually call Handshake() so we can call VerifyHostname() below
 | 
						|
	if err := tlsConn.Handshake(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Return if we were configured to skip validation
 | 
						|
	if tlsConfig.InsecureSkipVerify {
 | 
						|
		return tlsConn, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if err := tlsConn.VerifyHostname(tlsConfig.ServerName); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return tlsConn, nil
 | 
						|
}
 | 
						|
 | 
						|
// dialWithoutProxy dials the host specified by url, using TLS if appropriate.
 | 
						|
func (s *SpdyRoundTripper) dialWithoutProxy(ctx context.Context, url *url.URL) (net.Conn, error) {
 | 
						|
	dialAddr := netutil.CanonicalAddr(url)
 | 
						|
 | 
						|
	if url.Scheme == "http" {
 | 
						|
		if s.Dialer == nil {
 | 
						|
			var d net.Dialer
 | 
						|
			return d.DialContext(ctx, "tcp", dialAddr)
 | 
						|
		} else {
 | 
						|
			return s.Dialer.DialContext(ctx, "tcp", dialAddr)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// TODO validate the TLSClientConfig is set up?
 | 
						|
	var conn *tls.Conn
 | 
						|
	var err error
 | 
						|
	if s.Dialer == nil {
 | 
						|
		conn, err = tls.Dial("tcp", dialAddr, s.tlsConfig)
 | 
						|
	} else {
 | 
						|
		conn, err = tls.DialWithDialer(s.Dialer, "tcp", dialAddr, s.tlsConfig)
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Return if we were configured to skip validation
 | 
						|
	if s.tlsConfig != nil && s.tlsConfig.InsecureSkipVerify {
 | 
						|
		return conn, nil
 | 
						|
	}
 | 
						|
 | 
						|
	host, _, err := net.SplitHostPort(dialAddr)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if s.tlsConfig != nil && len(s.tlsConfig.ServerName) > 0 {
 | 
						|
		host = s.tlsConfig.ServerName
 | 
						|
	}
 | 
						|
	err = conn.VerifyHostname(host)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return conn, nil
 | 
						|
}
 | 
						|
 | 
						|
// proxyAuth returns, for a given proxy URL, the value to be used for the Proxy-Authorization header
 | 
						|
func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string {
 | 
						|
	if proxyURL == nil || proxyURL.User == nil {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	credentials := proxyURL.User.String()
 | 
						|
	encodedAuth := base64.StdEncoding.EncodeToString([]byte(credentials))
 | 
						|
	return fmt.Sprintf("Basic %s", encodedAuth)
 | 
						|
}
 | 
						|
 | 
						|
// RoundTrip executes the Request and upgrades it. After a successful upgrade,
 | 
						|
// clients may call SpdyRoundTripper.Connection() to retrieve the upgraded
 | 
						|
// connection.
 | 
						|
func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
 | 
						|
	header := utilnet.CloneHeader(req.Header)
 | 
						|
	header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
 | 
						|
	header.Add(httpstream.HeaderUpgrade, HeaderSpdy31)
 | 
						|
 | 
						|
	var (
 | 
						|
		conn        net.Conn
 | 
						|
		rawResponse []byte
 | 
						|
		err         error
 | 
						|
	)
 | 
						|
 | 
						|
	if s.followRedirects {
 | 
						|
		conn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, req.URL, header, req.Body, s, s.requireSameHostRedirects)
 | 
						|
	} else {
 | 
						|
		clone := utilnet.CloneRequest(req)
 | 
						|
		clone.Header = header
 | 
						|
		conn, err = s.Dial(clone)
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	responseReader := bufio.NewReader(
 | 
						|
		io.MultiReader(
 | 
						|
			bytes.NewBuffer(rawResponse),
 | 
						|
			conn,
 | 
						|
		),
 | 
						|
	)
 | 
						|
 | 
						|
	resp, err := http.ReadResponse(responseReader, nil)
 | 
						|
	if err != nil {
 | 
						|
		if conn != nil {
 | 
						|
			conn.Close()
 | 
						|
		}
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	s.conn = conn
 | 
						|
 | 
						|
	return resp, nil
 | 
						|
}
 | 
						|
 | 
						|
// NewConnection validates the upgrade response, creating and returning a new
 | 
						|
// httpstream.Connection if there were no errors.
 | 
						|
func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) {
 | 
						|
	connectionHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderConnection))
 | 
						|
	upgradeHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderUpgrade))
 | 
						|
	if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
 | 
						|
		defer resp.Body.Close()
 | 
						|
		responseError := ""
 | 
						|
		responseErrorBytes, err := ioutil.ReadAll(resp.Body)
 | 
						|
		if err != nil {
 | 
						|
			responseError = "unable to read error from server response"
 | 
						|
		} else {
 | 
						|
			// TODO: I don't belong here, I should be abstracted from this class
 | 
						|
			if obj, _, err := statusCodecs.UniversalDecoder().Decode(responseErrorBytes, nil, &metav1.Status{}); err == nil {
 | 
						|
				if status, ok := obj.(*metav1.Status); ok {
 | 
						|
					return nil, &apierrors.StatusError{ErrStatus: *status}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			responseError = string(responseErrorBytes)
 | 
						|
			responseError = strings.TrimSpace(responseError)
 | 
						|
		}
 | 
						|
 | 
						|
		return nil, fmt.Errorf("unable to upgrade connection: %s", responseError)
 | 
						|
	}
 | 
						|
 | 
						|
	return NewClientConnection(s.conn)
 | 
						|
}
 | 
						|
 | 
						|
// statusScheme is private scheme for the decoding here until someone fixes the TODO in NewConnection
 | 
						|
var statusScheme = runtime.NewScheme()
 | 
						|
 | 
						|
// ParameterCodec knows about query parameters used with the meta v1 API spec.
 | 
						|
var statusCodecs = serializer.NewCodecFactory(statusScheme)
 | 
						|
 | 
						|
func init() {
 | 
						|
	statusScheme.AddUnversionedTypes(metav1.SchemeGroupVersion,
 | 
						|
		&metav1.Status{},
 | 
						|
	)
 | 
						|
}
 |