aboutsummaryrefslogtreecommitdiff
path: root/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
diff options
context:
space:
mode:
authorMatthew Heon <matthew.heon@gmail.com>2017-11-01 11:24:59 -0400
committerMatthew Heon <matthew.heon@gmail.com>2017-11-01 11:24:59 -0400
commita031b83a09a8628435317a03f199cdc18b78262f (patch)
treebc017a96769ce6de33745b8b0b1304ccf38e9df0 /vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
parent2b74391cd5281f6fdf391ff8ad50fd1490f6bf89 (diff)
downloadpodman-a031b83a09a8628435317a03f199cdc18b78262f.tar.gz
podman-a031b83a09a8628435317a03f199cdc18b78262f.tar.bz2
podman-a031b83a09a8628435317a03f199cdc18b78262f.zip
Initial checkin from CRI-O repo
Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
Diffstat (limited to 'vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go')
-rw-r--r--vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go326
1 files changed, 326 insertions, 0 deletions
diff --git a/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
new file mode 100644
index 000000000..12bef075d
--- /dev/null
+++ b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
@@ -0,0 +1,326 @@
+/*
+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"
+ "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
+}
+
+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 bool) httpstream.UpgradeRoundTripper {
+ return NewSpdyRoundTripper(tlsConfig, followRedirects)
+}
+
+// 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 bool) *SpdyRoundTripper {
+ return &SpdyRoundTripper{tlsConfig: tlsConfig, followRedirects: followRedirects}
+}
+
+// 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 = http.ProxyFromEnvironment
+ }
+ proxyURL, err := proxier(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if proxyURL == nil {
+ return s.dialWithoutProxy(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(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(url *url.URL) (net.Conn, error) {
+ dialAddr := netutil.CanonicalAddr(url)
+
+ if url.Scheme == "http" {
+ if s.Dialer == nil {
+ return net.Dial("tcp", dialAddr)
+ } else {
+ return s.Dialer.Dial("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)
+ } 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{},
+ )
+}